.. _qtut_more_view_classes: ========================== 15: More With View Classes ========================== Group views into a class, sharing configuration, state, and logic. Background ========== As part of its mission to help build more ambitious web applications, Pyramid provides many more features for views and view classes. The Pyramid documentation discusses views as a Python "callable". This callable can be a function, an object with a ``__call__``, or a Python class. In this last case, methods on the class can be decorated with ``@view_config`` to register the class methods with the :term:`configurator` as a view. At first, our views were simple, free-standing functions. Many times your views are related: different ways to look at or work on the same data, or a REST API that handles multiple operations. Grouping these together as a :ref:`view class ` makes sense: - Group views. - Centralize some repetitive defaults. - Share some state and helpers. Pyramid views have :ref:`view predicates ` that determine which view is matched to a request, based on factors such as the request method, the form parameters, and so on. These predicates provide many axes of flexibility. The following shows a simple example with four operations: view a home page which leads to a form, save a change, and press the delete button. Objectives ========== - Group related views into a view class. - Centralize configuration with class-level ``@view_defaults``. - Dispatch one route/URL to multiple views based on request data. - Share states and logic between views and templates via the view class. Steps ===== #. First we copy the results of the ``templating`` step: .. code-block:: bash cd ..; cp -r templating more_view_classes; cd more_view_classes $VENV/bin/pip install -e . #. Our route in ``more_view_classes/tutorial/__init__.py`` needs some replacement patterns: .. literalinclude:: more_view_classes/tutorial/__init__.py :linenos: #. Our ``more_view_classes/tutorial/views.py`` now has a view class with several views: .. literalinclude:: more_view_classes/tutorial/views.py :linenos: #. Our primary view needs a template at ``more_view_classes/tutorial/home.pt``: .. literalinclude:: more_view_classes/tutorial/home.pt :language: html #. Ditto for our other view from the previous section at ``more_view_classes/tutorial/hello.pt``: .. literalinclude:: more_view_classes/tutorial/hello.pt :language: html #. We have an edit view that also needs a template at ``more_view_classes/tutorial/edit.pt``: .. literalinclude:: more_view_classes/tutorial/edit.pt :language: html #. And finally the delete view's template at ``more_view_classes/tutorial/delete.pt``: .. literalinclude:: more_view_classes/tutorial/delete.pt :language: html #. Our tests in ``more_view_classes/tutorial/tests.py`` fail, so let's modify them: .. literalinclude:: more_view_classes/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $VENV/bin/pytest tutorial/tests.py -q .. 2 passed in 0.40 seconds #. Run your Pyramid application with: .. code-block:: bash $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/howdy/jane/doe in your browser. Click the ``Save`` and ``Delete`` buttons, and watch the output in the console window. Analysis ======== As you can see, the four views are logically grouped together. Specifically: - We have a ``home`` view available at http://localhost:6543/ with a clickable link to the ``hello`` view. - The second view is returned when you go to ``/howdy/jane/doe``. This URL is mapped to the ``hello`` route that we centrally set using the optional ``@view_defaults``. - The third view is returned when the form is submitted with a ``POST`` method. This rule is specified in the ``@view_config`` for that view. - The fourth view is returned when clicking on a button such as ````. In this step we show, using the following information as criteria, how to decide which view to use: - Method of the HTTP request (``GET``, ``POST``, etc.) - Parameter information in the request (submitted form field names) We also centralize part of the view configuration to the class level with ``@view_defaults``, then in one view, override that default just for that one view. Finally, we put this commonality between views to work in the view class by sharing: - State assigned in ``TutorialViews.__init__`` - A computed value These are then available both in the view methods and in the templates (e.g., ``${view.view_name}`` and ``${view.full_name}``). As a note, we made a switch in our templates on how we generate URLs. We previously hardcoded the URLs, such as: .. code-block:: html Howdy In ``home.pt`` we switched to: .. code-block:: xml form Pyramid has rich facilities to help generate URLs in a flexible, non-error prone fashion. Extra credit ============ #. Why could our template do ``${view.full_name}`` and not have to do ``${view.full_name()}``? #. The ``edit`` and ``delete`` views are both receive ``POST`` requests. Why does the ``edit`` view configuration not catch the ``POST`` used by ``delete``? #. We used Python ``@property`` on ``full_name``. If we reference this many times in a template or view code, it would re-compute this every time. Does Pyramid provide something that will cache the initial computation on a property? #. Can you associate more than one route with the same view? #. There is also a ``request.route_path`` API. How does this differ from ``request.route_url``? .. seealso:: :ref:`class_as_view`, `Weird Stuff You Can Do With URL Dispatch `_