PY4WEB Different, yet cute, and a memorable evolutionary step. Dashboard Documentation Examples Tutorials Source Discuss ⌄⌄⌄ WHAT IS PY4WEB? py4web is a framework for rapid development of secure database driven web applications. It is the successor of web2py but much improved. Install it and start it $ pip install py4web # install it (but use a venv or Nix) $ py4web setup apps # answer yes to all questions $ py4web set_password # pick a password for the admin $ cp -r apps/_scaffold apps/myapp # make a new app $ py4web run apps # start py4web Each subfolder of apps/ with an __init__.py is its own app. One py4web can run multiple apps. You just copy the _scaffold app to make a new one. The basic functions/objects are imported from the py4web module. from py4web import action, redirect, request, URL, Field Use @action to map URLs into functions (aka actions). Actions can return strings or dictionaries. # http://127.0.0.1:8000/myapp/index @action("index") def index(): return "hello world" Actions can map path_info items into variables # http://127.0.0.1:8000/myapp/index/1 @action("index/") def index(x): return f"x = {x}" py4web uses a request object from ombott, compatible with bottlepy # http://127.0.0.1:8000/myapp/index/?x=1 @action("index") def index(): x = request.query.get("x") return f"x = {x}" It can parse JSON from POST requests for example # http://127.0.0.1:8000/myapp/index POST {x: 1} @action("index", method="POST") def index(): x = request.json.get("x") return {"x": x} A page can redirect to another page @action("index") def index(): redirect("http://example.com") We use URL to generate the urls of internal pages @action("index") def index(): redirect(URL("other_page")) We have a built-in session object which by default stores the session data, signed, in a cookie. Optionally it can be stored in db, redis, or other custom storage. Session is a fixture and it must be declared with @action.uses. Think of fixtures as per action (as opposed to per app) middleware. @action("index") @action.uses(session) def index(): session.x = (session.x or 0) + 1 return f"x = {x}" An action can return a dictionary and use a template to render the dictionary into HTML. A template is also a fixture and it must be declared with @action.uses. @action("index") @action.uses("index.html") def index(): x = 1 return locals() A template can be any text but typically it is HTML. Templates can extend and include other templates. Templetes can embed variables with [[=x]] and they can also embed python code (without limitations) with double square brakets. Indentation does not matter. [[pass]] closes [[ if ... ]] and [[ for ... ]]. [[extend "layout.html"]] x = [[=x]] [[ for i in range(10): ]][[ if i % 2==0: ]] [[=i]] is even [[ pass ]][[ pass ]] Py4web comes with a built-in auth object that generates all the pages required for user registration, login, email verification, retrieve and change password, edit profile, single sign on with OAuth2 and more. auth is also a fixture which exposed the current user to the action. Notice that fixtures have dependencies, and by including auth its dependencies (db, session, flash) are also included automatically. @action("index") @action.uses("generic.html", auth) def index(): user = auth.get_user() if user: message = f"Hello {user['first_name']}" else: message = "Hello, you are not logged in" return {"message": message} auth.user is a different fixture which requires a logged-in user and blocks access otherwise @action("index") @action.uses("generic.html", auth.user) def index(): user = auth.get_user() message = f"Hello {user['first_name']}" return {"message": message} More complex policies are possible using the built-in tagging system combined with auth. Condition is another fixture, if False it raises a 404 error page by default. is_manager = Condition(lambda: "manager" in groups.get(auth.user_id)) @action("index") @action.uses("generic.html", auth.user, is_manager) def index(): user = auth.get_user() message = f"Hello {user['first_name']} (manager!)" return {"message": message} Py4web has a built-in Database Abstraction Layer (support for sqlite, postgres, mysql, oracle, and more). It is integrated with auth and with form generation logic. It follows a declarative pattern and provides automatic migrations to create/alter tables. For example the following code creates a "thing" table with a "name" field and and an "image" and an additional standard signature fields ("created_by", "created_on", "modified_by", "modified_on"). Field types are more complex than basic database types as they have logic for validation and for handling content (such as uploading and downloading images). db.define_table( "thing", Field("name", requires=IS_NOT_EMPTY()), Field("image", "upload", download_url = lambda fn: URL(f"download/{fn}")), auth.signature) Given the object db.thing defined above py4web can automatically generate forms including validation. Here is a create form @action("create_thing") @action.uses("generic.html", auth.user) def create_thing(): form = Form(db.thing) if form.accepted: # record created redirect(URL("index")) return locals() Here is an edit form @action("edit_thing/") @action.uses("generic.html", auth.user) def edit_thing(thing_id): form = Form(db.thing, thing_id) if form.accepted: # record updated redirect(URL("index")) return locals() py4web can also generate a grid from a database query. The grid shows selected records with pagination and, optionally, enables creating, editing, deleting records, with multiple options for customization @action("my_things") @action("my_things/") @action.uses("generic.html", auth.user) def my_things(path=None): form = Grid(path, db.thing.created_by==auth.user_id, editable=True, create=True, deletable=True) return locals() The DAL also makes it very easy to create APIs. Here is a GET API example @action("api/things", method="GET") @action.uses(db) def api_GET_things(): return {"things": db(db.thing).select().as_list()} POST API example @action("api/things", method="POST") @action.uses(db) def api_POST_things(): return db.thing.validate_and_insert(**request.json) PUT API example @action("api/things/", method="PUT") @action.uses(db) def api_PUT_things(thing_id): return db.thing.validate_and_update(thing_id, **request.json) DELETE API example @action("api/things/", method="DELETE") @action.uses(db) def api_DELETE_things(thing_id): return {"deleted": db(db.thing.id==thing_id).delete()} These are just the basics. There is a lot more to it, including... A built-in Web UI. A built-in ticketing system to track and retrieve bugs in production. It provides full record versioning (remembers all changes to every record). internationalization and pluralization with a translation UI. Automatic generation of RESTful APIs for your database. Built-in logic for user impersonation. Integrated SSO support for Google, Github, Facebook, Okta, LDAP, PAM A built-in scheduler for submitting and managing async background tasks. LICENSE 3-clause BSD USEFUL LINKS A minimalist Facebook clone A minimalist Twitter clone (with Vue)1774499281https://py4web.com