diff options
Diffstat (limited to 'docs')
108 files changed, 1728 insertions, 1699 deletions
diff --git a/docs/conf.py b/docs/conf.py index 15cffb1eb..3b7eb261a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,26 +61,26 @@ extensions = [ # Looks for objects in external projects intersphinx_mapping = { - 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest', None), + 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None), 'cookbook': ('https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None), 'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None), - 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest', None), + 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None), 'jinja2': ('https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None), 'plaster': ('https://docs.pylonsproject.org/projects/plaster/en/latest/', None), 'pylonswebframework': ('https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), - 'python': ('https://docs.python.org/3', None), + 'python': ('https://docs.python.org/3/', None), 'pytest': ('https://docs.pytest.org/en/latest/', None), - 'sphinx': ('http://www.sphinx-doc.org/en/latest', None), - 'sqla': ('https://docs.sqlalchemy.org/en/latest', None), + 'sphinx': ('http://www.sphinx-doc.org/en/latest/', None), + 'sqla': ('https://docs.sqlalchemy.org/en/latest/', None), 'tm': ('https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None), - 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None), - 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest', None), + 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest/', None), + 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest/', None), 'tutorials': ('https://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None), - 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest', None), + 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest/', None), 'webob': ('https://docs.pylonsproject.org/projects/webob/en/latest/', None), 'webtest': ('https://docs.pylonsproject.org/projects/webtest/en/latest/', None), - 'who': ('https://repozewho.readthedocs.io/en/latest', None), - 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), + 'who': ('https://repozewho.readthedocs.io/en/latest/', None), + 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest/', None), 'zcomponent': ('https://zopecomponent.readthedocs.io/en/latest/', None), 'zinterface': ('https://zopeinterface.readthedocs.io/en/latest/', None), } diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index b7eeb19ae..ef914cab5 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -1,39 +1,33 @@ .. _wiki_adding_authorization: -==================== -Adding authorization -==================== - -:app:`Pyramid` provides facilities for :term:`authentication` and -:term:`authorization`. We'll make use of both features to provide security to -our application. Our application currently allows anyone with access to the -server to view, edit, and add pages to our wiki. We'll change that to allow -only people who are members of a *group* named ``group:editors`` to add and -edit wiki pages, but we'll continue allowing anyone with access to the server -to view pages. - -We will also add a login page and a logout link on all the pages. The login -page will be shown when a user is denied access to any of the views that +======================================= +Adding authorization and authentication +======================================= + +:app:`Pyramid` provides facilities for :term:`authentication` and :term:`authorization`. +We will make use of both features to provide security to our application. +Our application currently allows anyone with access to the server to view, edit, and add pages to our wiki. +We will change that to allow only people who are members of a *group* named ``group:editors`` to add and edit wiki pages. +We will continue to allow anyone with access to the server to view pages. + +We will also add a login page and a logout link on all the pages. +The login page will be shown when a user is denied access to any of the views that require permission, instead of a default "403 Forbidden" page. We will implement the access control with the following steps: -* Add password hashing dependencies. -* Add users and groups (``security.py``, a new module). -* Add an :term:`ACL` (``models.py``). -* Add an :term:`authentication policy` and an :term:`authorization policy` - (``__init__.py``). -* Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` - views (``views.py``). +- Add password hashing dependencies. +- Add users and groups (``security.py``, a new module). +- Add an :term:`ACL` (``models.py``). +- Add an :term:`authentication policy` and an :term:`authorization policy` (``__init__.py``). +- Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` views (``views.py``). Then we will add the login and logout features: -* Add ``login`` and ``logout`` views (``views.py``). -* Add a login template (``login.pt``). -* Make the existing views return a ``logged_in`` flag to the renderer - (``views.py``). -* Add a "Logout" link to be shown when logged in and viewing or editing a page - (``view.pt``, ``edit.pt``). +- Add ``login`` and ``logout`` views (``views.py``). +- Add a login template (``login.pt``). +- Make the existing views return a ``logged_in`` flag to the renderer (``views.py``). +- Add a "Logout" link to be shown when logged in and viewing or editing a page (``view.pt``, ``edit.pt``). Access control @@ -43,14 +37,15 @@ Access control Add dependencies ~~~~~~~~~~~~~~~~ -Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. +Just like in :ref:`wiki_defining_views`, we need a new dependency. +We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. Open ``setup.py`` and edit it to look like the following: .. literalinclude:: src/authorization/setup.py - :linenos: - :emphasize-lines: 23 - :language: python + :linenos: + :emphasize-lines: 23 + :language: python Only the highlighted line needs to be added. @@ -58,7 +53,9 @@ Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-in .. note:: - We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash. + We are using the ``bcrypt`` package from PyPI to hash our passwords securely. + There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. + Just make sure that it is an algorithm approved for storing passwords versus a generic one-way hash. Add users and groups @@ -67,78 +64,78 @@ Add users and groups Create a new ``tutorial/security.py`` module with the following content: .. literalinclude:: src/authorization/tutorial/security.py - :linenos: - :language: python + :linenos: + :language: python -The ``groupfinder`` function accepts a userid and a request and -returns one of these values: +The ``groupfinder`` function accepts a ``userid`` and a ``request`` +It returns one of these values: -- If ``userid`` exists in the system, it will return a sequence of group - identifiers (or an empty sequence if the user isn't a member of any groups). -- If the userid *does not* exist in the system, it will return ``None``. +- If ``userid`` exists in the system, it will return either a sequence of group identifiers, or an empty sequence if the user is not a member of any groups. +- If the userid *does not* exist in the system, it will return ``None``. -For example, ``groupfinder('editor', request )`` returns ``['group:editor']``, -``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin', -request)`` returns ``None``. We will use ``groupfinder()`` as an -:term:`authentication policy` "callback" that will provide the -:term:`principal` or principals for a user. +For example: + +- ``groupfinder('editor', request )`` returns ``['group:editor']``. +- ``groupfinder('viewer', request)`` returns ``[]``. +- ``groupfinder('admin', request)`` returns ``None``. + +We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user. There are two helper methods that will help us later to authenticate users. The first is ``hash_password`` which takes a raw password and transforms it using -bcrypt into an irreversible representation, a process known as "hashing". The -second method, ``check_password``, will allow us to compare the hashed value of the -submitted password against the hashed value of the password stored in the user's -record. If the two hashed values match, then the submitted -password is valid, and we can authenticate the user. +bcrypt into an irreversible representation, a process known as "hashing". +The second method, ``check_password``, will allow us to compare the hashed value of the submitted password against the hashed value of the password stored in the user's +record. +If the two hashed values match, then the submitted password is valid, and we can authenticate the user. -We hash passwords so that it is impossible to decrypt and use them to -authenticate in the application. If we stored passwords foolishly in clear text, -then anyone with access to the database could retrieve any password to authenticate -as any user. +We hash passwords so that it is impossible to decrypt and use them to authenticate in the application. +If we stored passwords foolishly in clear text, then anyone with access to the database could retrieve any password to authenticate as any user. In a production system, user and group data will most often be saved and come from a -database, but here we use "dummy" data to represent user and groups sources. +database. +Here we use "dummy" data to represent user and groups sources. + Add an ACL ~~~~~~~~~~ -Open ``tutorial/models.py`` and add the following import -statement near the top: +Open ``tutorial/models.py`` and add the following import statement near the top: -.. literalinclude:: src/authorization/tutorial/models.py - :lines: 4-8 - :lineno-match: - :language: python +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :lines: 4-8 + :lineno-match: + :language: python Add the following lines to the ``Wiki`` class: -.. literalinclude:: src/authorization/tutorial/models.py - :lines: 9-13 - :lineno-match: - :emphasize-lines: 4-5 - :language: python - -We import :data:`~pyramid.security.Allow`, an action that means that -permission is allowed, and :data:`~pyramid.security.Everyone`, a special -:term:`principal` that is associated to all requests. Both are used in the -:term:`ACE` entries that make up the ACL. - -The ACL is a list that needs to be named ``__acl__`` and be an attribute of a -class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry -allows any user the ``view`` permission. The second entry allows the -``group:editors`` principal the ``edit`` permission. - -The ``Wiki`` class that contains the ACL is the :term:`resource` constructor -for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is -provided to each view in the :term:`context` of the request as the ``context`` -attribute. - -It's only happenstance that we're assigning this ACL at class scope. An ACL -can be attached to an object *instance* too; this is how "row level security" -can be achieved in :app:`Pyramid` applications. We actually need only *one* -ACL for the entire system, however, because our security requirements are -simple, so this feature is not demonstrated. See :ref:`assigning_acls` for -more information about what an :term:`ACL` represents. +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :lines: 9-13 + :lineno-match: + :emphasize-lines: 4-5 + :language: python + +We import :data:`~pyramid.security.Allow`, an action which means that +permission is allowed. +We also import :data:`~pyramid.security.Everyone`, a special :term:`principal` that is associated to all requests. +Both are used in the :term:`ACE` entries that make up the ACL. + +The ACL is a list that needs to be named ``__acl__`` and be an attribute of a class. +We define an :term:`ACL` with two :term:`ACE` entries. +The first entry allows any user the ``view`` permission. +The second entry allows the ``group:editors`` principal the ``edit`` permission. + +The ``Wiki`` class that contains the ACL is the :term:`resource` constructor for the :term:`root` resource, which is a ``Wiki`` instance. +The ACL is provided to each view in the :term:`context` of the request as the ``context`` attribute. + +It is only happenstance that we assigned this ACL at class scope. +An ACL can be attached to an object *instance* too. +This is how "row level security" can be achieved in :app:`Pyramid` applications. +We actually need only *one* ACL for the entire system, however, because our security requirements are simple, so this feature is not demonstrated. + +.. seealso:: + + See :ref:`assigning_acls` for more information about what an :term:`ACL` represents. + Add authentication and authorization policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -147,123 +144,113 @@ Open ``tutorial/__init__.py`` and add the highlighted import statements: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 1-8 - :linenos: - :emphasize-lines: 3-6,8 - :language: python + :lines: 1-8 + :linenos: + :emphasize-lines: 3-6,8 + :language: python Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 18-25 - :lineno-match: - :emphasize-lines: 2-4,6-7 - :language: python + :lines: 15-25 + :lineno-match: + :emphasize-lines: 4-6,8-9 + :language: python Only the highlighted lines need to be added. -We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth -ticket that may be included in the request. We are also enabling an -``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or -*deny* outcome for a view. +We enabled an ``AuthTktAuthenticationPolicy`` which is based in an auth ticket that may be included in the request. +We also enabled an ``ACLAuthorizationPolicy`` which uses an ACL to determine the *allow* or *deny* outcome for a view. + +Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor accepts two arguments: ``secret`` and ``callback``. +``secret`` is a string representing an encryption key used by the "authentication ticket" machinery represented by this policy. +It is required. +The ``callback`` is the ``groupfinder()`` function that we created earlier. -Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` -constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is -a string representing an encryption key used by the "authentication ticket" -machinery represented by this policy: it is required. The ``callback`` is the -``groupfinder()`` function that we created before. Add permission declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter -to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 49-51 - :emphasize-lines: 2-3 - :language: python +Open ``tutorial/views/default.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: + +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 49-51 + :emphasize-lines: 2-3 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 68-70 - :emphasize-lines: 2-3 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 68-70 + :emphasize-lines: 2-3 + :language: python -Only the highlighted lines, along with their preceding commas, need to be -edited and added. +Only the highlighted lines, along with their preceding commas, need to be edited and added. -The result is that only users who possess the ``edit`` permission at the time -of the request may invoke those two views. +The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views. Add a ``permission='view'`` parameter to the ``@view_config`` decorator for ``view_wiki()`` and ``view_page()`` as follows: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 23-24 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 23-24 + :emphasize-lines: 1-2 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 28-29 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 28-29 + :emphasize-lines: 1-2 + :language: python -Only the highlighted lines, along with their preceding commas, need to be -edited and added. +Only the highlighted lines, along with their preceding commas, need to be edited and added. This allows anyone to invoke these two views. -We are done with the changes needed to control access. The changes that -follow will add the login and logout feature. +We are done with the changes needed to control access. +The changes that follow will add the login and logout feature. + Login, logout ------------- + Add login and logout views ~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll add a ``login`` view which renders a login form and processes the post -from the login form, checking credentials. +We will add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. -We'll also add a ``logout`` view callable to our application and provide a -link to it. This view will clear the credentials of the logged in user and -redirect back to the front page. +We will also add a ``logout`` view callable to our application and provide a link to it. +This view will clear the credentials of the logged in user and redirect back to the front page. -Add the following import statements to the head of -``tutorial/views.py``: +Add the following import statements to the head of ``tutorial/views/default.py``: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 6-17 - :emphasize-lines: 1-12 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 4-15 + :emphasize-lines: 2-10,12 + :language: python All the highlighted lines need to be added or edited. -:meth:`~pyramid.view.forbidden_view_config` will be used to customize the -default 403 Forbidden page. :meth:`~pyramid.security.remember` and -:meth:`~pyramid.security.forget` help to create and expire an auth ticket -cookie. +:meth:`~pyramid.view.forbidden_view_config` will be used to customize the default 403 Forbidden page. +:meth:`~pyramid.security.remember` and :meth:`~pyramid.security.forget` help to create and expire an auth ticket cookie. Now add the ``login`` and ``logout`` views at the end of the file: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 80- - :lineno-match: - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 78- + :lineno-match: + :language: python ``login()`` has two decorators: -- a ``@view_config`` decorator which associates it with the ``login`` route - and makes it visible when we visit ``/login``, -- a ``@forbidden_view_config`` decorator which turns it into a - :term:`forbidden view`. ``login()`` will be invoked when a user tries to - execute a view callable for which they lack authorization. For example, if - a user has not logged in and tries to add or edit a Wiki page, they will be - shown the login form before being allowed to continue. +- A ``@view_config`` decorator which associates it with the ``login`` route and makes it visible when we visit ``/login``. +- A ``@forbidden_view_config`` decorator which turns it into a :term:`forbidden view`. + ``login()`` will be invoked when a user tries to execute a view callable for which they lack authorization. + For example, if a user has not logged in and tries to add or edit a Wiki page, then they will be shown the login form before being allowed to continue. The order of these two :term:`view configuration` decorators is unimportant. -``logout()`` is decorated with a ``@view_config`` decorator which associates -it with the ``logout`` route. It will be invoked when we visit ``/logout``. +``logout()`` is decorated with a ``@view_config`` decorator which associates it with the ``logout`` route. +It will be invoked when we visit ``/logout``. + Add the ``login.pt`` Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -271,134 +258,127 @@ Add the ``login.pt`` Template Create ``tutorial/templates/login.pt`` with the following content: .. literalinclude:: src/authorization/tutorial/templates/login.pt - :language: html + :language: html + +The above template is referenced in the login view that we just added in ``views.py``. -The above template is referenced in the login view that we just added in -``views.py``. Return a ``logged_in`` flag to the renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to -the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as -follows: +Open ``tutorial/views/default.py`` again. +Add a ``logged_in`` parameter to the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as follows: -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 46-47 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 45-46 + :emphasize-lines: 1-2 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 65-66 - :emphasize-lines: 1-2 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 65-66 + :emphasize-lines: 1-2 + :language: python -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 76-78 - :emphasize-lines: 2-3 - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :lines: 77-79 + :emphasize-lines: 2-3 + :language: python Only the highlighted lines need to be added or edited. -The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if -the user is not authenticated, or a userid if the user is authenticated. +The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if the user is not authenticated, or a ``userid`` if the user is authenticated. + Add a "Logout" link when logged in ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Open ``tutorial/templates/edit.pt`` and -``tutorial/templates/view.pt`` and add the following code as -indicated by the highlighted lines. +Open ``tutorial/templates/edit.pt`` and ``tutorial/templates/view.pt``. +Add the following code as indicated by the highlighted lines. .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :lines: 35-39 - :emphasize-lines: 2-4 - :language: html + :lines: 4-8 + :emphasize-lines: 2-4 + :language: html -The attribute ``tal:condition="logged_in"`` will make the element be included -when ``logged_in`` is any user id. The link will invoke the logout view. The -above element will not be included if ``logged_in`` is ``None``, such as when +The attribute ``tal:condition="logged_in"`` will make the element be included when ``logged_in`` is any user id. +The link will invoke the logout view. +The above element will not be included if ``logged_in`` is ``None``, such as when a user is not authenticated. + Reviewing our changes --------------------- -Our ``tutorial/__init__.py`` will look like this when we're done: +Our ``tutorial/__init__.py`` will look like this when we are done: .. literalinclude:: src/authorization/tutorial/__init__.py - :linenos: - :emphasize-lines: 4-5,8,19-21,23-24 - :language: python + :linenos: + :emphasize-lines: 3-6,8,18-20,22-23 + :language: python Only the highlighted lines need to be added or edited. -Our ``tutorial/models.py`` will look like this when we're done: +Our ``tutorial/models.py`` will look like this when we are done: -.. literalinclude:: src/authorization/tutorial/models.py - :linenos: - :emphasize-lines: 4-7,12-13 - :language: python +.. literalinclude:: src/authorization/tutorial/models/__init__.py + :linenos: + :emphasize-lines: 4-8,12-13 + :language: python Only the highlighted lines need to be added or edited. -Our ``tutorial/views.py`` will look like this when we're done: +Our ``tutorial/views/default.py`` will look like this when we are done: -.. literalinclude:: src/authorization/tutorial/views.py - :linenos: - :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80- - :language: python +.. literalinclude:: src/authorization/tutorial/views/default.py + :linenos: + :emphasize-lines: 5-12,15,21-22,27-28,45-46,50-51,65-66,70-71,78- + :language: python Only the highlighted lines need to be added or edited. -Our ``tutorial/templates/edit.pt`` template will look like this when -we're done: +Our ``tutorial/templates/edit.pt`` template will look like this when we are done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :linenos: - :emphasize-lines: 36-38 - :language: html + :linenos: + :emphasize-lines: 5-7 + :language: html Only the highlighted lines need to be added or edited. -Our ``tutorial/templates/view.pt`` template will look like this when -we're done: +Our ``tutorial/templates/view.pt`` template will look like this when we are done: .. literalinclude:: src/authorization/tutorial/templates/view.pt - :linenos: - :emphasize-lines: 36-38 - :language: html + :linenos: + :emphasize-lines: 5-7 + :language: html Only the highlighted lines need to be added or edited. Viewing the application in a browser ------------------------------------ -We can finally examine our application in a browser (See -:ref:`wiki-start-the-application`). Launch a browser and visit each of the -following URLs, checking that the result is as expected: - -- http://localhost:6543/ invokes the ``view_wiki`` view. This always - redirects to the ``view_page`` view of the ``FrontPage`` Page resource. It - is executable by any user. - -- http://localhost:6543/FrontPage invokes the ``view_page`` view of the - ``FrontPage`` Page resource. This is because it's the :term:`default view` - (a view without a ``name``) for ``Page`` resources. It is executable by any - user. - -- http://localhost:6543/FrontPage/edit_page invokes the edit view for the - FrontPage object. It is executable by only the ``editor`` user. If a - different user (or the anonymous user) invokes it, a login form will be - displayed. Supplying the credentials with the username ``editor``, password - ``editor`` will display the edit page form. - -- http://localhost:6543/add_page/SomePageName invokes the add view for a page. - It is executable by only the ``editor`` user. If a different user (or the - anonymous user) invokes it, a login form will be displayed. Supplying the - credentials with the username ``editor``, password ``editor`` will display - the edit page form. - -- After logging in (as a result of hitting an edit or add page and submitting - the login form with the ``editor`` credentials), we'll see a Logout link in - the upper right hand corner. When we click it, we're logged out, and - redirected back to the front page. +We can finally examine our application in a browser (See :ref:`wiki-start-the-application`). +Launch a browser and visit each of the following URLs, checking that the result is as expected: + +- http://localhost:6543/ invokes the ``view_wiki`` view. + This always redirects to the ``view_page`` view of the ``FrontPage`` Page resource. + It is executable by any user. + +- http://localhost:6543/FrontPage invokes the ``view_page`` view of the ``FrontPage`` Page resource. + This is because it is the :term:`default view` (a view without a ``name``) for ``Page`` resources. + It is executable by any user. + +- http://localhost:6543/FrontPage/edit_page invokes the edit view for the FrontPage object. + It is executable by only the ``editor`` user. + If a different user (or the anonymous user) invokes it, then a login form will be displayed. + Supplying the credentials with the username ``editor`` and password ``editor`` will display the edit page form. + +- http://localhost:6543/add_page/SomePageName invokes the add view for a page. + It is executable by only the ``editor`` user. + If a different user (or the anonymous user) invokes it, a login form will be displayed. + Supplying the credentials with the username ``editor``, password ``editor`` will display the edit page form. + +- After logging in (as a result of hitting an edit or add page and submitting the login form with the ``editor`` credentials), we will see a Logout link in the upper right hand corner. + When we click it, we are logged out, and redirected back to the front page. + +- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter. diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst index c10ab9e55..1a076de85 100644 --- a/docs/tutorials/wiki/background.rst +++ b/docs/tutorials/wiki/background.rst @@ -15,9 +15,4 @@ To code along with this tutorial, the developer will need a Unix machine with development tools (macOS with XCode, any Linux or BSD variant, and so on) *or* a Windows system of any kind. -.. warning:: - - This tutorial has been written for Python 2. It is unlikely to work - without modification under Python 3. - Have fun! diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 49ee6902e..52d3f4670 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -4,188 +4,255 @@ Basic Layout ============ -The starter files generated by selecting the ``zodb`` backend in the -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. +The starter files generated by the 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. +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 is 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 :term:`Setuptools` :term:`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``. +When you run the application using the ``pserve`` command using the ``development.ini`` generated configuration file, the application configuration points at a :term:`Setuptools` :term:`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: +Open ``tutorial/__init__.py``. +It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/__init__.py - :linenos: - :language: py + :linenos: + :language: py + +Let's go over this piece-by-piece. +First we need some imports to support later code. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :end-before: root_factory + :lineno-match: + :language: py + +Define a :term:`root factory` for our Pyramid application. +It establishes a connection to ZODB database. +It returns an ``appmaker``, which we will describe in the next section :ref:`wiki-resources-and-models`. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :pyobject: root_factory + :lineno-match: + :language: py + +``__init__.py`` defines a function named ``main``. +Here is the entirety of the ``main`` function that we have defined in our ``__init__.py``: + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :pyobject: main + :lineno-match: + :language: py + +When you invoke the ``pserve development.ini`` command, the ``main`` function above is executed. +It accepts some settings and returns a :term:`WSGI` application. +See :ref:`startup_chapter` for more about ``pserve``. + +Next in ``main``, construct a :term:`Configurator` object using a context manager. +See also :term:`Deployment settings`. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 14 + :lineno-match: + :language: py + +``settings`` is passed to the ``Configurator`` as a keyword argument with the dictionary values passed as the ``**settings`` argument. +This will be a dictionary of settings parsed from the ``.ini`` file, which contains +deployment-related values, such as ``pyramid.reload_templates``, ``zodbconn.uri``, and so on. + +Next include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.org/project/transaction/>`_ package. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 15 + :lineno-match: + :language: py + +Next include support for ``pyramid_retry`` to retry a request when transient exceptions occur. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 16 + :lineno-match: + :language: py + +Next include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 17 + :lineno-match: + :language: py + +Next set a root factory using our function named ``root_factory``. + +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 18 + :lineno-match: + :language: py + +Next include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. -#. *Lines 1-3*. Perform some dependency imports. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 19 + :lineno-match: + :language: py -#. *Lines 6-8*. Define a :term:`root factory` for our Pyramid application. +Next include routes from the ``.routes`` module. -#. *Line 11*. ``__init__.py`` defines a function named ``main``. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 20 + :lineno-match: + :language: py -#. *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. +This registers a "static view" using the :meth:`pyramid.config.Configurator.add_static_view` method. +This view answers requests whose URL path starts with ``/static``. +This statement registers a view that will serve up static assets, such as CSS and image files. +In this case the URL will answer requests at ``http://localhost:6543/static/`` and below. -#. *Line 15*. Construct a :term:`Configurator` as a :term:`context manager` with the settings keyword parsed by :term:`PasteDeploy`. +The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``. -#. *Line 16*. Include support for the :term:`Chameleon` template rendering - bindings, allowing us to use the ``.pt`` templates. +The second argument of this method is the "path". +It is a relative :term:`asset specification`. +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 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. +The third argument is an optional ``cache_max_age`` which specifies the number of seconds the static asset will be HTTP-cached. -#. *Line 18*. Include support for ``pyramid_retry`` to retry a request when transient exceptions occur. +Next perform a :term:`scan`. -#. *Line 19*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 21 + :lineno-match: + :language: py -#. *Line 20*. Set a root factory using our function named ``root_factory``. +A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package. +It 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 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``). +Finally use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. -#. *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. +.. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 22 + :lineno-match: + :language: py -#. *Line 23*. Use the - :meth:`pyramid.config.Configurator.make_wsgi_app` method - to return a :term:`WSGI` application. -Resources and models with ``models.py`` ---------------------------------------- +.. _wiki-resources-and-models: -: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. +Resources and models with ``models`` package +-------------------------------------------- + +: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. +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 +.. literalinclude:: src/basiclayout/tutorial/models/__init__.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 +#. *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, these are set 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 by virtue of the ``root_factory`` defined in our ``__init__.py``. + It also performs bootstrapping by *creating* an application root (inside the ZODB root object) if one does not already exist. + + Bootstrapping is done by first seeing if the database has the persistent application root. + If not, then we make an instance, store it, and commit the transaction. + + We then return the application root object. + + +View declarations via the ``views`` package +------------------------------------------- + +Our cookiecutter generated a default ``views`` package on our behalf. +It contains a two views. + +The first view is used to render the page shown when you visit the URL ``http://localhost:6543/``. +Open ``tutorial/views/default.py`` in the ``views`` package. +It should already contain the following: + +.. literalinclude:: src/basiclayout/tutorial/views/default.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. +#. *Lines 1-3*. + Perform some dependency imports. + +#. *Line 6*. + 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. + Remember in our application's ``__init__.py`` when we executed the :meth:`pyramid.config.Configurator.scan` method ``config.scan()``? + By calling the scan method, Pyramid's configurator will find and process this ``@view_config`` decorator, and create a view configuration within our application. + Without being processed by ``scan``, the decorator effectively does nothing. + ``@view_config`` is inert without being detected via a :term:`scan`. + + 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 ``my_view`` should only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` :term:`resource` as the :term:`context` of a request. + In English this means that when the URL ``/`` is visited, and 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 will see a ``mytemplate.pt`` template file + This template renders the default home page of the generated project. + This asset specification is *relative* to the ``views`` package. + Alternatively we could have used the absolute asset specification ``tutorial:templates/mytemplate.pt``. + + 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 7-8*. + A :term:`view callable` named ``my_view`` is defined, which is decorated in the step above. + This view callable is a *function* generated by the cookiecutter. + It is given a single argument, ``request``. + This is the standard call signature for a Pyramid :term:`view callable`. + The function returns the dictionary ``{'project': 'myproj'}``. + This dictionary is used by the template named by the ``mytemplate.pt`` asset specification to fill in certain values on the page. + +Let us open ``tutorial/views/default.py`` in the ``views`` package to look at the second view. + +.. literalinclude:: src/basiclayout/tutorial/views/notfound.py + :linenos: + :language: python + +Without repeating ourselves, we will point out the differences between this view and the previous. + +#. *Line 4*. + The ``notfound_view`` function is decorated with ``@notfound_view_config``. + This decorator registers a :term:`Not Found View` using :meth:`pyramid.config.Configurator.add_notfound_view`. + + The ``renderer`` argument names an :term:`asset specification` of ``templates/404.pt``. + +#. *Lines 5-7*. + A :term:`view callable` named ``notfound_view`` is defined, which is decorated in the step above. + It sets the HTTP response status code to ``404``. + The function returns an empty dictionary to the template ``404.pt``, which accepts no parameters anyway. + Configuration in ``development.ini`` ------------------------------------ -The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as -opposed to the ``tutorial`` :term:`package` directory) looks like this: +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``. +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. +When the server is started via ``pserve``, the values within this section are passed as ``**settings`` to the ``main`` function defined in ``__init__.py``. diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index e973cfdfe..3a340e6f7 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -4,90 +4,111 @@ Defining the Domain Model ========================= -The first change we'll make to our stock cookiecutter-generated application will -be to define two :term:`resource` constructors, one representing a wiki page, -and another representing the wiki as a mapping of wiki page names to page -objects. We'll do this inside our ``models.py`` file. +Let's make changes to our stock cookiecutter-generated application. +We will define two :term:`resource` constructors, one representing a wiki page, and another representing the wiki as a mapping of wiki page names to page objects. +We will do this inside our ``models.py`` file. -Because we're using :term:`ZODB` to represent our -:term:`resource tree`, each of these resource constructors represents a -:term:`domain model` object, so we'll call these constructors "model -constructors". Both our Page and Wiki constructors will be class objects. A -single instance of the "Wiki" class will serve as a container for "Page" -objects, which will be instances of the "Page" class. +Because we are using :term:`ZODB` to represent our :term:`resource tree`, each of these resource constructors represents a :term:`domain model` object. +We will call these constructors "model constructors". +Both our ``Page`` and ``Wiki`` constructors will be class objects. +A single instance of the "Wiki" class will serve as a container for "Page" objects, which will be instances of the "Page" class. + +.. seealso:: + + We will introduce a lot of concepts throughout the remainder of this tutorial. + See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid. Delete the database ------------------- -In the next step, we're going to remove the ``MyModel`` Python model -class from our ``models.py`` file. Since this class is referred to within -our persistent storage (represented on disk as a file named ``Data.fs``), -we'll have strange things happen the next time we want to visit the -application in a browser. Remove the ``Data.fs`` from the ``tutorial`` -directory before proceeding any further. It's always fine to do this as long -as you don't care about the content of the database; the database itself will -be recreated as necessary. +In the next step, we will remove the ``MyModel`` Python model class from our ``models`` package. +Since this class is referred to within our persistent storage (represented on disk as a file named ``Data.fs``), we will have strange things happen the next time we want to visit the application in a browser. + +Remove the ``Data.fs`` from the ``tutorial`` directory before proceeding any further. +It is always fine to do this as long as you don't care about the content of the database. +The database itself will be recreated as necessary. -Edit ``models.py`` ------------------- + +Edit ``models`` package +----------------------- .. note:: - There is nothing special about the filename ``models.py``. A - project may have many models throughout its codebase in arbitrarily named - files. Files implementing models often have ``model`` in their filenames - or they may live in a Python subpackage of your application package named - ``models``, but this is only by convention. + There is nothing special about the package name ``models``. + A project may have many models throughout its codebase in arbitrarily named files and directories. + Files that implement models often have ``model`` in their names, or they may live in a Python subpackage of your application package named ``models``, but this is only by convention. -Open ``tutorial/models.py`` file and edit it to look like the following: +Open ``tutorial/models/__init__.py`` file and edit it to look like the following: -.. literalinclude:: src/models/tutorial/models.py +.. literalinclude:: src/models/tutorial/models/__init__.py :linenos: :language: python -The first thing we want to do is remove the ``MyModel`` class from the -generated ``models.py`` file. The ``MyModel`` class is only a sample and -we're not going to use it. - -Then we'll add an import at the top for the :class:`persistent.Persistent` class. We'll use this for a new ``Page`` class in a moment. - -Then we'll add a ``Wiki`` class. We want it to inherit from the -:class:`persistent.mapping.PersistentMapping` class because it provides -mapping behavior, and it makes sure that our Wiki page is stored as a -"first-class" persistent object in our ZODB database. - -Our ``Wiki`` class should have two attributes set to ``None`` at -class scope: ``__parent__`` and ``__name__``. If a model has a -``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid` -application, it means that it's the :term:`root` model. The ``__name__`` -of the root model is also always ``None``. - -Then we'll add a ``Page`` class. This class should inherit from the -:class:`persistent.Persistent` class. We'll also give it an ``__init__`` -method that accepts a single parameter named ``data``. This parameter will -contain the :term:`reStructuredText` body representing the wiki page content. -Note that ``Page`` objects don't have an initial ``__name__`` or -``__parent__`` attribute. All objects in a traversal graph must have a -``__name__`` and a ``__parent__`` attribute. We don't specify these here -because both ``__name__`` and ``__parent__`` will be set by a :term:`view` -function when a Page is added to our Wiki mapping. - -As a last step, we want to change the ``appmaker`` function in our -``models.py`` file so that the :term:`root` :term:`resource` of our -application is a Wiki instance. We'll also slot a single page object (the -front page) into the Wiki within the ``appmaker``. This will provide -:term:`traversal` a :term:`resource tree` to work against when it attempts to -resolve URLs to resources. +Remove the ``MyModel`` class from the generated ``models/__init__.py`` file. +The ``MyModel`` class is only a sample and we're not going to use it. + +Next we add an import at the top for the :class:`persistent.Persistent` class. +We will use this for a new ``Page`` class in a moment. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 1-2 + :lineno-match: + :emphasize-lines: 1 + :language: py + +Then we add a ``Wiki`` class. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 4-6 + :lineno-match: + :language: py + +We want it to inherit from the :class:`persistent.mapping.PersistentMapping` class because it provides mapping behavior. +It also makes sure that our ``Wiki`` page is stored as a "first-class" persistent object in our ZODB database. + +Our ``Wiki`` class should have two attributes set to ``None`` at class scope: ``__parent__`` and ``__name__``. +If a model has a ``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid` application, it means that it is the :term:`root` model. +The ``__name__`` of the root model is also always ``None``. + +Now we add a ``Page`` class. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 8-10 + :lineno-match: + :language: py + +This class should inherit from the :class:`persistent.Persistent` class. +We will give it an ``__init__`` method that accepts a single parameter named ``data``. +This parameter will contain the :term:`reStructuredText` body representing the wiki page content. + +Note that ``Page`` objects don't have an initial ``__name__`` or ``__parent__`` attribute. +All objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute. +We do not specify these here. +Instead both ``__name__`` and ``__parent__`` will be set by a :term:`view` function when a ``Page`` is added to our ``Wiki`` mapping. +We will create this function in the next chapter. + +As a last step, edit the ``appmaker`` function. + +.. literalinclude:: src/models/tutorial/models/__init__.py + :lines: 12-20 + :lineno-match: + :emphasize-lines: 4-8 + :language: py + +The :term:`root` :term:`resource` of our application is a Wiki instance. + +We will also slot a single page object (the front page) into the Wiki within the ``appmaker``. +This will provide :term:`traversal` a :term:`resource tree` to work against when it attempts to resolve URLs to resources. + View the application in a browser --------------------------------- -We can't. At this point, our system is in a "non-runnable" state; we'll need -to change view-related files in the next chapter to be able to start the -application successfully. If you try to start the application (See -:ref:`wiki-start-the-application`), you'll wind -up with a Python traceback on your console that ends with this exception: +We cannot. +At this point, our system is in a "non-runnable" state +We will need to change view-related files in the next chapter to be able to start the application successfully. +If you try to start the application (See :ref:`wiki-start-the-application`), you will wind up with a Python traceback on your console that ends with this exception: .. code-block:: text diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index d584a1b41..bd8dc6ecf 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -4,44 +4,37 @@ Defining Views ============== -A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid` -application is typically a simple Python function that accepts two -parameters: :term:`context` and :term:`request`. A view callable is -assumed to return a :term:`response` object. +A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid` application is typically a simple Python function that accepts two parameters: :term:`context` and :term:`request`. +A view callable is assumed to return a :term:`response` object. .. note:: - A :app:`Pyramid` view can also be defined as callable - which accepts *only* a :term:`request` argument. You'll see - this one-argument pattern used in other :app:`Pyramid` tutorials - and applications. Either calling convention will work in any - :app:`Pyramid` application; the calling conventions can be used - interchangeably as necessary. In :term:`traversal`-based applications, - URLs are mapped to a context :term:`resource`, and since our - :term:`resource tree` also represents our application's - "domain model", we're often interested in the context because - it represents the persistent storage of our application. For - this reason, in this tutorial we define views as callables that - accept ``context`` in the callable argument list. If you do - need the ``context`` within a view function that only takes - the request as a single argument, you can obtain it via - ``request.context``. - -We're going to define several :term:`view callable` functions, then wire them -into :app:`Pyramid` using some :term:`view configuration`. + A :app:`Pyramid` view can also be defined as callable which accepts *only* a :term:`request` argument. + You will see this one-argument pattern used in other :app:`Pyramid` tutorials and applications. + Either calling convention will work in any :app:`Pyramid` application. + The calling conventions can be used interchangeably as necessary. + + In :term:`traversal`-based applications, URLs are mapped to a context :term:`resource`. + Since our :term:`resource tree` also represents our application's "domain model", we are often interested in the context because it represents the persistent storage of our application. + For this reason, in this tutorial we define views as callables that accept ``context`` in the callable argument list. + If you do need the ``context`` within a view function that only takes the request as a single argument, you can obtain it via ``request.context``. + +We will define several :term:`view callable` functions, then wire them into :app:`Pyramid` using some :term:`view configuration`. + +.. seealso:: + + This chapter will introduce more concepts, as did the previous. + See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid. Declaring Dependencies in Our ``setup.py`` File =============================================== -The view code in our application will depend on a package which is not a -dependency of the original "tutorial" application. The original "tutorial" -application was generated by the cookiecutter; it doesn't know -about our custom application requirements. +The view code in our application will depend on a package which is not a dependency of the original "tutorial" application. +The original "tutorial" application was generated by the cookiecutter. +It does not know about our custom application requirements. -We need to add a dependency on the ``docutils`` package to our ``tutorial`` -package's ``setup.py`` file by assigning this dependency to the ``requires`` -parameter in the ``setup()`` function. +We need to add a dependency on the ``docutils`` package to our ``tutorial`` package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. Open ``setup.py`` and edit it to look like the following: @@ -52,17 +45,15 @@ Open ``setup.py`` and edit it to look like the following: Only the highlighted line needs to be added. + .. _wiki-running-pip-install: Running ``pip install -e .`` ============================ -Since a new software dependency was added, you will need to run ``pip install --e .`` again inside the root of the ``tutorial`` package to obtain and register -the newly added dependency distribution. +Since a new software dependency was added, you need to run ``pip install -e .`` again inside the root of the ``tutorial`` package to obtain and register the newly added dependency distribution. -Make sure your current working directory is the root of the project (the -directory in which ``setup.py`` lives) and execute the following command. +Make sure your current working directory is the root of the project (the directory in which ``setup.py`` lives) and execute the following command. On Unix: @@ -78,237 +69,238 @@ On Windows: cd tutorial %VENV%\Scripts\pip install -e . -Success executing this command will end with a line to the console something -like: +Success executing this command will end with a line to the console similar to the following: .. code-block:: text - Successfully installed docutils-0.13.1 tutorial + Successfully installed docutils-0.14 tutorial -Adding view functions in ``views.py`` -===================================== +Adding view functions in the ``views`` package +============================================== -It's time for a major change. Open ``tutorial/views.py`` and edit it to look -like the following: +It is time for a major change. +Open ``tutorial/views/default.py`` and edit it to look like the following: -.. literalinclude:: src/views/tutorial/views.py +.. literalinclude:: src/views/tutorial/views/default.py :linenos: :language: python We added some imports and created a regular expression to find "WikiWords". -We got rid of the ``my_view`` view function and its decorator that was added -when originally rendered after we selected the ``zodb`` backend option in the -cookiecutter. It was only an example and isn't relevant to our application. +We got rid of the ``my_view`` view function and its decorator that was added when originally rendered after we selected the ``zodb`` backend option in the cookiecutter. +It was only an example and is not relevant to our application. -Then we added four :term:`view callable` functions to our ``views.py`` -module: +Then we added four :term:`view callable` functions to our ``views.py`` module: * ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL. * ``view_page()`` - Displays an individual page. * ``add_page()`` - Allows the user to add a page. * ``edit_page()`` - Allows the user to edit a page. -We'll describe each one briefly in the following sections. +We will describe each one briefly in the following sections. .. note:: - There is nothing special about the filename ``views.py``. A project may - have many view callables throughout its codebase in arbitrarily named - files. Files implementing view callables often have ``view`` in their - filenames (or may live in a Python subpackage of your application package - named ``views``), but this is only by convention. + There is nothing special about the filename ``views.py``. + A project may have many view callables throughout its codebase in arbitrarily named files. + Files that implement view callables often have ``view`` in their names (or may live in a Python subpackage of your application package named ``views``), but this is only by convention. + The ``view_wiki`` view function ------------------------------- Following is the code for the ``view_wiki`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py - :lines: 12-14 +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 13-15 :lineno-match: :language: python -.. note:: In our code, we use an *import* that is *relative* to our package - named ``tutorial``, meaning we can omit the name of the package in the - ``import`` and ``context`` statements. In our narrative, however, we refer - to a *class* and thus we use the *absolute* form, meaning that the name of - the package is included. - -``view_wiki()`` is the :term:`default view` that gets called when a request is -made to the root URL of our wiki. It always redirects to an URL which -represents the path to our "FrontPage". - -We provide it with a ``@view_config`` decorator which names the class -``tutorial.models.Wiki`` as its context. This means that when a Wiki resource -is the context and no :term:`view name` exists in the request, then this view -will be used. The view configuration associated with ``view_wiki`` does not -use a ``renderer`` because the view callable always returns a :term:`response` -object rather than a dictionary. No renderer is necessary when a view returns -a response object. - -The ``view_wiki`` view callable always redirects to the URL of a Page resource -named "FrontPage". To do so, it returns an instance of the -:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement -the :class:`pyramid.interfaces.IResponse` interface, like -:class:`pyramid.response.Response` does). It uses the -:meth:`pyramid.request.Request.route_url` API to construct an URL to the -``FrontPage`` page resource (i.e., ``http://localhost:6543/FrontPage``), and -uses it as the "location" of the ``HTTPFound`` response, forming an HTTP -redirect. +.. note:: + + In our code, we use an *import* that is *relative* to our package named ``tutorial``. + This means we can omit the name of the package in the ``import`` and ``context`` statements. + In our narrative, however, we refer to a *class* and thus we use the *absolute* form. + This means that the name of the package is included. + +``view_wiki()`` is the :term:`default view` that gets called when a request is made to the root URL of our wiki. +It always redirects to an URL which represents the path to our ``FrontPage``. + +We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Wiki`` as its context. +This means that when a ``Wiki`` resource is the context and no :term:`view name` exists in the request, then this view will be used. +The view configuration associated with ``view_wiki`` does not use a ``renderer`` because the view callable always returns a :term:`response` object rather than a dictionary. +No renderer is necessary when a view returns a response object. + +The ``view_wiki`` view callable always redirects to the URL of a ``Page`` resource named ``FrontPage``. +To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class. +Instances of this class implement the :class:`pyramid.interfaces.IResponse` interface, similar to :class:`pyramid.response.Response`. +It uses the :meth:`pyramid.request.Request.route_url` API to construct an URL to the ``FrontPage`` page resource (in other words, ``http://localhost:6543/FrontPage``), and uses it as the ``location`` of the ``HTTPFound`` response, forming an HTTP redirect. + The ``view_page`` view function ------------------------------- Here is the code for the ``view_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py - :lines: 16-33 +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 18-35 :lineno-match: :language: python -The ``view_page`` function is configured to respond as the default view -of a Page resource. We provide it with a ``@view_config`` decorator which -names the class ``tutorial.models.Page`` as its context. This means that -when a Page resource is the context, and no :term:`view name` exists in the -request, this view will be used. We inform :app:`Pyramid` this view will use -the ``templates/view.pt`` template file as a ``renderer``. - -The ``view_page`` function generates the :term:`reStructuredText` body of a -page (stored as the ``data`` attribute of the context passed to the view; the -context will be a ``Page`` resource) as HTML. Then it substitutes an HTML -anchor for each *WikiWord* reference in the rendered HTML using a compiled -regular expression. - -The curried function named ``check`` is used as the first argument to -``wikiwords.sub``, indicating that it should be called to provide a value for -each WikiWord match found in the content. If the wiki (our page's -``__parent__``) already contains a page with the matched WikiWord name, the -``check`` function generates a view link to be used as the substitution value -and returns it. If the wiki does not already contain a page with the -matched WikiWord name, the function generates an "add" link as the -substitution value and returns it. - -As a result, the ``content`` variable is now a fully formed bit of HTML -containing various view and add links for WikiWords based on the content of -our current page resource. - -We then generate an edit URL because it's easier to do here than in the -template, and we wrap up a number of arguments in a dictionary and return -it. - -The arguments we wrap into a dictionary include ``page``, ``content``, and -``edit_url``. As a result, the *template* associated with this view callable -(via ``renderer=`` in its configuration) will be able to use these names to -perform various rendering tasks. The template associated with this view -callable will be a template which lives in ``templates/view.pt``. - -Note the contrast between this view callable and the ``view_wiki`` view -callable. In the ``view_wiki`` view callable, we unconditionally return a -:term:`response` object. In the ``view_page`` view callable, we return a -*dictionary*. It is *always* fine to return a :term:`response` object from a -:app:`Pyramid` view. Returning a dictionary is allowed only when there is a -:term:`renderer` associated with the view callable in the view configuration. +The ``view_page`` function is configured to respond as the default view of a ``Page`` resource. +We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Page`` as its context. +This means that when a ``Page`` resource is the context, and no :term:`view name` exists in the request, this view will be used. +We inform :app:`Pyramid` this view will use the ``templates/view.pt`` template file as a ``renderer``. + +The ``view_page`` function generates the :term:`reStructuredText` body of a page as HTML. +The body is stored as the ``data`` attribute of the context passed to the view. +The context will be a ``Page`` resource. +Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression. + +The curried function named ``check`` is used as the first argument to ``wikiwords.sub``, indicating that it should be called to provide a value for each ``WikiWord`` match found in the content. +If the wiki (our page's ``__parent__``) already contains a page with the matched ``WikiWord`` name, the ``check`` function generates a view link to be used as the substitution value and returns it. +If the wiki does not already contain a page with the matched ``WikiWord`` name, the function generates an "add" link as the substitution value and returns it. + +As a result, the ``page_text`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``\s based on the content of our current page resource. + +We then generate an edit URL because it is easier to do here than in the template. +Finally we wrap up a number of arguments in a dictionary and return it. + +The arguments we wrap into a dictionary include ``page``, ``page_text``, and ``edit_url``. +As a result, the *template* associated with this view callable (via ``renderer=`` in its configuration) will be able to use these names to perform various rendering tasks. +The template associated with this view callable will be a template which lives in ``templates/view.pt``. + +Note the contrast between this view callable and the ``view_wiki`` view callable. +In the ``view_wiki`` view callable, we unconditionally return a :term:`response` object. +In the ``view_page`` view callable, we return a *dictionary*. It is *always* fine to return a :term:`response` object from a :app:`Pyramid` view. +Returning a dictionary is allowed only when there is a :term:`renderer` associated with the view callable in the view configuration. + The ``add_page`` view function ------------------------------ Here is the code for the ``add_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py - :lines: 35-50 +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 38-53 :lineno-match: :language: python -The ``add_page`` function is configured to respond when the context resource -is a Wiki and the :term:`view name` is ``add_page``. We provide it with a -``@view_config`` decorator which names the string ``add_page`` as its -:term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its -context, and the renderer named ``templates/edit.pt``. This means that when a -Wiki resource is the context, and a :term:`view name` named ``add_page`` -exists as the result of traversal, this view will be used. We inform -:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a -``renderer``. We share the same template between add and edit views, thus -``edit.pt`` instead of ``add.pt``. - -The ``add_page`` function will be invoked when a user clicks on a WikiWord -which isn't yet represented as a page in the system. The ``check`` function -within the ``view_page`` view generates URLs to this view. It also acts as a -handler for the form that is generated when we want to add a page resource. -The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a -Page resource). - -The request :term:`subpath` in :app:`Pyramid` is the sequence of names that -are found *after* the :term:`view name` in the URL segments given in the -``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. If our -add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``, -the :term:`subpath` will be a tuple: ``('SomeName',)``. - -The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name), -and aliases it to the name attribute in order to know the name of the page -we're trying to add. - -If the view rendering is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the view -renders a template. To do so, it generates a "save url" which the template -uses as the form post URL during rendering. We're lazy here, so we're trying -to use the same template (``templates/edit.pt``) for the add view as well as -the page edit view. To do so, we create a dummy Page resource object in -order to satisfy the edit form's desire to have *some* page object exposed as -``page``, and we'll render the template to a response. - -If the view rendering *is* a result of a form submission (if the expression -``'form.submitted' in request.params`` is ``True``), we grab the page body -from the form data, create a Page object using the name in the subpath and -the page body, and save it into "our context" (the Wiki) using the -``__setitem__`` method of the context. We then redirect back to the -``view_page`` view (the default view for a page) for the newly created page. +The ``add_page`` function is configured to respond when the context resource is a ``Wiki`` and the :term:`view name` is ``add_page``. +We provide it with a ``@view_config`` decorator which names the string ``add_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its context, and the renderer named ``templates/edit.pt``. +This means that when a ``Wiki`` resource is the context, and a :term:`view name` named ``add_page`` exists as the result of traversal, then this view will be used. +We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. +We share the same template between add and edit views, thus ``edit.pt`` instead of ``add.pt``. + +The ``add_page`` function will be invoked when a user clicks on a ``WikiWord`` that is not yet represented as a page in the system. +The ``check`` function within the ``view_page`` view generates URLs to this view. +It also acts as a handler for the form that is generated when we want to add a page resource. +The ``context`` of the ``add_page`` view is always a ``Wiki`` resource (*not* a ``Page`` resource). + +The request :term:`subpath` in :app:`Pyramid` is the sequence of names that are found *after* the :term:`view name` in the URL segments given in the ``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. +If our add view is invoked via, for example, ``http://localhost:6543/add_page/SomeName``, then the :term:`subpath` will be a tuple ``('SomeName',)``. + +The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name), then aliases it to the name attribute to know the name of the page we are trying to add. + +If the view rendering is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), then the view renders a template. +To do so, it generates a ``save_url`` which the template uses as the form post URL during rendering. +We are lazy here, so we try to use the same template (``templates/edit.pt``) for both the add and edit views. +To do so, we create a dummy ``Page`` resource object to satisfy the edit form's desire to have *some* page object exposed as ``page``. +We then set the ``Page`` object's ``__name__`` and ``__parent__``. +Then we will render the template to a response. + +If the view rendering *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), then do the following: + +- Grab the page body from the form data as ``body``. +- Create a ``Page`` object using the name in the subpath and the page body as ``page``. +- Set the ``Page`` object's ``__name__`` and ``__parent__``. +- Save it into "our context" (the ``Wiki``) using the ``__setitem__`` method of the context. +- We then redirect back to the ``view_page`` view (the default view for a page) for the newly created page. + +.. seealso:: + + In the :ref:`previous chapter <wiki_defining_the_domain_model>`, we mentioned that all objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute. + That provides location awareness for resources. + See also the section on :ref:`location_aware` in the :ref:`resources_chapter` chapter for a complete discussion. + The ``edit_page`` view function ------------------------------- Here is the code for the ``edit_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py - :lines: 52-60 +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 56-64 :lineno-match: :language: python -The ``edit_page`` function is configured to respond when the context is -a Page resource and the :term:`view name` is ``edit_page``. We provide it -with a ``@view_config`` decorator which names the string ``edit_page`` as its -:term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its -context, and the renderer named ``templates/edit.pt``. This means that when -a Page resource is the context, and a :term:`view name` exists as the result -of traversal named ``edit_page``, this view will be used. We inform -:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as -a ``renderer``. - -The ``edit_page`` function will be invoked when a user clicks the "Edit this -Page" button on the view form. It renders an edit form but it also acts as -the form post view callable for the form it renders. The ``context`` of the -``edit_page`` view will *always* be a Page resource (never a Wiki resource). - -If the view execution is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the view -simply renders the edit form, passing the page resource, and a ``save_url`` -which will be used as the action of the generated form. - -If the view execution *is* a result of a form submission (if the expression -``'form.submitted' in request.params`` is ``True``), the view grabs the -``body`` element of the request parameter and sets it as the ``data`` -attribute of the page context. It then redirects to the default view of the -context (the page), which will always be the ``view_page`` view. +The ``edit_page`` function is configured to respond when the context is a ``Page`` resource and the :term:`view name` is ``edit_page``. +We provide it with a ``@view_config`` decorator which names the string ``edit_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its context, and the renderer named ``templates/edit.pt``. +This means that when a ``Page`` resource is the context, and a :term:`view name` exists as the result of traversal named ``edit_page``, this view will be used. +We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. + +The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form. +It renders an edit form. +It also acts as the form post view callable for the form it renders. +The ``context`` of the ``edit_page`` view will *always* be a ``Page`` resource (never a ``Wiki`` resource). + +If the view execution is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), then the view renders the edit form, passing the page resource, and a ``save_url`` which will be used as the action of the generated form. + +If the view execution *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameter and sets it as the ``data`` attribute of the page context. +It then redirects to the default view of the context (the page), which will always be the ``view_page`` view. + + +Modifying the ``notfound_view`` in ``notfound.py`` +-------------------------------------------------- + +We have one more view to modify. +Open ``tutorial/views/notfound.py`` and make the changes shown by the emphasized lines. + +.. literalinclude:: src/views/tutorial/views/notfound.py + :linenos: + :language: python + :emphasize-lines: 3-4, 9-12 + +We need to import the ``Page`` from our models. +We eventually return a ``Page`` object as ``page`` into the template ``layout.pt`` to display its name in the title tag. + Adding templates ================ -The ``view_page``, ``add_page`` and ``edit_page`` views that we've added -reference a :term:`template`. Each template is a :term:`Chameleon` -:term:`ZPT` template. These templates will live in the ``templates`` -directory of our tutorial package. Chameleon templates must have a ``.pt`` -extension to be recognized as such. +The ``view_page``, ``add_page``, and ``edit_page`` views that we added reference a :term:`template`. +Each template is a :term:`Chameleon` :term:`ZPT` template. +These templates will live in the ``templates`` directory of our tutorial package. +Chameleon templates must have a ``.pt`` extension to be recognized as such. + + +The ``layout.pt`` template +-------------------------- + +Update ``tutorial/templates/layout.pt`` with the following content, as indicated by the emphasized lines: + +.. literalinclude:: src/views/tutorial/templates/layout.pt + :linenos: + :emphasize-lines: 11-12, 37-41 + :language: html + +Since we are using a templating engine, we can factor common boilerplate out of our page templates into reusable components. +We can do this via :term:`METAL` macros and slots. + +- The cookiecutter defined a macro named ``layout`` (line 1). + This macro consists of the entire template. +- We changed the ``title`` tag to use the ``name`` attribute of a ``page`` object, or if it does not exist then the page title (lines 11-12). +- The cookiecutter defined a macro customization point or `slot` (line 36). + This slot is inside the macro ``layout``. + Therefore it can be replaced by content, customizing the macro. +- We added a ``div`` element with a link to allow the user to return to the front page (lines 37-41). +- We removed the row of icons and links from the original cookiecutter. + +.. seealso:: + + Please refer to the Chameleon documentation for more information about using `METAL <https://chameleon.readthedocs.io/en/latest/>`_ for defining and using macros and slots. + The ``view.pt`` template ------------------------ @@ -318,16 +310,18 @@ Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` an .. literalinclude:: src/views/tutorial/templates/view.pt :linenos: :language: html - :emphasize-lines: 11-12,37-52 + :emphasize-lines: 5-16 This template is used by ``view_page()`` for displaying a single wiki page. It includes: -- A ``div`` element that is replaced with the ``content`` value provided by - the view (lines 37-39). ``content`` contains HTML, so the ``structure`` - keyword is used to prevent escaping it (i.e., changing ">" to ">", etc.) -- A link that points at the "edit" URL which invokes the ``edit_page`` view - for the page being viewed (lines 41-43). +- The use of a macro to load the entire template ``layout.pt``. +- The template fills the slot named ``content`` (line 2) with a ``div`` element. +- A ``div`` element that is replaced with the ``page_text`` value provided by the view (line 5). + ``page_text`` contains HTML, so the ``structure`` keyword is used to prevent escaping HTML entities, such as changing ``>`` to ``>``. +- A link that points at the "edit" URL, which invokes the ``edit_page`` view for the page being viewed (lines 9-11). +- A ``span`` whose content is replaced by the name of the page, if present. + The ``edit.pt`` template ------------------------ @@ -337,59 +331,52 @@ Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit t .. literalinclude:: src/views/tutorial/templates/edit.pt :linenos: :language: html + :emphasize-lines: 5-20 -This template is used by ``add_page()`` and ``edit_page()`` for adding and -editing a wiki page. It displays a page containing a form that includes: +This template is used by ``add_page()`` and ``edit_page()`` for adding and editing a wiki page. +It displays a page containing a form that includes: -- A 10-row by 60-column ``textarea`` field named ``body`` that is filled - with any existing page data when it is rendered (line 46). -- A submit button that has the name ``form.submitted`` (line 49). +- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (lines 11-13). +- A submit button that has the name ``form.submitted`` (lines 16-18). -The form POSTs back to the ``save_url`` argument supplied by the view (line -44). The view will use the ``body`` and ``form.submitted`` values. +When submitted, the form sends a POST request to the ``save_url`` argument supplied by the view (line 9). +The view will use the ``body`` and ``form.submitted`` values. -.. note:: Our templates use a ``request`` object that none of our tutorial - views return in their dictionary. ``request`` is one of several names that - are available "by default" in a template when a template renderer is used. - See :ref:`renderer_system_values` for information about other names that - are available by default when a template is used as a renderer. +.. note:: + + Our templates use a ``request`` object that none of our tutorial views return in their dictionary. + ``request`` is one of several names that are available "by default" in a template when a template renderer is used. + See :ref:`renderer_system_values` for information about other names that are available by default when a template is used as a renderer. Static assets ------------- -Our templates name static assets, including CSS and images. We don't need -to create these files within our package's ``static`` directory because they -were provided at the time we created the project. +Our templates name static assets, including CSS and images. +We don't need to create these files within our package's ``static`` directory because they were provided by the cookiecutter at the time we created the project. -As an example, the CSS file will be accessed via -``http://localhost:6543/static/theme.css`` by virtue of the call to the -``add_static_view`` directive we've made in the ``__init__.py`` file. Any -number and type of static assets can be placed in this directory (or -subdirectories) and are just referred to by URL or by using the convenience -method ``static_url``, e.g., -``request.static_url('<package>:static/foo.css')`` within templates. +As an example, the CSS file will be accessed via ``http://localhost:6543/static/theme.css`` by virtue of the call to the ``add_static_view`` directive in the ``routes.py`` file. +Any number and type of static assets can be placed in this directory (or subdirectories) +They are referred to by either URL or using the convenience method ``static_url``, for example ``request.static_url('<package>:static/foo.css')``, within templates. Viewing the application in a browser ==================================== -We can finally examine our application in a browser (See -:ref:`wiki-start-the-application`). Launch a browser and visit -each of the following URLs, checking that the result is as expected: +We can finally examine our application in a browser (See :ref:`wiki-start-the-application`). +Launch a browser and visit each of the following URLs, checking that the result is as expected: + +- http://localhost:6543/ invokes the ``view_wiki`` view. + This always redirects to the ``view_page`` view of the ``FrontPage`` ``Page`` resource. -- http://localhost:6543/ invokes the ``view_wiki`` view. This always - redirects to the ``view_page`` view of the ``FrontPage`` Page resource. +- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front page resource. + This is because it is the :term:`default view` (a view without a ``name``) for ``Page`` resources. -- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front - page resource. This is because it's the :term:`default view` (a view - without a ``name``) for Page resources. +- http://localhost:6543/FrontPage/edit_page invokes the edit view for the ``FrontPage`` ``Page`` resource. -- http://localhost:6543/FrontPage/edit_page invokes the edit view for the - ``FrontPage`` Page resource. +- http://localhost:6543/add_page/SomePageName invokes the add view for a ``Page``. -- http://localhost:6543/add_page/SomePageName invokes the add view for a Page. +- To generate an error, visit http://localhost:6543/add_page which will generate an ``IndexError: tuple index out of range`` error. + You will see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. -- To generate an error, visit http://localhost:6543/add_page which will - generate an ``IndexError: tuple index out of range`` error. You'll see an - interactive traceback facility provided by :term:`pyramid_debugtoolbar`. +- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter. diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst index 30d443bb8..5c86293f6 100644 --- a/docs/tutorials/wiki/design.rst +++ b/docs/tutorials/wiki/design.rst @@ -4,85 +4,77 @@ Design ====== -Following is a quick overview of the design of our wiki application, to help -us understand the changes that we will be making as we work through the -tutorial. +Following is a quick overview of the design of our wiki application to help us understand the changes that we will make as we work through the tutorial. + Overall ------- -We choose to use :term:`reStructuredText` markup in the wiki text. Translation -from reStructuredText to HTML is provided by the widely used ``docutils`` -Python module. We will add this module in the dependency list on the project -``setup.py`` file. +We choose to use :term:`reStructuredText` markup in the wiki text. +Conversion from reStructuredText to HTML is provided by the widely used ``docutils`` Python module. +We will add this module in the dependency list on the project ``setup.py`` file. + Models ------ -The root resource named ``Wiki`` will be a mapping of wiki page -names to page resources. The page resources will be instances -of a *Page* class and they store the text content. +The root resource named ``Wiki`` will be a mapping of wiki page names to page resources. +The page resources will be instances of a *Page* class. +They store the text content. + +URLs like ``/PageName`` will be traversed using Wiki[ *PageName* ] => page. +The resulting context is the page resource of an existing page. -URLs like ``/PageName`` will be traversed using Wiki[ -*PageName* ] => page, and the context that results is the page -resource of an existing page. +To add a page to the wiki, a new instance of the page resource is created. +Its name and reference are added to the Wiki mapping. -To add a page to the wiki, a new instance of the page resource -is created and its name and reference are added to the Wiki -mapping. +A page named ``FrontPage`` containing the text *This is the front page* will be created when the storage is initialized. +It will be used as the wiki home page. -A page named ``FrontPage`` containing the text *This is the front page*, will -be created when the storage is initialized, and will be used as the wiki home -page. Views ----- -There will be three views to handle the normal operations of adding, -editing, and viewing wiki pages, plus one view for the wiki front page. -Two templates will be used, one for viewing, and one for both adding -and editing wiki pages. +There will be three views to handle the normal operations of adding, editing, and viewing wiki pages, plus one view for the wiki front page. +Two templates will be used, one for viewing, and one for both adding and editing wiki pages. -As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In this tutorial, we will use :term:`Chameleon`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. +As of version 1.5 :app:`Pyramid` no longer ships with templating systems. +In this tutorial we will use :term:`Chameleon`. +Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. Security -------- -We'll eventually be adding security to our application. The components we'll -use to do this are below. +We'll eventually add security to our application. +The components we'll use to do this are below. -- USERS, a dictionary mapping :term:`userids <userid>` to their - corresponding passwords. +- USERS, a dictionary mapping :term:`userids <userid>` to their corresponding passwords. -- GROUPS, a dictionary mapping :term:`userids <userid>` to a - list of groups to which they belong. +- GROUPS, a dictionary mapping :term:`userids <userid>` to a list of groups to which they belong. -- ``groupfinder``, an *authorization callback* that looks up USERS and - GROUPS. It will be provided in a new ``security.py`` file. +- ``groupfinder``, an *authorization callback* that looks up USERS and GROUPS. + It will be provided in a new ``security.py`` file. -- An :term:`ACL` is attached to the root :term:`resource`. Each row below - details an :term:`ACE`: +- An :term:`ACL` is attached to the root :term:`resource`. + Each row below details an :term:`ACE`: - +----------+----------------+----------------+ - | Action | Principal | Permission | - +==========+================+================+ - | Allow | Everyone | View | - +----------+----------------+----------------+ - | Allow | group:editors | Edit | - +----------+----------------+----------------+ + +----------+----------------+----------------+ + | Action | Principal | Permission | + +==========+================+================+ + | Allow | Everyone | View | + +----------+----------------+----------------+ + | Allow | group:editors | Edit | + +----------+----------------+----------------+ -- Permission declarations are added to the views to assert the security - policies as each request is handled. +- Permission declarations are added to the views to assert the security policies as each request is handled. -Two additional views and one template will handle the login and -logout tasks. +Two additional views and one template will handle the login and logout tasks. Summary ------- -The URL, context, actions, template and permission associated to each view are -listed in the following table: +The URL, context, actions, template and permission associated to each view are listed in the following table: +----------------------+-------------+-----------------+-----------------------+------------+------------+ | URL | View | Context | Action | Template | Permission | @@ -139,10 +131,6 @@ listed in the following table: | | | | /FrontPage | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ -.. [1] This is the default view for a Page context - when there is no view name. -.. [2] Pyramid will return a default 404 Not Found page - if the page *PageName* does not exist yet. -.. [3] ``pyramid.exceptions.Forbidden`` is reached when a - user tries to invoke a view that is - not authorized by the authorization policy. +.. [1] This is the default view for a Page context when there is no view name. +.. [2] Pyramid will return a default 404 Not Found page if the page *PageName* does not exist yet. +.. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke a view that is not authorized by the authorization policy. diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst index 36d00adb4..c23f79b5a 100644 --- a/docs/tutorials/wiki/distributing.rst +++ b/docs/tutorials/wiki/distributing.rst @@ -4,10 +4,8 @@ Distributing Your Application ============================= -Once your application works properly, you can create a "tarball" from it by -using the ``setup.py sdist`` command. The following commands assume your -current working directory contains the ``tutorial`` package and the -``setup.py`` file. +Once your application works properly, you can create a :term:`distribution` from it by using the ``setup.py sdist`` command. +The following commands assume your current working directory contains the ``tutorial`` package and the ``setup.py`` file. On Unix: @@ -31,10 +29,8 @@ The output of such a command will be something like: Creating tar archive removing 'tutorial-0.0' (and everything under it) -Note that this command creates a tarball in the "dist" subdirectory named -``tutorial-0.0.tar.gz``. You can send this file to your friends to show them -your cool new application. They should be able to install it by pointing the -``pip install`` command directly at it. Or you can upload it to `PyPI -<https://pypi.org/>`_ and share it with the rest of the world, where -it can be downloaded via ``pip install`` remotely like any other package people -download from PyPI. +This command creates a subdirectory named ``dist``. +Inside that is a tarball named ``tutorial-0.0.tar.gz``, which is the :term:`distribution` of your application. +You can send this file to your friends to show them your cool new application. +They should be able to install it by pointing the ``pip install`` command directly at it. +Or you can upload it to `PyPI <https://pypi.org/>`_ and share it with the rest of the world, where it can be downloaded via ``pip install`` remotely like any other package people download from PyPI. diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index d0037e584..37e3498b2 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -4,12 +4,13 @@ Installation ============ + Before you begin ---------------- -This tutorial assumes that you have already followed the steps in -:ref:`installing_chapter`, except **do not create a virtual environment or -install Pyramid**. Thereby you will satisfy the following requirements. +This tutorial assumes that you have already followed the steps in :ref:`installing_chapter`, except **do not create a virtual environment or +install Pyramid**. +Thereby you will satisfy the following requirements. * A Python interpreter is installed on your operating system. * You've satisfied the :ref:`requirements-for-installing-packages`. @@ -17,13 +18,17 @@ install Pyramid**. Thereby you will satisfy the following requirements. Install cookiecutter -------------------- -We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions. +We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. +See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions. Generate a Pyramid project from a cookiecutter ---------------------------------------------- -We will create a Pyramid project in your home directory for Unix or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows. +We will create a Pyramid project in your home directory for Unix or at the root for Windows. +It is assumed you know the path to where you installed ``cookiecutter``. +Issue the following commands and override the defaults in the prompts as follows. + On Unix ^^^^^^^ @@ -33,6 +38,7 @@ On Unix cd ~ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + On Windows ^^^^^^^^^^ @@ -41,30 +47,33 @@ On Windows cd \ cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master + On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ If prompted for the first item, accept the default ``yes`` by hitting return. .. code-block:: text - You've cloned ~/.cookiecutters/pyramid-cookiecutter-theone before. - Is it okay to delete and re-clone it? [yes]: yes + You've downloaded ~/.cookiecutters/pyramid-cookiecutter-starter before. + Is it okay to delete and re-download it? [yes]: yes project_name [Pyramid Scaffold]: myproj repo_name [myproj]: tutorial Select template_language: 1 - jinja2 2 - chameleon 3 - mako - Choose from 1, 2, 3 [1]: 1 + Choose from 1, 2, 3 [1]: 2 Select backend: 1 - none 2 - sqlalchemy 3 - zodb Choose from 1, 2, 3 [1]: 3 + Change directory into your newly created project ------------------------------------------------ + On Unix ^^^^^^^ @@ -72,6 +81,7 @@ On Unix cd tutorial + On Windows ^^^^^^^^^^ @@ -85,6 +95,7 @@ Set and use a ``VENV`` environment variable We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward. + On Unix ^^^^^^^ @@ -92,6 +103,7 @@ On Unix export VENV=~/tutorial + On Windows ^^^^^^^^^^ @@ -103,6 +115,7 @@ On Windows Create a virtual environment ---------------------------- + On Unix ^^^^^^^ @@ -110,17 +123,10 @@ On Unix python3 -m venv $VENV + On Windows ^^^^^^^^^^ -Each version of Python uses different paths, so you might need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher. - -Python 2.7: - -.. code-block:: doscon - - c:\Python27\Scripts\virtualenv %VENV% - Python 3.7: .. code-block:: doscon @@ -131,6 +137,7 @@ Python 3.7: Upgrade packaging tools in the virtual environment -------------------------------------------------- + On Unix ^^^^^^^ @@ -138,6 +145,7 @@ On Unix $VENV/bin/pip install --upgrade pip setuptools + On Windows ^^^^^^^^^^ @@ -151,7 +159,10 @@ On Windows Installing the project in development mode ------------------------------------------ -In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command. +In order to work on the project, you must "register" the project as a development egg in your workspace. +We will install testing requirements at the same time. +We do so with the following command. + On Unix ^^^^^^^ @@ -160,6 +171,7 @@ On Unix $VENV/bin/pip install -e ".[testing]" + On Windows ^^^^^^^^^^ @@ -167,6 +179,7 @@ On Windows %VENV%\Scripts\pip install -e ".[testing]" + On all operating systems ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,16 +187,18 @@ The console will show ``pip`` checking for packages and installing missing packa .. code-block:: bash - Successfully installed BTrees-4.3.1 Chameleon-3.0 Mako-1.0.6 \ - MarkupSafe-0.23 PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.6.3 \ - WebTest-2.0.23 ZConfig-3.1.0 ZEO-5.0.4 ZODB-5.1.1 ZODB3-3.11.0 \ - beautifulsoup4-4.5.1 coverage-4.2 mock-2.0.0 pbr-1.10.0 persistent-4.2.2 \ - py-1.4.31 pyramid-1.7.3 pyramid-chameleon-0.3 pyramid-debugtoolbar-3.0.5 \ - pyramid-mako-1.0.2 pyramid-tm-1.1.1 pyramid-zodbconn-0.7 pytest-3.0.5 \ - pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 transaction-2.0.3 \ - translationstring-1.3 tutorial venusian-1.0 waitress-1.0.1 \ - zc.lockfile-1.2.1 zdaemon-4.2.0 zodbpickle-0.6.0 zodburi-2.0 \ - zope.deprecation-4.2.0 zope.interface-4.3.3 + Successfully installed BTrees-4.5.1 Chameleon-3.5 Mako-1.0.7 \ + MarkupSafe-1.1.0 PasteDeploy-1.5.2 Pygments-2.2.0 WebTest-2.0.32 \ + ZConfig-3.3.0 ZEO-5.2.0 ZODB-5.5.1 ZODB3-3.11.0 atomicwrites-1.2.1 \ + attrs-18.2.0 beautifulsoup4-4.6.3 coverage-4.5.2 hupper-1.4.1 \ + more-itertools-4.3.0 persistent-4.4.3 plaster-1.0 plaster-pastedeploy-0.6 \ + pluggy-0.8.0 py-1.7.0 pyramid-1.10.1 pyramid-chameleon-0.3 \ + pyramid-debugtoolbar-4.5 pyramid-mako-1.0.2 pyramid-retry-1.0 \ + pyramid-tm-2.2.1 pyramid-zodbconn-0.8.1 pytest-4.0.0 pytest-cov-2.6.0 \ + repoze.lru-0.7 six-1.11.0 transaction-2.4.0 translationstring-1.3 \ + tutorial venusian-1.1.0 waitress-1.1.0 webob-1.8.4 zc.lockfile-1.4 \ + zdaemon-4.3 zodbpickle-1.0.2 zodburi-2.3.0 zope.deprecation-4.3.0 \ + zope.interface-4.6.0 Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas. @@ -208,6 +223,7 @@ requirements, you may run the tests for the project. The following commands provide options to ``pytest`` that specify the module for which its tests shall be run, and to run ``pytest`` in quiet mode. + On Unix ^^^^^^^ @@ -215,6 +231,7 @@ On Unix $VENV/bin/pytest -q + On Windows ^^^^^^^^^^ @@ -233,13 +250,11 @@ For a successful test run, you should see output that ends like this: Expose test coverage information -------------------------------- -You can run the ``pytest`` command to see test coverage information. This -runs the tests in the same way that ``pytest`` does, but provides additional -:term:`coverage` information, exposing which lines of your project are covered by the -tests. +You can run the ``pytest`` command to see test coverage information. +This runs the tests in the same way that ``pytest`` does, but provides additional :term:`coverage` information, exposing which lines of your project are covered by the tests. + +We've already installed the ``pytest-cov`` package into our virtual environment, so we can run the tests with coverage. -We've already installed the ``pytest-cov`` package into our virtual -environment, so we can run the tests with coverage. On Unix ^^^^^^^ @@ -248,6 +263,7 @@ On Unix $VENV/bin/pytest --cov --cov-report=term-missing + On Windows ^^^^^^^^^^ @@ -259,23 +275,30 @@ If successful, you will see output something like this: .. code-block:: bash - ======================== test session starts ======================== - platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0 - rootdir: /Users/stevepiercy/tutorial, inifile: - plugins: cov-2.4.0 - collected 1 items + ======================== test session starts ========================= + platform darwin -- Python 3.7.0, pytest-4.0.0, py-1.7.0, pluggy-0.8.0 + rootdir: /Users/stevepiercy/projects/hack-on-pyramid/tutorial, inifile: pytest.ini + plugins: cov-2.6.0 + collected 1 item tutorial/tests.py . - ------------------ coverage: platform Python 3.6.0 ------------------ - Name Stmts Miss Cover Missing - ------------------------------------------------------- - tutorial/__init__.py 14 9 36% 7-8, 14-20 - tutorial/models.py 10 6 40% 9-14 - tutorial/views.py 4 0 100% - ------------------------------------------------------- - TOTAL 28 15 46% + [100%] + + ---------- coverage: platform darwin, python 3.7.0-final-0 ----------- + Name Stmts Miss Cover Missing + ----------------------------------------------------------- + tutorial/__init__.py 17 12 29% 7-8, 14-23 + tutorial/models/__init__.py 8 4 50% 9-12 + tutorial/pshell.py 6 6 0% 1-12 + tutorial/routes.py 2 2 0% 1-2 + tutorial/views/__init__.py 0 0 100% + tutorial/views/default.py 4 0 100% + tutorial/views/notfound.py 4 4 0% 1-7 + ----------------------------------------------------------- + TOTAL 41 28 32% + - ===================== 1 passed in 0.31 seconds ====================== + ===================== 1 passed in 0.31 seconds ======================= Our package doesn't quite have 100% test coverage. @@ -285,11 +308,10 @@ Our package doesn't quite have 100% test coverage. Test and coverage cookiecutter defaults --------------------------------------- -The Pyramid cookiecutter includes configuration defaults for ``pytest`` and -test coverage. These configuration files are ``pytest.ini`` and -``.coveragerc``, located at the root of your package. Without these defaults, -we would need to specify the path to the module on which we want to run tests -and coverage. +The Pyramid cookiecutter includes configuration defaults for ``pytest`` and test coverage. +These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package. +Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage. + On Unix ^^^^^^^ @@ -305,13 +327,11 @@ On Windows %VENV%\Scripts\pytest --cov=tutorial tutorial\tests.py -q -``pytest`` follows :ref:`conventions for Python test discovery -<pytest:test discovery>`, and the configuration defaults from the cookiecutter -tell ``pytest`` where to find the module on which we want to run tests and -coverage. -.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke - ``pytest -h`` to see its full set of options. +``pytest`` follows :ref:`conventions for Python test discovery <pytest:test discovery>`. +The configuration defaults from the cookiecutter tell ``pytest`` where to find the module on which we want to run tests and coverage. + +.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke ``pytest -h`` to see its full set of options. .. _wiki-start-the-application: @@ -319,8 +339,9 @@ coverage. Start the application --------------------- -Start the application. See :ref:`what_is_this_pserve_thing` for more -information on ``pserve``. +Start the application. +See :ref:`what_is_this_pserve_thing` for more information on ``pserve``. + On Unix ^^^^^^^ @@ -329,6 +350,7 @@ On Unix $VENV/bin/pserve development.ini --reload + On Windows ^^^^^^^^^^ @@ -336,10 +358,10 @@ On Windows %VENV%\Scripts\pserve development.ini --reload + .. note:: - Your OS firewall, if any, may pop up a dialog asking for authorization - to allow python to accept incoming network connections. + Your OS firewall, if any, may pop up a dialog asking for authorization to allow python to accept incoming network connections. If successful, you will see something like this on your console: @@ -356,13 +378,12 @@ This means the server is ready to accept requests. Visit the application in a browser ---------------------------------- -In a browser, visit http://localhost:6543/. You will see the generated -application's default page. +In a browser, visit http://localhost:6543/. +You will see the generated application's default page. -One thing you'll notice is the "debug toolbar" icon on right hand side of the -page. You can read more about the purpose of the icon at -:ref:`debug_toolbar`. It allows you to get information about your -application while you develop. +One thing you'll notice is the "debug toolbar" icon on right hand side of the page. +You can read more about the purpose of the icon at :ref:`debug_toolbar`. +It allows you to get information about your application while you develop. Decisions the cookiecutter backend option ``zodb`` has made for you @@ -371,27 +392,11 @@ Decisions the cookiecutter backend option ``zodb`` has made for you When creating a project and selecting the backend option of ``zodb``, the cookiecutter makes the following assumptions: - You are willing to use :term:`ZODB` for persistent storage. - - You are willing to use :term:`traversal` to map URLs to code. - -- You want to use pyramid_zodbconn_, pyramid_tm_, and the transaction_ packages - to manage connections and transactions with :term:`ZODB`. +- You want to use `pyramid_zodbconn <https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/>`_, `pyramid_tm <https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/>`_, and the `transaction <https://zodb.readthedocs.io/en/latest/transactions.html>`_ packages to manage connections and transactions with :term:`ZODB`. .. note:: - :app:`Pyramid` supports any persistent storage mechanism (e.g., an SQL - database or filesystem files). It also supports an additional mechanism to - map URLs to code (:term:`URL dispatch`). However, for the purposes of this - tutorial, we'll only be using :term:`traversal` and :term:`ZODB`. - -.. _pyramid_chameleon: - https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/ - -.. _pyramid_tm: - https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/ - -.. _pyramid_zodbconn: - https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/ - -.. _transaction: - https://zodb.readthedocs.io/en/latest/transactions.html + :app:`Pyramid` supports any persistent storage mechanism (e.g., an SQL database or filesystem files). + It also supports an additional mechanism to map URLs to code (:term:`URL dispatch`). + However, for the purposes of this tutorial, we will only use :term:`traversal` and :term:`ZODB`. diff --git a/docs/tutorials/wiki/src/authorization/MANIFEST.in b/docs/tutorials/wiki/src/authorization/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/authorization/MANIFEST.in +++ b/docs/tutorials/wiki/src/authorization/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/authorization/pytest.ini b/docs/tutorials/wiki/src/authorization/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/authorization/pytest.ini +++ b/docs/tutorials/wiki/src/authorization/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index 7b405745e..fa5948acb 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', 'docutils', 'bcrypt', ] diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 58635ea74..935a5d6d2 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -15,18 +15,17 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() with Configurator(settings=settings) as config: config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py index ebd70e912..ebd70e912 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py diff --git a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/authorization/tutorial/routes.py b/docs/tutorials/wiki/src/authorization/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> + + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> + </div> + + </div> +</div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt index eedb83da4..6438b1569 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt @@ -1,72 +1,27 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <p tal:condition="logged_in" class="pull-right"> + <div class="content"> + <p tal:condition="logged_in" class="pull-right"> <a href="${request.application_url}/logout">Logout</a> - </p> - <p> - Editing <strong><span tal:replace="page.__name__"> - Page Name Goes Here</span></strong> - </p> - <p>You can return to the - <a href="${request.application_url}">FrontPage</a>. - </p> - <form action="${save_url}" method="post"> + </p> + <p> + Editing <strong><span tal:replace="page.__name__"> + Page Name Goes Here</span></strong> + </p> + <form action="${save_url}" method="post"> <div class="form-group"> - <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea> + <textarea tal:content="page.data" + class="form-control" name="body" + rows="10" cols="60"></textarea> </div> <div class="form-group"> - <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button> + <button type="submit" + name="form.submitted" value="Save" + class="btn btn-default">Save</button> </div> - </form> - </div> - </div> + </form> </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt new file mode 100644 index 000000000..b606e8dad --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt @@ -0,0 +1,59 @@ +<!DOCTYPE html metal:define-macro="layout"> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + + <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + + <!-- Bootstrap core CSS --> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + + <!-- Custom styles for this scaffold --> + <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div metal:define-slot="content">No content</div> + <div class="content"> + <p>You can return to the + <a href="${request.application_url}">FrontPage</a>. + </p> + </div> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> + <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + </body> +</html> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt index 626db6637..acc4876cf 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt @@ -1,39 +1,7 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>Login - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> + <div class="content"> <p> <strong> Login @@ -54,22 +22,7 @@ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button> </div> </form> - </div> - </div> </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt index f2a9249ef..911ab0c99 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt @@ -1,72 +1,23 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <p tal:condition="logged_in" class="pull-right"> + <div class="content"> + <p tal:condition="logged_in" class="pull-right"> <a href="${request.application_url}/logout">Logout</a> - </p> - <div tal:replace="structure content"> - Page text goes here. - </div> - <p> + </p> + <div tal:replace="structure page_text"> + Page text goes here. + </div> + <p> <a tal:attributes="href edit_url" href=""> Edit this page </a> - </p> - <p> - Viewing <strong><span tal:replace="page.__name__"> - Page Name Goes Here</span></strong> - </p> - <p>You can return to the - <a href="${request.application_url}">FrontPage</a>. - </p> - </div> - </div> - </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> + </p> + <p> + Viewing <strong><span tal:replace="page.__name__"> + Page Name Goes Here</span></strong> + </p> </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py index ea2da01af..3a3b170e2 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py @@ -2,30 +2,29 @@ from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound - -from pyramid.view import ( - view_config, - forbidden_view_config, - ) - from pyramid.security import ( - remember, forget, + remember, +) +from pyramid.view import ( + forbidden_view_config, + view_config, ) - -from .security import USERS, check_password -from .models import Page +from ..models import Page +from ..security import check_password, USERS # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='.models.Wiki', + +@view_config(context='..models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='.models.Page', renderer='templates/view.pt', + +@view_config(context='..models.Page', renderer='../templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -40,14 +39,15 @@ def view_page(context, request): add_url = request.application_url + '/add_page/' + word return '<a href="%s">%s</a>' % (add_url, word) - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) + page_text = publish_parts(context.data, writer_name='html')['html_body'] + page_text = wikiwords.sub(check, page_text) edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url, + return dict(page=context, page_text=page_text, edit_url=edit_url, logged_in=request.authenticated_userid) -@view_config(name='add_page', context='.models.Wiki', - renderer='templates/edit.pt', + +@view_config(name='add_page', context='..models.Wiki', + renderer='../templates/edit.pt', permission='edit') def add_page(context, request): pagename = request.subpath[0] @@ -65,8 +65,9 @@ def add_page(context, request): return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) -@view_config(name='edit_page', context='.models.Page', - renderer='templates/edit.pt', + +@view_config(name='edit_page', context='..models.Page', + renderer='../templates/edit.pt', permission='edit') def edit_page(context, request): if 'form.submitted' in request.params: @@ -77,9 +78,10 @@ def edit_page(context, request): save_url=request.resource_url(context, 'edit_page'), logged_in=request.authenticated_userid) -@view_config(context='.models.Wiki', name='login', - renderer='templates/login.pt') -@forbidden_view_config(renderer='templates/login.pt') + +@view_config(context='..models.Wiki', name='login', + renderer='../templates/login.pt') +@forbidden_view_config(renderer='../templates/login.pt') def login(request): login_url = request.resource_url(request.context, 'login') referrer = request.url @@ -104,10 +106,11 @@ def login(request): came_from=came_from, login=login, password=password, + title='Login', ) -@view_config(context='.models.Wiki', name='logout') +@view_config(context='..models.Wiki', name='logout') def logout(request): headers = forget(request) return HTTPFound(location=request.resource_url(request.context), diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py new file mode 100644 index 000000000..d44b4d0e6 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py @@ -0,0 +1,12 @@ +from pyramid.view import notfound_view_config + +from ..models import Page + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + pagename = request.path + page = Page(pagename) + page.__name__ = pagename + return dict(page=page) diff --git a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in +++ b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/basiclayout/pytest.ini b/docs/tutorials/wiki/src/basiclayout/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/basiclayout/pytest.ini +++ b/docs/tutorials/wiki/src/basiclayout/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index a4f143d24..d6d488ed2 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', ] tests_require = [ diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index f2b3c9568..830a607f3 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -11,13 +11,12 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py index aca6a4129..aca6a4129 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> + + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> + </div> + + </div> +</div> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt new file mode 100644 index 000000000..9fdaef00f --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt @@ -0,0 +1,62 @@ +<!DOCTYPE html metal:define-macro="layout"> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + + <!-- Custom styles for this scaffold --> + <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div metal:define-slot="content">No content</div> + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li> + </ul> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> + <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + </body> +</html> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index d63ea8c45..adac4fe35 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -1,65 +1,11 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>Cookiecutter ZODB project for the Pyramid Web Framework</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> - </div> - </div> + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid + application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> - <div class="row"> - <div class="links"> - <ul> - <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> - <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li> - </ul> - </div> - </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py deleted file mode 100644 index c1878bdd0..000000000 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyramid.view import view_config -from .models import MyModel - - -@view_config(context=MyModel, renderer='templates/mytemplate.pt') -def my_view(request): - return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py new file mode 100644 index 000000000..5d708d15c --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py @@ -0,0 +1,8 @@ +from pyramid.view import view_config + +from ..models import MyModel + + +@view_config(context=MyModel, renderer='../templates/mytemplate.pt') +def my_view(request): + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py new file mode 100644 index 000000000..728791d0a --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + return {} diff --git a/docs/tutorials/wiki/src/installation/MANIFEST.in b/docs/tutorials/wiki/src/installation/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/installation/MANIFEST.in +++ b/docs/tutorials/wiki/src/installation/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/installation/pytest.ini b/docs/tutorials/wiki/src/installation/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/installation/pytest.ini +++ b/docs/tutorials/wiki/src/installation/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py index a4f143d24..d6d488ed2 100644 --- a/docs/tutorials/wiki/src/installation/setup.py +++ b/docs/tutorials/wiki/src/installation/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', ] tests_require = [ diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py index f2b3c9568..830a607f3 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py @@ -11,13 +11,12 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/installation/tutorial/models.py b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py index aca6a4129..aca6a4129 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/models.py +++ b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py diff --git a/docs/tutorials/wiki/src/installation/tutorial/pshell.py b/docs/tutorials/wiki/src/installation/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/installation/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/installation/tutorial/routes.py b/docs/tutorials/wiki/src/installation/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> + + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> + </div> + + </div> +</div> diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt new file mode 100644 index 000000000..9fdaef00f --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt @@ -0,0 +1,62 @@ +<!DOCTYPE html metal:define-macro="layout"> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + + <!-- Custom styles for this scaffold --> + <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div metal:define-slot="content">No content</div> + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li> + </ul> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> + <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + </body> +</html> diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt index d63ea8c45..adac4fe35 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt @@ -1,65 +1,11 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>Cookiecutter ZODB project for the Pyramid Web Framework</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> - </div> - </div> + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid + application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> - <div class="row"> - <div class="links"> - <ul> - <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> - <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li> - </ul> - </div> - </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/installation/tutorial/tests.py +++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py deleted file mode 100644 index c1878bdd0..000000000 --- a/docs/tutorials/wiki/src/installation/tutorial/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyramid.view import view_config -from .models import MyModel - - -@view_config(context=MyModel, renderer='templates/mytemplate.pt') -def my_view(request): - return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/default.py b/docs/tutorials/wiki/src/installation/tutorial/views/default.py new file mode 100644 index 000000000..5d708d15c --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/views/default.py @@ -0,0 +1,8 @@ +from pyramid.view import view_config + +from ..models import MyModel + + +@view_config(context=MyModel, renderer='../templates/mytemplate.pt') +def my_view(request): + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py new file mode 100644 index 000000000..728791d0a --- /dev/null +++ b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + return {} diff --git a/docs/tutorials/wiki/src/models/MANIFEST.in b/docs/tutorials/wiki/src/models/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/models/MANIFEST.in +++ b/docs/tutorials/wiki/src/models/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/models/pytest.ini b/docs/tutorials/wiki/src/models/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/models/pytest.ini +++ b/docs/tutorials/wiki/src/models/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index a4f143d24..d6d488ed2 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', ] tests_require = [ diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index f2b3c9568..830a607f3 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -11,13 +11,12 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/models.py b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py index 7c6597afa..7c6597afa 100644 --- a/docs/tutorials/wiki/src/models/tutorial/models.py +++ b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py diff --git a/docs/tutorials/wiki/src/models/tutorial/pshell.py b/docs/tutorials/wiki/src/models/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/models/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/models/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/models/tutorial/routes.py b/docs/tutorials/wiki/src/models/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/models/tutorial/static/theme.css b/docs/tutorials/wiki/src/models/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/models/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/models/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/404.pt b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> + + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> + </div> + + </div> +</div> diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt new file mode 100644 index 000000000..9fdaef00f --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt @@ -0,0 +1,62 @@ +<!DOCTYPE html metal:define-macro="layout"> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + + <title>Cookiecutter Starter project for the Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + + <!-- Custom styles for this scaffold --> + <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div metal:define-slot="content">No content</div> + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li> + </ul> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> + <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + </body> +</html> diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index d63ea8c45..adac4fe35 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -1,65 +1,11 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>Cookiecutter ZODB project for the Pyramid Web Framework</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB Project</span></h1> - <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p> - </div> - </div> + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, a Pyramid + application generated by<br><span class="font-normal">Cookiecutter</span>.</p> </div> - <div class="row"> - <div class="links"> - <ul> - <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> - <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li> - <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li> - </ul> - </div> - </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki/src/models/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py deleted file mode 100644 index c1878bdd0..000000000 --- a/docs/tutorials/wiki/src/models/tutorial/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from pyramid.view import view_config -from .models import MyModel - - -@view_config(context=MyModel, renderer='templates/mytemplate.pt') -def my_view(request): - return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/models/tutorial/views/__init__.py b/docs/tutorials/wiki/src/models/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/views/__init__.py diff --git a/docs/tutorials/wiki/src/models/tutorial/views/default.py b/docs/tutorials/wiki/src/models/tutorial/views/default.py new file mode 100644 index 000000000..5d708d15c --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/views/default.py @@ -0,0 +1,8 @@ +from pyramid.view import view_config + +from ..models import MyModel + + +@view_config(context=MyModel, renderer='../templates/mytemplate.pt') +def my_view(request): + return {'project': 'myproj'} diff --git a/docs/tutorials/wiki/src/models/tutorial/views/notfound.py b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py new file mode 100644 index 000000000..728791d0a --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py @@ -0,0 +1,7 @@ +from pyramid.view import notfound_view_config + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + return {} diff --git a/docs/tutorials/wiki/src/tests/MANIFEST.in b/docs/tutorials/wiki/src/tests/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/tests/MANIFEST.in +++ b/docs/tutorials/wiki/src/tests/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/tests/pytest.ini b/docs/tutorials/wiki/src/tests/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/tests/pytest.ini +++ b/docs/tutorials/wiki/src/tests/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py index 7b405745e..fa5948acb 100644 --- a/docs/tutorials/wiki/src/tests/setup.py +++ b/docs/tutorials/wiki/src/tests/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', 'docutils', 'bcrypt', ] diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py index 58635ea74..935a5d6d2 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py @@ -15,18 +15,17 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() with Configurator(settings=settings) as config: config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py index ebd70e912..ebd70e912 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/models.py +++ b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py diff --git a/docs/tutorials/wiki/src/tests/tutorial/pshell.py b/docs/tutorials/wiki/src/tests/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/tests/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/tests/tutorial/routes.py b/docs/tutorials/wiki/src/tests/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> + + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> + </div> + + </div> +</div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt index eedb83da4..6438b1569 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt @@ -1,72 +1,27 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <p tal:condition="logged_in" class="pull-right"> + <div class="content"> + <p tal:condition="logged_in" class="pull-right"> <a href="${request.application_url}/logout">Logout</a> - </p> - <p> - Editing <strong><span tal:replace="page.__name__"> - Page Name Goes Here</span></strong> - </p> - <p>You can return to the - <a href="${request.application_url}">FrontPage</a>. - </p> - <form action="${save_url}" method="post"> + </p> + <p> + Editing <strong><span tal:replace="page.__name__"> + Page Name Goes Here</span></strong> + </p> + <form action="${save_url}" method="post"> <div class="form-group"> - <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea> + <textarea tal:content="page.data" + class="form-control" name="body" + rows="10" cols="60"></textarea> </div> <div class="form-group"> - <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button> + <button type="submit" + name="form.submitted" value="Save" + class="btn btn-default">Save</button> </div> - </form> - </div> - </div> + </form> </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt new file mode 100644 index 000000000..b606e8dad --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt @@ -0,0 +1,59 @@ +<!DOCTYPE html metal:define-macro="layout"> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + + <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + + <!-- Bootstrap core CSS --> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + + <!-- Custom styles for this scaffold --> + <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div metal:define-slot="content">No content</div> + <div class="content"> + <p>You can return to the + <a href="${request.application_url}">FrontPage</a>. + </p> + </div> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> + <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + </body> +</html> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt index 626db6637..acc4876cf 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt @@ -1,39 +1,7 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>Login - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> + <div class="content"> <p> <strong> Login @@ -54,22 +22,7 @@ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button> </div> </form> - </div> - </div> </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt index f2a9249ef..911ab0c99 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt @@ -1,72 +1,23 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <p tal:condition="logged_in" class="pull-right"> + <div class="content"> + <p tal:condition="logged_in" class="pull-right"> <a href="${request.application_url}/logout">Logout</a> - </p> - <div tal:replace="structure content"> - Page text goes here. - </div> - <p> + </p> + <div tal:replace="structure page_text"> + Page text goes here. + </div> + <p> <a tal:attributes="href edit_url" href=""> Edit this page </a> - </p> - <p> - Viewing <strong><span tal:replace="page.__name__"> - Page Name Goes Here</span></strong> - </p> - <p>You can return to the - <a href="${request.application_url}">FrontPage</a>. - </p> - </div> - </div> - </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> + </p> + <p> + Viewing <strong><span tal:replace="page.__name__"> + Page Name Goes Here</span></strong> + </p> </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py index d713d3fdd..ff1c07b7c 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py @@ -43,7 +43,7 @@ class AppmakerTests(unittest.TestCase): class ViewWikiTests(unittest.TestCase): def test_it(self): - from .views import view_wiki + from .views.default import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) @@ -51,7 +51,7 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): - from .views import view_page + from .views.default import view_page return view_page(context, request) def test_it(self): @@ -64,7 +64,7 @@ class ViewPageTests(unittest.TestCase): info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual( - info['content'], + info['page_text'], '<div class="document">\n' '<p>Hello <a href="http://example.com/add_page/CruelWorld">' 'CruelWorld</a> ' @@ -77,7 +77,7 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): - from .views import add_page + from .views.default import add_page return add_page(context, request) def test_it_notsubmitted(self): @@ -103,7 +103,7 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): - from .views import edit_page + from .views.default import edit_page return edit_page(context, request) def test_it_notsubmitted(self): diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views/default.py index ea2da01af..3a3b170e2 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/tests/tutorial/views/default.py @@ -2,30 +2,29 @@ from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound - -from pyramid.view import ( - view_config, - forbidden_view_config, - ) - from pyramid.security import ( - remember, forget, + remember, +) +from pyramid.view import ( + forbidden_view_config, + view_config, ) - -from .security import USERS, check_password -from .models import Page +from ..models import Page +from ..security import check_password, USERS # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='.models.Wiki', + +@view_config(context='..models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='.models.Page', renderer='templates/view.pt', + +@view_config(context='..models.Page', renderer='../templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -40,14 +39,15 @@ def view_page(context, request): add_url = request.application_url + '/add_page/' + word return '<a href="%s">%s</a>' % (add_url, word) - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) + page_text = publish_parts(context.data, writer_name='html')['html_body'] + page_text = wikiwords.sub(check, page_text) edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url, + return dict(page=context, page_text=page_text, edit_url=edit_url, logged_in=request.authenticated_userid) -@view_config(name='add_page', context='.models.Wiki', - renderer='templates/edit.pt', + +@view_config(name='add_page', context='..models.Wiki', + renderer='../templates/edit.pt', permission='edit') def add_page(context, request): pagename = request.subpath[0] @@ -65,8 +65,9 @@ def add_page(context, request): return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) -@view_config(name='edit_page', context='.models.Page', - renderer='templates/edit.pt', + +@view_config(name='edit_page', context='..models.Page', + renderer='../templates/edit.pt', permission='edit') def edit_page(context, request): if 'form.submitted' in request.params: @@ -77,9 +78,10 @@ def edit_page(context, request): save_url=request.resource_url(context, 'edit_page'), logged_in=request.authenticated_userid) -@view_config(context='.models.Wiki', name='login', - renderer='templates/login.pt') -@forbidden_view_config(renderer='templates/login.pt') + +@view_config(context='..models.Wiki', name='login', + renderer='../templates/login.pt') +@forbidden_view_config(renderer='../templates/login.pt') def login(request): login_url = request.resource_url(request.context, 'login') referrer = request.url @@ -104,10 +106,11 @@ def login(request): came_from=came_from, login=login, password=password, + title='Login', ) -@view_config(context='.models.Wiki', name='logout') +@view_config(context='..models.Wiki', name='logout') def logout(request): headers = forget(request) return HTTPFound(location=request.resource_url(request.context), diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py new file mode 100644 index 000000000..d44b4d0e6 --- /dev/null +++ b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py @@ -0,0 +1,12 @@ +from pyramid.view import notfound_view_config + +from ..models import Page + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + pagename = request.path + page = Page(pagename) + page.__name__ = pagename + return dict(page=page) diff --git a/docs/tutorials/wiki/src/views/MANIFEST.in b/docs/tutorials/wiki/src/views/MANIFEST.in index 81beba1b1..05cc195d9 100644 --- a/docs/tutorials/wiki/src/views/MANIFEST.in +++ b/docs/tutorials/wiki/src/views/MANIFEST.in @@ -1,2 +1,2 @@ include *.txt *.ini *.cfg *.rst -recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml +recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2 diff --git a/docs/tutorials/wiki/src/views/pytest.ini b/docs/tutorials/wiki/src/views/pytest.ini index 8b76bc410..a3489cdf8 100644 --- a/docs/tutorials/wiki/src/views/pytest.ini +++ b/docs/tutorials/wiki/src/views/pytest.ini @@ -1,3 +1,3 @@ [pytest] testpaths = tutorial -python_files = *.py +python_files = test*.py diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index 3c19db7b9..6f3cae397 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -10,15 +10,15 @@ with open(os.path.join(here, 'CHANGES.txt')) as f: requires = [ 'plaster_pastedeploy', - 'pyramid >= 1.9a', + 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', + 'waitress', 'pyramid_retry', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', - 'waitress', 'docutils', ] diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index f2b3c9568..830a607f3 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -11,13 +11,12 @@ def root_factory(request): def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager' with Configurator(settings=settings) as config: - config.include('pyramid_chameleon') config.include('pyramid_tm') config.include('pyramid_retry') config.include('pyramid_zodbconn') config.set_root_factory(root_factory) - config.add_static_view('static', 'static', cache_max_age=3600) + config.include('pyramid_chameleon') + config.include('.routes') config.scan() - return config.make_wsgi_app() + return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/views/tutorial/models.py b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py index 7c6597afa..7c6597afa 100644 --- a/docs/tutorials/wiki/src/views/tutorial/models.py +++ b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py diff --git a/docs/tutorials/wiki/src/views/tutorial/pshell.py b/docs/tutorials/wiki/src/views/tutorial/pshell.py index 3d026291b..a7cfa6a27 100644 --- a/docs/tutorials/wiki/src/views/tutorial/pshell.py +++ b/docs/tutorials/wiki/src/views/tutorial/pshell.py @@ -1,5 +1,6 @@ from . import models + def setup(env): request = env['request'] diff --git a/docs/tutorials/wiki/src/views/tutorial/routes.py b/docs/tutorials/wiki/src/views/tutorial/routes.py new file mode 100644 index 000000000..3c0a37992 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/routes.py @@ -0,0 +1,2 @@ +def includeme(config): + config.add_static_view('static', 'static', cache_max_age=3600) diff --git a/docs/tutorials/wiki/src/views/tutorial/static/theme.css b/docs/tutorials/wiki/src/views/tutorial/static/theme.css index 0f4b1a4d4..a70ee557a 100644 --- a/docs/tutorials/wiki/src/views/tutorial/static/theme.css +++ b/docs/tutorials/wiki/src/views/tutorial/static/theme.css @@ -17,6 +17,9 @@ h6 { p { font-weight: 300; } +button, input, optgroup, select, textarea { + color: black; +} .font-normal { font-weight: 400; } diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/404.pt b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt new file mode 100644 index 000000000..07298940c --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt @@ -0,0 +1,10 @@ +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> + + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1> + <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p> + </div> + + </div> +</div> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt index 2db3ca79c..488e7a6af 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt @@ -1,69 +1,24 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <p> - Editing <strong><span tal:replace="page.__name__"> - Page Name Goes Here</span></strong> - </p> - <p>You can return to the - <a href="${request.application_url}">FrontPage</a>. - </p> - <form action="${save_url}" method="post"> + <div class="content"> + <p> + Editing <strong><span tal:replace="page.__name__"> + Page Name Goes Here</span></strong> + </p> + <form action="${save_url}" method="post"> <div class="form-group"> - <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea> + <textarea tal:content="page.data" + class="form-control" name="body" + rows="10" cols="60"></textarea> </div> <div class="form-group"> - <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button> + <button type="submit" + name="form.submitted" value="Save" + class="btn btn-default">Save</button> </div> - </form> - </div> - </div> + </form> </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> - </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt new file mode 100644 index 000000000..b606e8dad --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt @@ -0,0 +1,59 @@ +<!DOCTYPE html metal:define-macro="layout"> +<html lang="{{request.locale_name}}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> + + <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + + <!-- Bootstrap core CSS --> + <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + + <!-- Custom styles for this scaffold --> + <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div metal:define-slot="content">No content</div> + <div class="content"> + <p>You can return to the + <a href="${request.application_url}">FrontPage</a>. + </p> + </div> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> + <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> + </body> +</html> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt index 1feeab5ef..b8a6fbbaf 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt @@ -1,70 +1,20 @@ -<!DOCTYPE html> -<html lang="${request.locale_name}"> - <head> - <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="description" content="pyramid web application"> - <meta name="author" content="Pylons Project"> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}"> +<div metal:use-macro="load: layout.pt"> + <div metal:fill-slot="content"> - <title>${page.__name__} - Pyramid tutorial wiki (based on - TurboGears 20-Minute Wiki)</title> - - <!-- Bootstrap core CSS --> - <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> - - <!-- Custom styles for this scaffold --> - <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet"> - - <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries --> - <!--[if lt IE 9]> - <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script> - <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script> - <![endif]--> - </head> - - <body> - - <div class="starter-template"> - <div class="container"> - <div class="row"> - <div class="col-md-2"> - <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework"> - </div> - <div class="col-md-10"> - <div class="content"> - <div tal:replace="structure content"> - Page text goes here. - </div> - <p> + <div class="content"> + <div tal:replace="structure page_text"> + Page text goes here. + </div> + <p> <a tal:attributes="href edit_url" href=""> Edit this page </a> - </p> - <p> - Viewing <strong><span tal:replace="page.__name__"> - Page Name Goes Here</span></strong> - </p> - <p>You can return to the - <a href="${request.application_url}">FrontPage</a>. - </p> - </div> - </div> - </div> - <div class="row"> - <div class="copyright"> - Copyright © Pylons Project - </div> + </p> + <p> + Viewing <strong><span tal:replace="page.__name__"> + Page Name Goes Here</span></strong> + </p> </div> - </div> - </div> - - <!-- Bootstrap core JavaScript - ================================================== --> - <!-- Placed at the end of the document so the pages load faster --> - <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> - <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> - </body> -</html> + </div> +</div> diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py index ca7a47279..6279d9f66 100644 --- a/docs/tutorials/wiki/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki/src/views/tutorial/tests.py @@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from .views import my_view + from .views.default import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'myproj') + diff --git a/docs/tutorials/wiki/src/views/tutorial/views/__init__.py b/docs/tutorials/wiki/src/views/tutorial/views/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/views/__init__.py diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views/default.py index fd2b0edc1..b4b65a49b 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ b/docs/tutorials/wiki/src/views/tutorial/views/default.py @@ -4,16 +4,18 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config -from .models import Page +from ..models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='.models.Wiki') + +@view_config(context='..models.Wiki') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='.models.Page', renderer='templates/view.pt') + +@view_config(context='..models.Page', renderer='../templates/view.pt') def view_page(context, request): wiki = context.__parent__ @@ -27,13 +29,14 @@ def view_page(context, request): add_url = request.application_url + '/add_page/' + word return '<a href="%s">%s</a>' % (add_url, word) - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) + page_text = publish_parts(context.data, writer_name='html')['html_body'] + page_text = wikiwords.sub(check, page_text) edit_url = request.resource_url(context, 'edit_page') - return dict(page=context, content=content, edit_url=edit_url) + return dict(page=context, page_text=page_text, edit_url=edit_url) -@view_config(name='add_page', context='.models.Wiki', - renderer='templates/edit.pt') + +@view_config(name='add_page', context='..models.Wiki', + renderer='../templates/edit.pt') def add_page(context, request): pagename = request.subpath[0] if 'form.submitted' in request.params: @@ -49,12 +52,13 @@ def add_page(context, request): page.__parent__ = context return dict(page=page, save_url=save_url) -@view_config(name='edit_page', context='.models.Page', - renderer='templates/edit.pt') + +@view_config(name='edit_page', context='..models.Page', + renderer='../templates/edit.pt') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] return HTTPFound(location=request.resource_url(context)) return dict(page=context, - save_url=request.resource_url(context, 'edit_page'))
\ No newline at end of file + save_url=request.resource_url(context, 'edit_page')) diff --git a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py new file mode 100644 index 000000000..d44b4d0e6 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py @@ -0,0 +1,12 @@ +from pyramid.view import notfound_view_config + +from ..models import Page + + +@notfound_view_config(renderer='../templates/404.pt') +def notfound_view(request): + request.response.status = 404 + pagename = request.path + page = Page(pagename) + page.__name__ = pagename + return dict(page=page) diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst index fdd218add..a0872e605 100644 --- a/docs/tutorials/wiki/tests.rst +++ b/docs/tutorials/wiki/tests.rst @@ -4,58 +4,56 @@ Adding Tests ============ -We will now add tests for the models and the views and a few functional tests -in ``tests.py``. Tests ensure that an application works, and that it -continues to work when changes are made in the future. +We will now add tests for the models and the views and a few functional tests in ``tests.py``. +Tests ensure that an application works, and that it continues to work when changes are made in the future. + Test the models =============== -We write tests for the ``model`` classes and the ``appmaker``. Changing -``tests.py``, we'll write a separate test class for each ``model`` class, and -we'll write a test class for the ``appmaker``. +We write tests for the ``model`` classes and the ``appmaker``. +Changing ``tests.py``, we will write a separate test class for each ``model`` class +We will also write a test class for the ``appmaker``. + +To do so, we will retain the ``tutorial.tests.ViewTests`` class that was generated from choosing the ``zodb`` backend option. +We will add three test classes, one for each of the following: + +- the ``Page`` model named ``PageModelTests`` +- the ``Wiki`` model named ``WikiModelTests`` +- the appmaker named ``AppmakerTests`` -To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was -generated from choosing the ``zodb`` backend option. We'll add three test -classes: one for the ``Page`` model named ``PageModelTests``, one for the -``Wiki`` model named ``WikiModelTests``, and one for the appmaker named -``AppmakerTests``. Test the views ============== -We'll modify our ``tests.py`` file, adding tests for each view function we -added previously. As a result, we'll delete the ``ViewTests`` class that the -``zodb`` backend option provided, and add four other test classes: -``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. -These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` -views. +We will modify our ``tests.py`` file, adding tests for each view function that we added previously. +As a result, we will delete the ``ViewTests`` class that the ``zodb`` backend option provided, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. +These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views. + Functional tests ================ -We'll test the whole application, covering security aspects that are not -tested in the unit tests, like logging in, logging out, checking that -the ``viewer`` user cannot add or edit pages, but the ``editor`` user -can, and so on. +We will test the whole application, covering security aspects that are not tested in the unit tests, such as logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on. + View the results of all our edits to ``tests.py`` ================================================= -Open the ``tutorial/tests.py`` module, and edit it such that it appears as -follows: +Open the ``tutorial/tests.py`` module, and edit it such that it appears as follows: .. literalinclude:: src/tests/tutorial/tests.py :linenos: :language: python + Running the tests ================= -We can run these tests by using ``pytest`` similarly to how we did in -:ref:`running_tests`. Courtesy of the cookiecutter, our testing dependencies have -already been satisfied and ``pytest`` and coverage have already been -configured, so we can jump right to running tests. +We can run these tests by using ``pytest`` similarly to how we did in :ref:`running_tests`. +Courtesy of the cookiecutter, our testing dependencies have already been satisfied. +``pytest`` and coverage have already been configured. +We can jump right to running tests. On Unix: @@ -75,3 +73,6 @@ The expected result should look like the following: ......................... 25 passed in 6.87 seconds + +If you use Python 3.7, you may see deprecation warnings from the docutils 0.14 package. +You can apply a [patch](https://sourceforge.net/p/docutils/patches/144/) to fix the issue, or ignore it and wait for the next release of docutils. |
