From bb60b86feeea7cfbb531460b22ad40f211562708 Mon Sep 17 00:00:00 2001 From: Zack Marvel Date: Wed, 10 Dec 2014 01:25:11 -0500 Subject: Revise URL Dispatch documentation to use config.scan() in Examples 1, 2, and 3 In response to #600. --- docs/narr/urldispatch.rst | 57 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 10 deletions(-) (limited to 'docs') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 87a962a9a..2fd971917 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -495,17 +495,20 @@ result in a particular view callable being invoked: :linenos: config.add_route('idea', 'site/{id}') - config.add_view('mypackage.views.site_view', route_name='idea') + config.scan() When a route configuration with a ``view`` attribute is added to the system, and an incoming request matches the *pattern* of the route configuration, the :term:`view callable` named as the ``view`` attribute of the route configuration will be invoked. -In the case of the above example, when the URL of a request matches -``/site/{id}``, the view callable at the Python dotted path name -``mypackage.views.site_view`` will be called with the request. In other -words, we've associated a view callable directly with a route pattern. +Recall that ``config.scan`` is equivalent to calling ``config.add_view``, +because the ``@view_config`` decorator in ``mypackage.views``, shown below, +maps the route name to the matching view callable. In the case of the above +example, when the URL of a request matches ``/site/{id}``, the view callable at +the Python dotted path name ``mypackage.views.site_view`` will be called with +the request. In other words, we've associated a view callable directly with a +route pattern. When the ``/site/{id}`` route pattern matches during a request, the ``site_view`` view callable is invoked with that request as its sole @@ -519,8 +522,10 @@ The ``mypackage.views`` module referred to above might look like so: .. code-block:: python :linenos: + from pyramid.view import view_config from pyramid.response import Response + @view_config(route_name='idea') def site_view(request): return Response(request.matchdict['id']) @@ -542,11 +547,30 @@ add to your application: config.add_route('idea', 'ideas/{idea}') config.add_route('user', 'users/{user}') config.add_route('tag', 'tags/{tag}') + config.scan() + +Here is an example of a corresponding ``mypackage.views`` module: - config.add_view('mypackage.views.idea_view', route_name='idea') - config.add_view('mypackage.views.user_view', route_name='user') - config.add_view('mypackage.views.tag_view', route_name='tag') +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + @view_config(route_name='idea') + def idea_view(request): + return Response(request.matchdict['id']) + + @view_config(route_name='user') + def user_view(request): + user = request.matchdict['user'] + return Response(u'The user is {}.'.format(user)) + + @view_config(route_name='tag') + def tag_view(request): + tag = request.matchdict['tag'] + return Response(u'The tag is {}.'.format(tag)) + The above configuration will allow :app:`Pyramid` to service URLs in these forms: @@ -596,7 +620,7 @@ An example of using a route with a factory: :linenos: config.add_route('idea', 'ideas/{idea}', factory='myproject.resources.Idea') - config.add_view('myproject.views.idea_view', route_name='idea') + config.scan() The above route will manufacture an ``Idea`` resource as a :term:`context`, assuming that ``mypackage.resources.Idea`` resolves to a class that accepts a @@ -610,7 +634,20 @@ request in its ``__init__``. For example: pass In a more complicated application, this root factory might be a class -representing a :term:`SQLAlchemy` model. +representing a :term:`SQLAlchemy` model. The view ``mypackage.views.idea_view`` +might look like this: + +.. code-block:: python + :linenos: + + @view_config(route_name='idea') + def idea_view(request): + idea = request.context + return Response(idea) + +Here, ``request.context`` is an instance of ``Idea``. If indeed the resource +object is a SQLAlchemy model, you do not even have to perform a query in the +view callable, since you have access to the resource via ``request.context``. See :ref:`route_factories` for more details about how to use route factories. -- cgit v1.2.3 From 3c163b212a6848c1d45916073d6a60a9020ea5c1 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 17 Feb 2015 21:08:58 -0600 Subject: reword a small part to clarify what's happening with view_config --- docs/narr/urldispatch.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 2fd971917..ca6a55164 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -502,9 +502,10 @@ and an incoming request matches the *pattern* of the route configuration, the :term:`view callable` named as the ``view`` attribute of the route configuration will be invoked. -Recall that ``config.scan`` is equivalent to calling ``config.add_view``, -because the ``@view_config`` decorator in ``mypackage.views``, shown below, -maps the route name to the matching view callable. In the case of the above +Recall that the ``@view_config`` is equivalent to calling ``config.add_view``, +because the ``config.scan()`` call will import ``mypackage.views``, shown +below, and execute ``config.add_view`` under the hood. Each view then maps the +route name to the matching view callable. In the case of the above example, when the URL of a request matches ``/site/{id}``, the view callable at the Python dotted path name ``mypackage.views.site_view`` will be called with the request. In other words, we've associated a view callable directly with a -- cgit v1.2.3 From 459493929a92b14a986ba387bdabd3c551ddee72 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 17 Feb 2015 21:14:49 -0600 Subject: grammar --- docs/narr/security.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 2dc0c76af..a02f65660 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -653,7 +653,7 @@ that implements the following interface: """ After you do so, you can pass an instance of such a class into the -:class:`~pyramid.config.Configurator.set_authentication_policy` method +:class:`~pyramid.config.Configurator.set_authentication_policy` method at configuration time to use it. .. index:: -- cgit v1.2.3 From df966ac2f5c6fc230db920d945be4a6567521e40 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 17 Feb 2015 21:45:56 -0600 Subject: enhance security docs with an example of subclassing a builtin policy --- docs/narr/security.rst | 58 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/narr/security.rst b/docs/narr/security.rst index a02f65660..75f4dc7c5 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -341,9 +341,7 @@ third argument is a permission or sequence of permission names. A principal is usually a user id, however it also may be a group id if your authentication system provides group information and the effective :term:`authentication policy` policy is written to respect group information. -For example, the -:class:`pyramid.authentication.RepozeWho1AuthenticationPolicy` respects group -information if you configure it with a ``callback``. +See :ref:`extending_default_authentication_policies`. Each ACE in an ACL is processed by an authorization policy *in the order dictated by the ACL*. So if you have an ACL like this: @@ -582,6 +580,60 @@ denied or allowed. Introspecting this information in the debugger or via print statements when a call to :meth:`~pyramid.request.Request.has_permission` fails is often useful. +.. index:: + single: authentication policy (extending) + +.. _extending_default_authentication_policies: + +Extending Default Authentication Policies +----------------------------------------- + +Pyramid ships with some builtin authentication policies for use in your +applications. See :mod:`pyramid.authentication` for the available +policies. They differ on their mechanisms for tracking authentication +credentials between requests, however they all interface with your +application in mostly the same way. + +Above you learned about :ref:`assigning_acls`. Each :term:`principal` used +in the :term:`ACL` is matched against the list returned from +:meth:`pyramid.interfaces.IAuthenticationPolicy.effective_principals`. +Similarly, :meth:`pyramid.request.Request.authenticated_userid` maps to +:meth:`pyramid.interfaces.IAuthenticationPolicy.authenticated_userid`. + +You may control these values by subclassing the default authentication +policies. For example, below we subclass the +:class:`pyramid.authentication.AuthTktAuthenticationPolicy` and define +extra functionality to query our database before confirming that the +:term:`userid` is valid in order to avoid blindly trusting the value in the +cookie (what if the cookie is still valid but the user has deleted their +account?). We then use that :term:`userid` to augment the +``effective_principals`` with information about groups and other state for +that user. + +.. code-block:: python + :linenos: + + from pyramid.authentication import AuthTktAuthenticationPolicy + + class MyAuthenticationPolicy(AuthTktAuthenticationPolicy): + def authenticated_userid(self, request): + userid = self.unauthenticated_userid(request) + if userid: + if request.verify_userid_is_still_valid(userid): + return userid + + def effective_principals(self, request): + principals = [Everyone] + userid = self.authenticated_userid(request) + if userid: + principals += [Authenticated, str(userid)] + return principals + +In most instances ``authenticated_userid`` and ``effective_principals`` are +application-specific whereas ``unauthenticated_userid``, ``remember`` and +``forget`` are generic and focused on transport/serialization of data +between consecutive requests. + .. index:: single: authentication policy (creating) -- cgit v1.2.3