diff --git a/docs/chapter-06.rst b/docs/chapter-06.rst index 8fc7c87d..b3437314 100644 --- a/docs/chapter-06.rst +++ b/docs/chapter-06.rst @@ -21,7 +21,8 @@ save the session back in the database if data has changed. PY4WEB fixtures provide a mechanism to specify what an action needs so that py4web can accomplish the required tasks (and skip non required ones) in the most efficient manner. Fixtures make the code efficient and -reduce the need for boilerplate code. +reduce the need for boilerplate code. Think of fixtures as per action +(as opposed to per app) middleware. PY4WEB fixtures are similar to WSGI middleware and BottlePy plugin except that they apply to individual actions, not to all of them, and @@ -409,9 +410,14 @@ Client-side session in cookies By default the session object is stored inside a cookie called ``appname_session``. It's a JWT, hence encoded in a URL-friendly string format and signed using the provided secret for preventing tampering. -Notice that it's not encrypted (in fact it's quite trivial to read its -content from http communications or from disk), so do not place any -sensitive information inside, and use a complex secret. + +.. warning:: + + Data embedded in cookies is signed, not encrypted! In fact it's quite + trivial to read its content from http communications or from disk, so + do not place any sensitive information inside, and use a complex secret. + + If the secret changes existing sessions are invalidated. If the user switches from HTTP to HTTPS or vice versa, the user session is also invalidated. Session in cookies have a @@ -539,7 +545,7 @@ data from another app (app1) running on the same server: The Condition fixture --------------------- -Some times you want to restrict access to an action based on a +Sometimes you want to restrict access to an action based on a given condition. For example to enforce a workflow: .. code:: python @@ -585,9 +591,10 @@ for example, to redirect to another page: Condition(cond, on_false=lambda: redirect(URL('step1'))) -You can use condition to check permissions. For example, assuming you are using -`Tags` as explained in chapter 13 and you are giving group memberships to users, -then you can require that users action have specific group membership: +You can use condition to check permissions. For example, if you +are giving group memberships to users using `Tags` (it will be explained +later on the :ref:`Authorization using Tags` chapter), then you can +require that users action have specific group membership: .. code:: python diff --git a/docs/chapter-07.rst b/docs/chapter-07.rst index 0f379d06..4e15ed98 100644 --- a/docs/chapter-07.rst +++ b/docs/chapter-07.rst @@ -5,7 +5,7 @@ The Database Abstraction Layer (DAL) DAL introduction ---------------- -py4web rely on a database abstraction layer (DAL), an API that maps +py4web rely on a database abstraction layer (**DAL**), an API that maps Python objects into database objects such as queries, tables, and records. The DAL dynamically generates the SQL in real time using the specified dialect for the database back end, so that you do not have to @@ -16,6 +16,13 @@ The DAL choosen is a pure Python one called `pyDAL >>`` are also directly executable via a py4web shell. -This is a simple example, using the provided ``examples`` app: +This is a simple example, using the provided ``showcase`` app: .. code:: python - >>> from py4web import DAL, Field - >>> from apps.examples import db + >>> from apps.showcase.examples.models import db >>> db.tables() - ['auth_user', 'auth_user_tag_groups', 'person', 'superhero', 'superpower', 'tag', 'product', 'thing'] + ['auth_user', 'auth_user_tag_groups', 'person', 'superhero', 'superpower', 'tag', 'thing', 'user_token', 'dummy'] >>> rows = db(db.superhero.name != None).select() >>> rows.first() , 'name': 'Superman', 'real_identity': 1}> @@ -210,6 +219,24 @@ You can also start by creating a connection from zero. For the sake of simplicit can use SQLite. Nothing in this discussion changes when you switch the back-end engine. +Using the dashboard app with databases +-------------------------------------- + +Generally you can use the dashboard app for viewing and modifying the databases +of a particular app. However this is not bulletproof, so for +security reason this by default is not applied to the showcase app. +But if your installation is local (not exposed to public networks), you can enable it +by simply adding to the file``apps/showcase/__init__.py`` the line: + +.. code:: python + + from .examples.models import db + + +This allow you to look graphically inside the showcase application database: + +.. image:: images/example_db.png + DAL constructor --------------- @@ -321,6 +348,10 @@ Database Connection string - in SQLite the database consists of a single file. If it does not exist, it is created. This file is locked every time it is accessed. + In addition to the file 'storage.sqlite' that contains the data, there will + be also a sql.log file plus one additional file called longhash_tablename.table + for every table definition. The table definition files are used during migrations; + in case of problems they could be deleted (they'll be automatically recreated). - in the case of MySQL, PostgreSQL, MSSQL, FireBird, Oracle, DB2, Ingres and Informix the database “test” must be created outside py4web. Once the connection is established, py4web will create, alter, and drop @@ -415,8 +446,8 @@ table is actually referenced. Model-less applications ~~~~~~~~~~~~~~~~~~~~~~~ -In py4web the code defined outside of actions (where normally DAL tables -are defined) is only executed at startup. +Normally in py4web the code that define DAL tables lives in the file +``models.py``, hence it's only executed at startup because it's outside of actions. However, it is possible to define DAL tables on demand inside actions. This is referred to as “model-less” development by the py4web community. @@ -546,15 +577,24 @@ Database folder location ^^^^^^^^^^^^^^^^^^^^^^^^ ``folder`` sets the place where migration files will be created (see -Migrations_ for details). -It is also used for SQLite databases. Automatically set within py4web. -Set a path when using DAL outside py4web. +Migrations_ for details). By default it's automatically set within py4web on the same +folder of the database itself, but you have to specify it when using DAL outside py4web. + +Note that for SQLite databases it's normally necessary, +otherwise you'll implictly choose an in memory database (where folder and +migrations don't have any sense). So these constructors have the same meaning: + +.. code:: python + + db = DAL('sqlite://storage.sqlite') # folder parameter not specified + db = DAL('sqlite:memory') # in memory database + Default migration settings ^^^^^^^^^^^^^^^^^^^^^^^^^^ The DAL constructor migration settings are booleans affecting defaults -and global behaviour. +and global behaviour (again, see Migrations_ for details) ``migrate = True`` sets default migrate behavior for all tables @@ -574,7 +614,7 @@ operations may be executed immediately, depending on the database engine. If you pass ``db`` in an ``action.uses`` decorator, you don't need to call -commit in the controller, it is done for you. (Also, if you use +commit in the controller, it is automatically done for you (also, if you use ``authenticated`` or ``unauthenticated`` decorator.) .. tip:: @@ -1644,9 +1684,9 @@ database. db = DAL("sqlite:memory") db.define_table("thing", Field("name")) - properties = Tags(db.thing) id1 = db.thing.insert(name="chair") id2 = db.thing.insert(name="table") + properties = Tags(db.thing) properties.add(id1, "color/red") properties.add(id1, "style/modern") properties.add(id2, "color/green") @@ -1664,14 +1704,16 @@ database. rows = db(properties.find(["color"])).select() assert len(rows) == 2 -It is internally implemented as a table, which in + +``Tags`` are hierarchical. Then ``find([“color”])`` would return id1 and id2 +because both records have tags with “color”. + +It is internally implemented with the creation of an additional table, which in this example would be db.thing_tags_default, because no tail was -specified on the Tags(table, tail=“default”) constructor. +specified on the ``Tags(table, tail=“default”)`` constructor. -The ``find`` method is doing a search by ``startswith`` of the -parameter. Then find([“color”]) would return id1 and id2 -because both records have tags starting with “color”. py4web uses tags as a -flexible mechanism to manage permissions. +py4web uses ``Tags`` as a flexible mechanism to manage permissions, we'll see +all the details later on the :ref:`Authorization using Tags` chapter. Raw SQL @@ -3303,10 +3345,9 @@ and all owners of Boat: Alex Curt -A lighter alternative to many-to-many relations is tagging, you can -found an example of this in the next section. Tagging works even on -database backends that do not support JOINs like the Google App Engine -NoSQL. +A lighter alternative to many-to-many relations is tagging, see the +:ref:`Authorization using Tags` chapter. Tagging works even on database backends +that do not support JOINs like the Google App Engine NoSQL. Self-Reference and aliases ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3962,7 +4003,7 @@ it to XML/HTML: If you need to serialize the Rows in any other XML format with custom -tags, you can easily do that using the universal :ref:`TAG` helper +tags, you can easily do that using the universal ``TAG`` XML helper that we'll see later and the Python syntax ``*`` allowed in function calls: @@ -3979,6 +4020,13 @@ that we'll see later and the Python syntax 3Carl +.. warning:: + + Do not confuse the `TAG` XML helper used here (see the :ref:`TAG` + chapter) with the ``Tags`` method that will be extensively explained + on the :ref:`Authorization using Tags` chapter. + + Data representation ~~~~~~~~~~~~~~~~~~~ diff --git a/docs/chapter-13.rst b/docs/chapter-13.rst index 192b8321..64441a95 100644 --- a/docs/chapter-13.rst +++ b/docs/chapter-13.rst @@ -356,7 +356,8 @@ Authorization using Tags As already mentioned, authorization is the process of verifying what specific applications, files, and data a user has access to. This is accomplished -in py4web using ``Tags``. +in py4web using ``Tags``, that we've already discovered on :ref:`Tagging records` +in the DAL chapter. Tags and Permissions @@ -382,14 +383,16 @@ from ``pydal.tools``. Then create a Tags object to tag a table: .. code:: python from pydal.tools.tags import Tags - groups = Tags(db.auth_user) + groups = Tags(db.auth_user, 'groups') -If you look at the database level, a new table will be created with a -name equals to tagged_db + '_tag' + tagged_name, in this case -``auth_user_tag_groups``: +The tail_name parameter is optional and if not specified the 'default' +value will be used. If you look at the database level, a new table will +be created with a name equals to ``tagged_db + '_tag_' + tail_name``, +in this case ``auth_user_tag_groups``: .. image:: images/tags_db.png + Then you can add one or more tags to records of the table as well as remove existing tags: @@ -434,6 +437,25 @@ tag(s): users = db(groups.find([group_name])).select(orderby=db.auth_user.first_name | db.auth_user.last_name) return {'users': users} +We've already seen a simple ``requires_membership`` fixture on :ref:``The Condition fixture``. It +enables the following syntax: + +.. code:: python + + groups = Tags(db.auth_user) + + def requires_membership(group_name): + return Condition( + lambda: group_name in groups.get(auth.user_id), + exception=HTTP(404) + ) + + @action('index') + @action.uses(requires_membership('teacher')) + def index(): + return 'hello teacher' + + We leave it to you as an exercise to create a fixture ``has_membership`` to enable the following syntax: diff --git a/docs/images/example_db.png b/docs/images/example_db.png new file mode 100644 index 00000000..7ae7315b Binary files /dev/null and b/docs/images/example_db.png differ diff --git a/docs/images/first_run.png b/docs/images/first_run.png index 59320040..a670f015 100644 Binary files a/docs/images/first_run.png and b/docs/images/first_run.png differ