.. _wiki_basic_layout:
|
|
============
|
Basic Layout
|
============
|
|
The starter files generated by the ``zodb`` cookiecutter are very basic, but
|
they provide a good orientation for the high-level patterns common to most
|
:term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects.
|
|
|
Application configuration with ``__init__.py``
|
----------------------------------------------
|
|
A directory on disk can be turned into a Python :term:`package` by containing
|
an ``__init__.py`` file. Even if empty, this marks a directory as a Python
|
package. We use ``__init__.py`` both as a marker, indicating the directory in
|
which it's contained is a package, and to contain application configuration
|
code.
|
|
When you run the application using the ``pserve`` command using the
|
``development.ini`` generated configuration file, the application
|
configuration points at a setuptools *entry point* described as
|
``egg:tutorial``. In our application, because the application's ``setup.py``
|
file says so, this entry point happens to be the ``main`` function within the
|
file named ``__init__.py``.
|
|
Open ``tutorial/__init__.py``. It should already contain the following:
|
|
.. literalinclude:: src/basiclayout/tutorial/__init__.py
|
:linenos:
|
:language: py
|
|
#. *Lines 1-3*. Perform some dependency imports.
|
|
#. *Lines 6-8*. Define a :term:`root factory` for our Pyramid application.
|
|
#. *Line 11*. ``__init__.py`` defines a function named ``main``.
|
|
#. *Line 14*. Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle.
|
|
#. *Line 15*. Construct a :term:`Configurator` as a :term:`context manager` with the settings keyword parsed by :term:`PasteDeploy`.
|
|
#. *Line 16*. Include support for the :term:`Chameleon` template rendering
|
bindings, allowing us to use the ``.pt`` templates.
|
|
#. *Line 17*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.org/project/transaction/>`_ package.
|
|
#. *Line 18*. Include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
|
|
#. *Line 19*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
|
|
#. *Line 20*. Set a root factory using our function named ``root_factory``.
|
|
#. *Line 21*. Register a "static view", which answers requests whose URL
|
paths start with ``/static``, using the
|
:meth:`pyramid.config.Configurator.add_static_view` method. This
|
statement registers a view that will serve up static assets, such as CSS
|
and image files, for us, in this case, at
|
``http://localhost:6543/static/`` and below. The first argument is the
|
"name" ``static``, which indicates that the URL path prefix of the view
|
will be ``/static``. The second argument of this tag is the "path",
|
which is a relative :term:`asset specification`, so it finds the resources
|
it should serve within the ``static`` directory inside the ``tutorial``
|
package. Alternatively the cookiecutter could have used an *absolute* asset
|
specification as the path (``tutorial:static``).
|
|
#. *Line 22*. Perform a :term:`scan`. A scan will find :term:`configuration
|
decoration`, such as view configuration decorators (e.g., ``@view_config``)
|
in the source code of the ``tutorial`` package and will take actions based
|
on these decorators. We don't pass any arguments to
|
:meth:`~pyramid.config.Configurator.scan`, which implies that the scan
|
should take place in the current package (in this case, ``tutorial``).
|
The cookiecutter could have equivalently said ``config.scan('tutorial')``, but
|
it chose to omit the package name argument.
|
|
#. *Line 23*. Use the
|
:meth:`pyramid.config.Configurator.make_wsgi_app` method
|
to return a :term:`WSGI` application.
|
|
Resources and models with ``models.py``
|
---------------------------------------
|
|
:app:`Pyramid` uses the word :term:`resource` to describe objects arranged
|
hierarchically in a :term:`resource tree`. This tree is consulted by
|
:term:`traversal` to map URLs to code. In this application, the resource
|
tree represents the site structure, but it *also* represents the
|
:term:`domain model` of the application, because each resource is a node
|
stored persistently in a :term:`ZODB` database. The ``models.py`` file is
|
where the ``zodb`` cookiecutter put the classes that implement our
|
resource objects, each of which also happens to be a domain model object.
|
|
Here is the source for ``models.py``:
|
|
.. literalinclude:: src/basiclayout/tutorial/models.py
|
:linenos:
|
:language: python
|
|
#. *Lines 4-5*. The ``MyModel`` :term:`resource` class is implemented here.
|
Instances of this class are capable of being persisted in :term:`ZODB`
|
because the class inherits from the
|
:class:`persistent.mapping.PersistentMapping` class. The ``__parent__``
|
and ``__name__`` are important parts of the :term:`traversal` protocol.
|
By default, set these to ``None`` to indicate that this is the
|
:term:`root` object.
|
|
#. *Lines 8-12*. ``appmaker`` is used to return the *application
|
root* object. It is called on *every request* to the
|
:app:`Pyramid` application. It also performs bootstrapping by
|
*creating* an application root (inside the ZODB root object) if one
|
does not already exist. It is used by the ``root_factory`` we've defined
|
in our ``__init__.py``.
|
|
Bootstrapping is done by first seeing if the database has the persistent
|
application root. If not, we make an instance, store it, and commit the
|
transaction. We then return the application root object.
|
|
Views With ``views.py``
|
-----------------------
|
|
Our cookiecutter generated a default ``views.py`` on our behalf. It
|
contains a single view, which is used to render the page shown when you visit
|
the URL ``http://localhost:6543/``.
|
|
Here is the source for ``views.py``:
|
|
.. literalinclude:: src/basiclayout/tutorial/views.py
|
:linenos:
|
:language: python
|
|
Let's try to understand the components in this module:
|
|
#. *Lines 1-2*. Perform some dependency imports.
|
|
#. *Line 5*. Use the :func:`pyramid.view.view_config` :term:`configuration
|
decoration` to perform a :term:`view configuration` registration. This
|
view configuration registration will be activated when the application is
|
started. It will be activated by virtue of it being found as the result
|
of a :term:`scan` (when Line 14 of ``__init__.py`` is run).
|
|
The ``@view_config`` decorator accepts a number of keyword arguments. We
|
use two keyword arguments here: ``context`` and ``renderer``.
|
|
The ``context`` argument signifies that the decorated view callable should
|
only be run when :term:`traversal` finds the ``tutorial.models.MyModel``
|
:term:`resource` to be the :term:`context` of a request. In English, this
|
means that when the URL ``/`` is visited, because ``MyModel`` is the root
|
model, this view callable will be invoked.
|
|
The ``renderer`` argument names an :term:`asset specification` of
|
``templates/mytemplate.pt``. This asset specification points at a
|
:term:`Chameleon` template which lives in the ``mytemplate.pt`` file
|
within the ``templates`` directory of the ``tutorial`` package. And
|
indeed if you look in the ``templates`` directory of this package, you'll
|
see a ``mytemplate.pt`` template file, which renders the default home page
|
of the generated project. This asset specification is *relative* (to the
|
view.py's current package). Alternatively we could have used the
|
absolute asset specification ``tutorial:templates/mytemplate.pt``, but
|
chose to use the relative version.
|
|
Since this call to ``@view_config`` doesn't pass a ``name`` argument, the
|
``my_view`` function which it decorates represents the "default" view
|
callable used when the context is of the type ``MyModel``.
|
|
#. *Lines 6-7*. We define a :term:`view callable` named ``my_view``, which
|
we decorated in the step above. This view callable is a *function* we
|
write generated by the ``zodb`` cookiecutter that is given a
|
``request`` and which returns a dictionary. The ``mytemplate.pt``
|
:term:`renderer` named by the asset specification in the step above will
|
convert this dictionary to a :term:`response` on our behalf.
|
|
The function returns the dictionary ``{'project':'tutorial'}``. This
|
dictionary is used by the template named by the ``mytemplate.pt`` asset
|
specification to fill in certain values on the page.
|
|
Configuration in ``development.ini``
|
------------------------------------
|
|
The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as
|
opposed to the ``tutorial`` :term:`package` directory) looks like this:
|
|
.. literalinclude:: src/basiclayout/development.ini
|
:language: ini
|
|
Note the existence of a ``[app:main]`` section which specifies our WSGI
|
application. Our ZODB database settings are specified as the
|
``zodbconn.uri`` setting within this section. This value, and the other
|
values within this section, are passed as ``**settings`` to the ``main``
|
function we defined in ``__init__.py`` when the server is started via
|
``pserve``.
|