diff options
| -rw-r--r-- | CHANGES.txt | 75 | ||||
| -rw-r--r-- | TODO.txt | 24 | ||||
| -rw-r--r-- | docs/api/authentication.rst | 10 | ||||
| -rw-r--r-- | docs/api/security.rst | 2 | ||||
| -rw-r--r-- | docs/designdefense.rst | 8 | ||||
| -rw-r--r-- | docs/glossary.rst | 9 | ||||
| -rw-r--r-- | docs/narr/project.rst | 8 | ||||
| -rw-r--r-- | docs/narr/templates.rst | 2 | ||||
| -rw-r--r-- | docs/tutorials/wiki2/src/views/tutorial/__init__.py | 2 | ||||
| -rw-r--r-- | docs/zcml/handler.rst | 2 | ||||
| -rw-r--r-- | pyramid/authentication.py | 27 | ||||
| -rw-r--r-- | pyramid/config.py | 350 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 19 | ||||
| -rw-r--r-- | pyramid/security.py | 18 | ||||
| -rw-r--r-- | pyramid/testing.py | 8 | ||||
| -rw-r--r-- | pyramid/tests/test_authentication.py | 33 | ||||
| -rw-r--r-- | pyramid/tests/test_config.py | 1434 | ||||
| -rw-r--r-- | pyramid/tests/test_security.py | 33 | ||||
| -rw-r--r-- | pyramid/tests/test_testing.py | 6 | ||||
| -rw-r--r-- | pyramid/tests/test_view.py | 5 | ||||
| -rw-r--r-- | pyramid/url.py | 2 | ||||
| -rw-r--r-- | pyramid/view.py | 6 |
22 files changed, 1262 insertions, 821 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 04f5a7d05..6471dd7a8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,23 +9,70 @@ Bug Fixes Instead of trying to resolve the view, if it cannot, it will now just print ``<unknown>``. +- The `self` argument was included in new methods of the ``ISession`` interface + signature, causing ``pyramid_beaker`` tests to fail (jkrebs). + Features -------- - ``config.add_view`` now accepts a ``decorator`` keyword argument, a callable which will decorate the view callable before it is added to the registry. -- ``config.add_view`` now accepts a ``view_mapper`` keyword argument, which - should be a class which implements the new - ``pyramid.interfaces.IViewMapperFactory`` interface. Use of an alternate - view mapper allows objects that are meant to be used as view callables to - have an arbitrary argument list and an arbitrary result. This feature will - be used by Pyramid extension developers, not by "civilians". - -- If a handler class provides an __action_decorator__ attribute (usually a - classmethod or staticmethod), use that as the decorator for each view +- If a handler class provides an ``__action_decorator__`` attribute (usually + a classmethod or staticmethod), use that as the decorator for each view registration for that handler. +- The ``pyramid.interfaces.IAuthenticationPolicy`` interface now specifies an + ``unauthenticated_userid`` method. This method supports an important + optimization required by people who are using persistent storages which do + not support object caching and whom want to create a "user object" as a + request attribute. + +- A new API has been added to the ``pyramid.security`` module named + ``unauthenticated_userid``. This API function calls the + ``unauthenticated_userid`` method of the effective security policy. + +- An ``unauthenticated_userid`` method has been added to the dummy + authentication policy returned by + ``pyramid.config.Configurator.testing_securitypolicy``. It returns the + same thing as that the dummy authentication policy's + ``authenticated_userid`` method. + +- The class ``pyramid.authentication.AuthTktCookieHelper`` is now an API. + This class can be used by third-party authentication policy developers to + help in the mechanics of authentication cookie-setting. + +- New constructor argument to Configurator: ``default_view_mapper``. Useful + to create systems that have alternate view calling conventions. A view + mapper allows objects that are meant to be used as view callables to have + an arbitrary argument list and an arbitrary result. The object passed as + ``default_view_mapper`` should implement the + ``pyramid.interfaces.IViewMapperFactory`` interface. + +- add a ``set_view_mapper`` API to Configurator. Has + the same result as passing ``default_view_mapper`` to the Configurator + constructor. + +- ``config.add_view`` now accepts a ``view_mapper`` keyword argument, which + should either be ``None``, a string representing a Python dotted name, or + an object which is an ``IViewMapperFactory``. This feature is not useful + for "civilians", only for extension writers. + +- Allow static renderer provided during view registration to be overridden at + request time via a request attribute named ``override_renderer``, which + should be the name of a previously registered renderer. Useful to provide + "omnipresent" RPC using existing rendered views. + +Backwards Incompatibilities +--------------------------- + +- Since the ``pyramid.interfaces.IAuthenticationPolicy`` interface now + specifies that a policy implementation must implement an + ``unauthenticated_userid`` method, all third-party custom authentication + policies now must implement this method. It, however, will only be called + when the global function named ``pyramid.security.unauthenticated_userid`` + is invoked, so if you're not invoking that, you will not notice any issues. + Documentation ------------- @@ -51,6 +98,16 @@ Internals - The class used as the "page template" in ``pyramid.chameleon_text`` was removed, in preference to using a Chameleon-inbuilt version. +- A view callable wrapper registered in the registry now contains an + ``__original_view__`` attribute which references the original view callable + (or class). + +- The (non-API) method of all internal authentication policy implementations + previously named ``_get_userid`` is now named ``unauthenticated_userid``, + promoted to an API method. If you were overriding this method, you'll now + need to override it as ``unauthenticated_userid`` instead. + +- Remove (non-API) function of config.py named _map_view. 1.0a8 (2010-12-27) ================== @@ -11,14 +11,32 @@ Must-Have (before 1.0) - Re-make testing.setUp() and testing.tearDown() the canonical APIs for test configuration. -- Document ``decorator=`` and ``view_mapper`` parameters to add_view. +- Document ``decorator=`` and ``view_mapper`` parameters to add_view and + ``@view_config``. -- Allow ``decorator=`` and ``view_mapper=`` to be passed via ZCML and the - ``view_config`` decorator. +- Allow ``decorator=`` and ``view_mapper=`` to be passed via ZCML. + +- Document ``Configurator.set_view_mapper``. + +- Document ``__view_mapper__`` attribute for view callable view mapper + preference. + +- ZCML directive for view mapper (or just use "utility", but it's not eager). + +- Decide whether ``self.decorated_view(view)`` is in the right place in the + view deriver chain. + +- API docs for ``pyramid.views.action``. Should-Have ----------- +- ``current_route_url`` function. https://gist.github.com/762842 + +- Convert paster template and tutorial HTML templates to use + ``request.static_url('{{package}}:static/foo.css')`` rather than + ``${request.application_url}/static/foo.css``. + - Add notes about renderer response attrs to request docs. - Add an example of using a cascade to serve static assets from the root. diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst index 54db77417..a6d4c1e18 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -3,6 +3,9 @@ :mod:`pyramid.authentication` -------------------------------- +Authentication Policies +~~~~~~~~~~~~~~~~~~~~~~~ + .. automodule:: pyramid.authentication .. autoclass:: AuthTktAuthenticationPolicy @@ -11,3 +14,10 @@ .. autoclass:: RemoteUserAuthenticationPolicy +Helper Classes +~~~~~~~~~~~~~~ + + .. autoclass:: AuthTktCookieHelper + + + diff --git a/docs/api/security.rst b/docs/api/security.rst index 4acf5fe4d..de249355d 100644 --- a/docs/api/security.rst +++ b/docs/api/security.rst @@ -10,6 +10,8 @@ Authentication API Functions .. autofunction:: authenticated_userid +.. autofunction:: unauthenticated_userid + .. autofunction:: effective_principals .. autofunction:: forget diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 1d6941283..df14fb440 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -895,9 +895,9 @@ Pyramid Applications are Extensible; I Don't Believe In Application Extensibilit Any :app:`Pyramid` application written obeying certain constraints is *extensible*. This feature is discussed in the :app:`Pyramid` documentation -chapters named :ref:`extending_chapter` and :ref:`advconf_narr`. It is made -possible by the use of the :term:`Zope Component Architecture` and within -:app:`Pyramid`. +chapters named :ref:`extending_chapter` and :ref:`advconfig_narr`. It is +made possible by the use of the :term:`Zope Component Architecture` and +within :app:`Pyramid`. "Extensible", in this context, means: @@ -1018,7 +1018,7 @@ Challenge :app:`Pyramid` performs automatic authorization checks only at :term:`view` execution time. Zope 3 wraps context objects with a `security proxy -<http://wiki.zope.org/zope3/WhatAreSecurityProxies>`, which causes Zope 3 to +<http://wiki.zope.org/zope3/WhatAreSecurityProxies>`_, which causes Zope 3 to do also security checks during attribute access. I like this, because it means: diff --git a/docs/glossary.rst b/docs/glossary.rst index 49d273197..5deb9f5c6 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -834,7 +834,7 @@ Glossary :meth:`pyramid.config.Configurator.add_route` and :meth:`pyramid.config.Configurator.add_view` to make it more convenient to register a collection of views as a single class when - using :term:`url dispatch`. See also :ref:`handlers_chapter`. + using :term:`url dispatch`. See also :ref:`views_chapter`. Deployment settings Deployment settings are settings passed to the :term:`Configurator` as a @@ -850,6 +850,11 @@ Glossary WSGI middleware which can display debuggable traceback information in the browser when an exception is raised by a Pyramid application. See http://pypi.python.org/pypi/WebError . - + view mapper + + A view mapper is a class which implements the + :class:`pyramid.interfaces.IViewMapperFactory` interface, which performs + view argument and return value mapping. This is a plug point for + extension builders, not normally used by "civilians". diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 55a2711f3..5e84a4fa7 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -910,6 +910,8 @@ example. See :ref:`testing_chapter` for more information about writing :app:`Pyramid` unit tests. +.. _modifying_package_structure: + Modifying Package Structure ---------------------------- @@ -958,12 +960,14 @@ To this: .. code-block:: python :linenos: - config.add_view('myproject.views.blogs.my_view', + config.add_view('myproject.views.blog.my_view', renderer='myproject:templates/mytemplate.pt') You can then continue to add files to the ``views`` directory, and refer to views or handler classes/functions within those files via the dotted name -passed as the first argument to ``add_view``. For example: +passed as the first argument to ``add_view``. For example, if you added a +file named ``anothermodule.py`` to the ``views`` subdirectory, and added a +view callable named ``my_view`` to it: .. code-block:: python :linenos: diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 437b823e9..7ef8e1923 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -628,7 +628,7 @@ application's configuration section, e.g.: .. code-block:: ini :linenos: - [app:main] + [app:MyProject] use = egg:MyProject#app debug_templates = true diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index 334fde814..1a8d24499 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -10,7 +10,7 @@ def main(global_config, **settings): initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') - config.add_route('home', '/', view='tutorial.views.view_wiki') + config.add_route('view_wiki', '/', view='tutorial.views.view_wiki') config.add_route('view_page', '/{pagename}', view='tutorial.views.view_page', view_renderer='tutorial:templates/view.pt') diff --git a/docs/zcml/handler.rst b/docs/zcml/handler.rst index 01d442ab6..64aac7e78 100644 --- a/docs/zcml/handler.rst +++ b/docs/zcml/handler.rst @@ -155,4 +155,4 @@ You can also add a :term:`route configuration` via: See Also ~~~~~~~~ -See also :ref:`handlers_chapter`. +See also :ref:`views_chapter`. diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 86d725bcf..bf08b519a 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -17,7 +17,7 @@ from pyramid.security import Everyone class CallbackAuthenticationPolicy(object): """ Abstract class """ def authenticated_userid(self, request): - userid = self._get_userid(request) + userid = self.unauthenticated_userid(request) if userid is None: return None if self.callback is None: @@ -27,7 +27,7 @@ class CallbackAuthenticationPolicy(object): def effective_principals(self, request): effective_principals = [Everyone] - userid = self._get_userid(request) + userid = self.unauthenticated_userid(request) if userid is None: return effective_principals if self.callback is None: @@ -89,6 +89,12 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): if self.callback(identity, request) is not None: # is not None! return identity['repoze.who.userid'] + def unauthenticated_userid(self, request): + identity = self._get_identity(request) + if identity is None: + return None + return identity['repoze.who.userid'] + def effective_principals(self, request): effective_principals = [Everyone] identity = self._get_identity(request) @@ -147,7 +153,7 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): self.environ_key = environ_key self.callback = callback - def _get_userid(self, request): + def unauthenticated_userid(self, request): return request.environ.get(self.environ_key) def remember(self, request, principal, **kw): @@ -264,7 +270,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): ) self.callback = callback - def _get_userid(self, request): + def unauthenticated_userid(self, request): result = self.cookie.identify(request) if result: return result['userid'] @@ -285,6 +291,12 @@ def b64decode(v): EXPIRE = object() class AuthTktCookieHelper(object): + """ + A helper class for use in third-party authentication policy + implementations. See + :class:`pyramid.authentication.AuthTktAuthenticationPolicy' for the + meanings of the constructor arguments. + """ auth_tkt = auth_tkt # for tests now = None # for tests @@ -356,6 +368,8 @@ class AuthTktCookieHelper(object): return cookies def identify(self, request): + """ Return a dictionary with authentication information, or ``None`` + if no valid auth_tkt is attached to ``request``""" environ = request.environ cookies = get_cookies(environ) cookie = cookies.get(self.cookie_name) @@ -411,11 +425,14 @@ class AuthTktCookieHelper(object): return identity def forget(self, request): - # return a set of expires Set-Cookie headers + """ Return a set of expires Set-Cookie headers, which will destroy + any existing auth_tkt cookie when attached to a response""" environ = request.environ return self._get_cookies(environ, '', max_age=EXPIRE) def remember(self, request, userid, max_age=None): + """ Return a set of Set-Cookie headers; when set into a response, + these headers will represent a valid authentication ticket.""" max_age = max_age or self.max_age environ = request.environ diff --git a/pyramid/config.py b/pyramid/config.py index ee34adae1..8a908725f 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -246,7 +246,13 @@ class Configurator(object): ``autocommit`` is ``True``. If a conflict is detected a ``ConfigurationConflictError`` will be raised. Calling :meth:`pyramid.config.Configurator.make_wsgi_app` always implies a final - commit.""" + commit. + + If ``default_view_mapper`` is passed, it will be used as the default + view mapper factory for view configurations that don't otherwise specify + one (see :class:`pyramid.interfaces.IViewMapperFactory`). + If a default_view_mapper is not passed, a superdefault view mapper will + be used. """ manager = manager # for testing injection venusian = venusian # for testing injection @@ -267,6 +273,7 @@ class Configurator(object): renderer_globals_factory=None, default_permission=None, session_factory=None, + default_view_mapper=None, autocommit=False, ): if package is None: @@ -292,6 +299,7 @@ class Configurator(object): renderer_globals_factory=renderer_globals_factory, default_permission=default_permission, session_factory=session_factory, + default_view_mapper=default_view_mapper, ) def _set_settings(self, mapping): @@ -343,8 +351,10 @@ class Configurator(object): def _derive_view(self, view, permission=None, predicates=(), attr=None, renderer=None, wrapper_viewname=None, viewname=None, accept=None, order=MAX_ORDER, - phash=DEFAULT_PHASH): + phash=DEFAULT_PHASH, decorator=None, + view_mapper=None): view = self.maybe_dotted(view) + view_mapper = self.maybe_dotted(view_mapper) if isinstance(renderer, basestring): renderer = RendererHelper(name=renderer, package=self.package, registry = self.registry) @@ -354,18 +364,21 @@ class Configurator(object): renderer = RendererHelper(name=None, package=self.package, registry=self.registry) - deriver = ViewDeriver( - registry=self.registry, - permission=permission, - predicates=predicates, - attr=attr, - renderer=renderer, - wrapper_viewname=wrapper_viewname, - viewname=viewname, - accept=accept, - order=order, - phash=phash, - package=self.package) + + deriver = ViewDeriver(registry=self.registry, + permission=permission, + predicates=predicates, + attr=attr, + renderer=renderer, + wrapper_viewname=wrapper_viewname, + viewname=viewname, + accept=accept, + order=order, + phash=phash, + package=self.package, + view_mapper=view_mapper, + decorator=decorator) + return deriver(view) def _override(self, package, path, override_package, override_prefix, @@ -596,9 +609,8 @@ class Configurator(object): authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None, locale_negotiator=None, request_factory=None, - renderer_globals_factory=None, - default_permission=None, - session_factory=None): + renderer_globals_factory=None, default_permission=None, + session_factory=None, default_view_mapper=None): """ When you pass a non-``None`` ``registry`` argument to the :term:`Configurator` constructor, no initial 'setup' is performed against the registry. This is because the registry you pass in may @@ -644,7 +656,13 @@ class Configurator(object): self.set_default_permission(default_permission) if session_factory is not None: self.set_session_factory(session_factory) + # commit before adding default_view_mapper, as the + # default_exceptionresponse_view above requires the superdefault view + # mapper self.commit() + if default_view_mapper is not None: + self.set_view_mapper(default_view_mapper) + self.commit() # getSiteManager is a unit testing dep injection def hook_zca(self, getSiteManager=None): @@ -925,7 +943,7 @@ class Configurator(object): Any extra keyword arguments are passed along to ``add_route``. - See :ref:`handlers_chapter` for more explanatory documentation. + See :ref:`views_chapter` for more explanatory documentation. This method returns the result of add_route.""" handler = self.maybe_dotted(handler) @@ -1140,11 +1158,11 @@ class Configurator(object): decorator A function which will be used to decorate the registered - :term:`view callable`. The decorator function will be - called with the view callable as a single argument, and it - must return a replacement view callable which accepts the - same arguments and returns the same type of values as the - original function. + :term:`view callable`. The decorator function will be called with + the view callable as a single argument. The view callable it is + passed will accept ``(context, request)`. The decorator must + return a replacement view callable which also accepts ``(context, + request)``. Predicate Arguments @@ -1285,20 +1303,19 @@ class Configurator(object): view_mapper - A class implementing the - :class:`pyramid.interfaces.IViewMapperFactory` interface, which - performs view argument and response mapping. By default it is - ``None``, which indicates that the view should use the default view - mapper. This plug-point is useful for Pyramid extension - developers, but it's not very useful for 'civilians' who are - just developing stock Pyramid applications. Pay no attention to - the man behind the curtain. + A Python object or :term:`dotted Python name` which refers to a + :term:`view mapper`, or ``None``. By default it is ``None``, which + indicates that the view should use the default view mapper. This + plug-point is useful for Pyramid extension developers, but it's not + very useful for 'civilians' who are just developing stock Pyramid + applications. Pay no attention to the man behind the curtain. """ view = self.maybe_dotted(view) context = self.maybe_dotted(context) for_ = self.maybe_dotted(for_) containment = self.maybe_dotted(containment) + view_mapper = self.maybe_dotted(view_mapper) if not view: if renderer: @@ -1369,19 +1386,21 @@ class Configurator(object): # intent: will be None if no default permission is registered permission = self.registry.queryUtility(IDefaultPermission) - # NO_PERMISSION_REQUIRED handled by _secure_view - derived_view = ViewDeriver(registry=self.registry, - permission=permission, - predicates=predicates, - attr=attr, - renderer=renderer, - wrapper_viewname=wrapper, - viewname=name, - accept=accept, - order=order, - phash=phash, - decorator=decorator, - view_mapper=view_mapper)(view) + # __no_permission_required__ handled by _secure_view + deriver = ViewDeriver(registry=self.registry, + permission=permission, + predicates=predicates, + attr=attr, + renderer=renderer, + wrapper_viewname=wrapper, + viewname=name, + accept=accept, + order=order, + phash=phash, + package=self.package, + view_mapper=view_mapper, + decorator=decorator) + derived_view = deriver(view) registered = self.registry.adapters.registered @@ -2163,6 +2182,29 @@ class Configurator(object): self.action(IDefaultPermission, None) @action_method + def set_view_mapper(self, mapper): + """ + Setting a :term:`view mapper` makes it possible to make use of + :term:`view callable` objects which implement different call + signatures than the ones supported by :app:`Pyramid` as described in + its narrative documentation. + + The ``mapper`` should argument be an object implementing + :class:`pyramid.interfaces.IViewMapperFactory` or a :term:`dotted + Python name` to such an object. + + The provided ``mapper`` will become the default view mapper to be + used by all subsequent :term:`view configuration` registrations, as + if you had passed a ``default_view_mapper`` argument to the + :class:`pyramid.config.Configurator` constructor. + + See also :ref:`using_an_alternate_view_mapper`. + """ + mapper = self.maybe_dotted(mapper) + self.registry.registerUtility(mapper, IViewMapperFactory) + self.action(IViewMapperFactory, None) + + @action_method def set_session_factory(self, session_factory): """ Configure the application with a :term:`session factory`. If @@ -2695,6 +2737,10 @@ def wraps_view(wrapped): def preserve_view_attrs(view, wrapped_view): if wrapped_view is view: return view + original_view = getattr(view, '__original_view__', None) + if original_view is None: + original_view = view + wrapped_view.__original_view__ = original_view wrapped_view.__module__ = view.__module__ wrapped_view.__doc__ = view.__doc__ try: @@ -2734,19 +2780,30 @@ class ViewDeriver(object): self.logger = self.registry.queryUtility(IDebugLogger) def __call__(self, view): - mapper = self.kw.get('view_mapper') - if mapper is None: - mapper = DefaultViewMapper - view = mapper(**self.kw)(view) return self.attr_wrapped_view( self.predicated_view( self.authdebug_view( self.secured_view( - self.owrap_view( - view))))) + self.owrapped_view( + self.decorated_view( + self.rendered_view( + self.mapped_view(view)))))))) + + @wraps_view + def mapped_view(self, view): + mapper = self.kw.get('view_mapper') + if mapper is None: + mapper = getattr(view, '__view_mapper__', None) + if mapper is None: + mapper = self.registry.queryUtility(IViewMapperFactory) + if mapper is None: + mapper = DefaultViewMapper + + mapped_view = mapper(**self.kw)(view) + return mapped_view @wraps_view - def owrap_view(self, view): + def owrapped_view(self, view): wrapper_viewname = self.kw.get('wrapper_viewname') viewname = self.kw.get('viewname') if not wrapper_viewname: @@ -2860,29 +2917,64 @@ class ViewDeriver(object): attr_view.__phash__ = phash return attr_view + @wraps_view + def rendered_view(self, view): + wrapped_view = view + static_renderer = self.kw.get('renderer') + if static_renderer is None: + # register a default renderer if you want super-dynamic + # rendering. registering a default renderer will also allow + # override_renderer to work if a renderer is left unspecified for + # a view registration. + return view + + def _rendered_view(context, request): + renderer = static_renderer + response = wrapped_view(context, request) + if not is_response(response): + attrs = getattr(request, '__dict__', {}) + if 'override_renderer' in attrs: + # renderer overridden by newrequest event or other + renderer_name = attrs.pop('override_renderer') + renderer = RendererHelper(name=renderer_name, + package=self.kw.get('package'), + registry = self.kw['registry']) + if '__view__' in attrs: + view_inst = attrs.pop('__view__') + else: + view_inst = getattr(wrapped_view, '__original_view__', + wrapped_view) + return renderer.render_view(request, response, view_inst, + context) + return response + + return _rendered_view + + @wraps_view + def decorated_view(self, view): + decorator = self.kw.get('decorator') + if decorator is None: + return view + return decorator(view) + class DefaultViewMapper(object): implements(IViewMapperFactory) def __init__(self, **kw): - self.renderer = kw.get('renderer') self.attr = kw.get('attr') - self.decorator = kw.get('decorator') def __call__(self, view): - decorator = self.decorator if inspect.isclass(view): - view = preserve_view_attrs(view, self.map_class(view)) + view = self.map_class(view) else: - view = preserve_view_attrs(view, self.map_nonclass(view)) - if decorator is not None: - view = preserve_view_attrs(view, decorator(view)) + view = self.map_nonclass(view) return view def map_class(self, view): - ronly = self.requestonly(view) + ronly = requestonly(view, self.attr) if ronly: - mapped_view = self._map_class_requestonly(view) + mapped_view = self.map_class_requestonly(view) else: - mapped_view = self._map_class_native(view) + mapped_view = self.map_class_native(view) return mapped_view def map_nonclass(self, view): @@ -2890,47 +2982,41 @@ class DefaultViewMapper(object): # view unless it actually requires wrapping (to avoid function call # overhead). mapped_view = view - ronly = self.requestonly(view) + ronly = requestonly(view, self.attr) if ronly: - mapped_view = self._map_nonclass_requestonly(view) + mapped_view = self.map_nonclass_requestonly(view) elif self.attr: - mapped_view = self._map_nonclass_attr(view) - elif self.renderer is not None: - mapped_view = self._map_nonclass_rendered(view) + mapped_view = self.map_nonclass_attr(view) return mapped_view - def _map_class_requestonly(self, view): + def map_class_requestonly(self, view): # its a class that has an __init__ which only accepts request attr = self.attr def _class_requestonly_view(context, request): inst = view(request) + request.__view__ = inst if attr is None: response = inst() else: response = getattr(inst, attr)() - if self.renderer is not None and not is_response(response): - response = self.renderer.render_view(request, response, view, - context) return response return _class_requestonly_view - def _map_class_native(self, view): + def map_class_native(self, view): # its a class that has an __init__ which accepts both context and # request attr = self.attr def _class_view(context, request): inst = view(context, request) + request.__view__ = inst if attr is None: response = inst() else: response = getattr(inst, attr)() - if self.renderer is not None and not is_response(response): - response = self.renderer.render_view(request, response, view, - context) return response return _class_view - def _map_nonclass_requestonly(self, view): + def map_nonclass_requestonly(self, view): # its a function that has a __call__ which accepts only a single # request argument attr = self.attr @@ -2939,86 +3025,58 @@ class DefaultViewMapper(object): response = view(request) else: response = getattr(view, attr)(request) - if self.renderer is not None and not is_response(response): - response = self.renderer.render_view(request, response, view, - context) return response return _requestonly_view - def _map_nonclass_attr(self, view): + def map_nonclass_attr(self, view): # its a function that has a __call__ which accepts both context and # request, but still has an attr - attr = self.attr def _attr_view(context, request): - response = getattr(view, attr)(context, request) - if self.renderer is not None and not is_response(response): - response = self.renderer.render_view(request, response, view, - context) + response = getattr(view, self.attr)(context, request) return response return _attr_view - def _map_nonclass_rendered(self, view): - # it's a function that has a __call__ that accepts both context and - # request, but requires rendering - def _rendered_view(context, request): - response = view(context, request) - if self.renderer is not None and not is_response(response): - response = self.renderer.render_view(request, response, view, - context) - return response - return _rendered_view - - def requestonly(self, view): - attr = self.attr - if attr is None: - attr = '__call__' - if inspect.isfunction(view): - fn = view - elif inspect.isclass(view): - try: - fn = view.__init__ - except AttributeError: - return False - else: - try: - fn = getattr(view, attr) - except AttributeError: - return False - +def requestonly(view, attr=None): + if attr is None: + attr = '__call__' + if inspect.isfunction(view): + fn = view + elif inspect.isclass(view): try: - argspec = inspect.getargspec(fn) - except TypeError: + fn = view.__init__ + except AttributeError: return False - - args = argspec[0] - defaults = argspec[3] - - if hasattr(fn, 'im_func'): - # it's an instance method - if not args: - return False - args = args[1:] - if not args: + else: + try: + fn = getattr(view, attr) + except AttributeError: return False - if len(args) == 1: - return True + try: + argspec = inspect.getargspec(fn) + except TypeError: + return False - elif args[0] == 'request': - if len(args) - len(defaults) == 1: - return True + args = argspec[0] + defaults = argspec[3] + if hasattr(fn, 'im_func'): + # it's an instance method + if not args: + return False + args = args[1:] + if not args: return False + if len(args) == 1: + return True -def isexception(o): - if IInterface.providedBy(o): - if IException.isEqualOrExtendedBy(o): + elif args[0] == 'request': + if len(args) - len(defaults) == 1: return True - return ( - isinstance(o, Exception) or - (inspect.isclass(o) and (issubclass(o, Exception))) - ) + + return False + class ActionPredicate(object): action_name = 'action' @@ -3065,20 +3123,18 @@ def translator(msg): localizer = get_localizer(request) return localizer.translate(msg) -# b/c -def _map_view(view, registry, attr=None, renderer=None): - return DefaultViewMapper(registry=registry, attr=attr, - renderer=renderer)(view) - -# b/c -def requestonly(view, attr=None): - """ Return true of the class or callable accepts only a request argument, - as opposed to something that accepts context, request """ - return DefaultViewMapper(attr=attr).requestonly(view) - def is_response(ob): if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and hasattr(ob, 'status') ): return True return False +def isexception(o): + if IInterface.providedBy(o): + if IException.isEqualOrExtendedBy(o): + return True + return ( + isinstance(o, Exception) or + (inspect.isclass(o) and (issubclass(o, Exception))) + ) + diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 10a324b28..b109df77e 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -155,8 +155,19 @@ class IRouteRequest(Interface): class IAuthenticationPolicy(Interface): """ An object representing a Pyramid authentication policy. """ def authenticated_userid(request): - """ Return the authenticated userid or ``None`` if no - authenticated userid can be found. """ + """ Return the authenticated userid or ``None`` if no authenticated + userid can be found. This method of the policy should ensure that a + record exists in whatever persistent store is used related to the + user (the user should not have been deleted); if a record associated + with the current id does not exist in a persistent store, it should + return ``None``.""" + + def unauthenticated_userid(request): + """ Return the *unauthenticated* userid. This method performs the + same duty as ``authenticated_userid`` but is permitted to return the + userid based only on data present in the request; it neednt (and + shouldn't) check any persistent store to ensure that the user record + related to the request userid exists.""" def effective_principals(request): """ Return a sequence representing the effective principals @@ -501,11 +512,11 @@ class ISession(Interface): :meth:`pyramid.interfaces.ISesssion.flash` """ - def new_csrf_token(self): + def new_csrf_token(): """ Create and set into the session a new, random cross-site request forgery protection token. Return the token. It will be a string.""" - def get_csrf_token(self): + def get_csrf_token(): """ Get the CSRF token previously added to the session via ``new_csrf_token``, and return the token. If no CSRF token exists, the value returned will be ``None``. diff --git a/pyramid/security.py b/pyramid/security.py index 723e87a87..51c0802d5 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -64,6 +64,24 @@ def authenticated_userid(request): return None return policy.authenticated_userid(request) +def unauthenticated_userid(request): + """ Return an object which represents the *claimed* (not verified) user + id of the credentials present in the request. ``None`` if there is no + :term:`authentication policy` in effect or there is no user data + associated with the current request. This differs from + :func:`~pyramid.security.authenticated_userid`, because the effective + authentication policy will not ensure that a record associated with the + userid exists in persistent storage.""" + try: + reg = request.registry + except AttributeError: + reg = get_current_registry() # b/c + + policy = reg.queryUtility(IAuthenticationPolicy) + if policy is None: + return None + return policy.unauthenticated_userid(request) + def effective_principals(request): """ Return the list of 'effective' :term:`principal` identifiers for the ``request``. This will include the userid of the diff --git a/pyramid/testing.py b/pyramid/testing.py index 61bb1843a..15fc385cd 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -44,9 +44,10 @@ def registerDummySecurityPolicy(userid=None, groupids=(), permissive=True): :func:`pyramid.security.authenticated_userid` or :func:`pyramid.security.effective_principals` APIs are used. - This function is most useful when testing code that uses the APIs - named :func:`pyramid.security.has_permission`, + This function is most useful when testing code that uses the APIs named + :func:`pyramid.security.has_permission`, :func:`pyramid.security.authenticated_userid`, + :func:`pyramid.security.unauthenticated_userid`, :func:`pyramid.security.effective_principals`, and :func:`pyramid.security.principals_allowed_by_permission`. @@ -332,6 +333,9 @@ class DummySecurityPolicy(object): def authenticated_userid(self, request): return self.userid + def unauthenticated_userid(self, request): + return self.userid + def effective_principals(self, request): effective_principals = [Everyone] if self.userid: diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index d9d0c2c97..49d655466 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -18,11 +18,22 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase): from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne()) + def test_unauthenticated_userid_returns_None(self): + request = DummyRequest({}) + policy = self._makeOne() + self.assertEqual(policy.unauthenticated_userid(request), None) + + def test_unauthenticated_userid(self): + request = DummyRequest( + {'repoze.who.identity':{'repoze.who.userid':'fred'}}) + policy = self._makeOne() + self.assertEqual(policy.unauthenticated_userid(request), 'fred') + def test_authenticated_userid_None(self): request = DummyRequest({}) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) - + def test_authenticated_userid(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred'}}) @@ -132,6 +143,16 @@ class TestRemoteUserAuthenticationPolicy(unittest.TestCase): from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne()) + def test_unauthenticated_userid_returns_None(self): + request = DummyRequest({}) + policy = self._makeOne() + self.assertEqual(policy.unauthenticated_userid(request), None) + + def test_unauthenticated_userid(self): + request = DummyRequest({'REMOTE_USER':'fred'}) + policy = self._makeOne() + self.assertEqual(policy.unauthenticated_userid(request), 'fred') + def test_authenticated_userid_None(self): request = DummyRequest({}) policy = self._makeOne() @@ -196,6 +217,16 @@ class TestAutkTktAuthenticationPolicy(unittest.TestCase): from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne(None, None)) + def test_unauthenticated_userid_returns_None(self): + request = DummyRequest({}) + policy = self._makeOne(None, None) + self.assertEqual(policy.unauthenticated_userid(request), None) + + def test_unauthenticated_userid(self): + request = DummyRequest({'REMOTE_USER':'fred'}) + policy = self._makeOne(None, {'userid':'fred'}) + self.assertEqual(policy.unauthenticated_userid(request), 'fred') + def test_authenticated_userid_no_cookie_identity(self): request = DummyRequest({}) policy = self._makeOne(None, None) diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index b2fa0e329..1632a4e5c 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -63,27 +63,11 @@ class ConfiguratorTests(unittest.TestCase): config.registry.registerHandler(subscriber, (event_iface,)) return L - def _registerLogger(self, config): - from pyramid.interfaces import IDebugLogger - logger = DummyLogger() - config.registry.registerUtility(logger, IDebugLogger) - return logger - def _makeRequest(self, config): request = DummyRequest() request.registry = config.registry return request - def _registerSecurityPolicy(self, config, permissive): - from pyramid.interfaces import IAuthenticationPolicy - from pyramid.interfaces import IAuthorizationPolicy - policy = DummySecurityPolicy(permissive) - config.registry.registerUtility(policy, IAuthenticationPolicy) - config.registry.registerUtility(policy, IAuthorizationPolicy) - - def _registerSettings(self, config, **settings): - config.registry.settings = settings - def test_ctor_no_registry(self): import sys from pyramid.interfaces import ISettings @@ -196,6 +180,13 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(session_factory='factory') self.assertEqual(config.registry.getUtility(ISessionFactory), 'factory') + def test_ctor_default_view_mapper(self): + from pyramid.interfaces import IViewMapperFactory + mapper = object() + config = self._makeOne(default_view_mapper=mapper) + self.assertEqual(config.registry.getUtility(IViewMapperFactory), + mapper) + def test_with_package_module(self): from pyramid.tests import test_configuration import pyramid.tests @@ -790,8 +781,10 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.add_view(view=view) wrapper = self._getViewCallable(config) - result = wrapper(None, None) + request = self._makeRequest(config) + result = wrapper(None, request) self.assertEqual(result, 'OK') + self.assertEqual(request.__view__.__class__, view) def test_add_view_as_oldstyle_class_requestonly(self): class view: @@ -803,8 +796,11 @@ class ConfiguratorTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.add_view(view=view) wrapper = self._getViewCallable(config) - result = wrapper(None, None) + + request = self._makeRequest(config) + result = wrapper(None, request) self.assertEqual(result, 'OK') + self.assertEqual(request.__view__.__class__, view) def test_add_view_context_as_class(self): from zope.interface import implementedBy @@ -1427,8 +1423,6 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(result.settings, settings) def test_add_view_with_default_renderer(self): - import pyramid.tests - from pyramid.interfaces import ISettings class view(object): def __init__(self, context, request): self.request = request @@ -2340,7 +2334,8 @@ class ConfiguratorTests(unittest.TestCase): request_type = self._getRouteRequestIface(config, 'name') wrapper = self._getViewCallable(config, None, request_type) self._assertRoute(config, 'name', 'path') - self.assertEqual(wrapper(None, None), 'OK') + request = self._makeRequest(config) + self.assertEqual(wrapper(None, request), 'OK') def test_add_route_with_view_renderer_alias(self): config = self._makeOne(autocommit=True) @@ -2437,6 +2432,52 @@ class ConfiguratorTests(unittest.TestCase): else: # pragma: no cover raise AssertionError + def test_derive_view_function(self): + def view(request): + return 'OK' + config = self._makeOne() + result = config.derive_view(view) + self.failIf(result is view) + self.assertEqual(result(None, None), 'OK') + + def test_derive_view_dottedname(self): + config = self._makeOne() + result = config.derive_view( + 'pyramid.tests.test_config.dummy_view') + self.failIf(result is dummy_view) + self.assertEqual(result(None, None), 'OK') + + def test_derive_view_with_default_renderer_no_explicit_renderer(self): + config = self._makeOne() + class moo(object): + def __init__(self, view): + pass + def __call__(self, *arg, **kw): + return 'moo' + config.add_renderer(None, moo) + def view(request): + return 'OK' + result = config.derive_view(view) + self.failIf(result is view) + self.assertEqual(result(None, None).body, 'moo') + + def test_derive_view_with_default_renderer_with_explicit_renderer(self): + class moo(object): pass + class foo(object): + def __init__(self, view): + pass + def __call__(self, *arg, **kw): + return 'foo' + def view(request): + return 'OK' + config = self._makeOne() + config.add_renderer(None, moo) + config.add_renderer('foo', foo) + result = config.derive_view(view, renderer='foo') + self.failIf(result is view) + request = self._makeRequest(config) + self.assertEqual(result(None, request).body, 'foo') + def test__override_not_yet_registered(self): from pyramid.interfaces import IPackageOverrides package = DummyPackage('package') @@ -2633,6 +2674,22 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(IDefaultPermission), 'view') + def test_add_view_mapper(self): + from pyramid.interfaces import IViewMapperFactory + config = self._makeOne(autocommit=True) + mapper = object() + config.set_view_mapper(mapper) + result = config.registry.getUtility(IViewMapperFactory) + self.assertEqual(result, mapper) + + def test_add_view_mapper_dottedname(self): + from pyramid.interfaces import IViewMapperFactory + config = self._makeOne(autocommit=True) + config.set_view_mapper('pyramid.tests.test_config') + result = config.registry.getUtility(IViewMapperFactory) + from pyramid.tests import test_config + self.assertEqual(result, test_config) + def test_set_session_factory(self): from pyramid.interfaces import ISessionFactory config = self._makeOne(autocommit=True) @@ -2680,404 +2737,6 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(config.registry.getUtility(ITranslationDirectories), [locale]) - def test_derive_view_function(self): - def view(request): - return 'OK' - config = self._makeOne() - result = config.derive_view(view) - self.failIf(result is view) - self.assertEqual(result(None, None), 'OK') - - def test_derive_view_dottedname(self): - config = self._makeOne() - result = config.derive_view( - 'pyramid.tests.test_config.dummy_view') - self.failIf(result is dummy_view) - self.assertEqual(result(None, None), 'OK') - - def test_derive_view_with_renderer(self): - def view(request): - return 'OK' - config = self._makeOne(autocommit=True) - class moo(object): - def __init__(self, *arg, **kw): - pass - def __call__(self, *arg, **kw): - return 'moo' - config.add_renderer('moo', moo) - result = config.derive_view(view, renderer='moo') - self.failIf(result is view) - self.assertEqual(result(None, None).body, 'moo') - - def test_derive_view_with_default_renderer_no_explicit_renderer(self): - def view(request): - return 'OK' - config = self._makeOne(autocommit=True) - class moo(object): - def __init__(self, *arg, **kw): - pass - def __call__(self, *arg, **kw): - return 'moo' - config.add_renderer(None, moo) - result = config.derive_view(view) - self.failIf(result is view) - self.assertEqual(result(None, None).body, 'moo') - - def test_derive_view_with_default_renderer_with_explicit_renderer(self): - def view(request): - return 'OK' - config = self._makeOne(autocommit=True) - class moo(object): pass - class foo(object): - def __init__(self, *arg, **kw): - pass - def __call__(self, *arg, **kw): - return 'foo' - config.add_renderer(None, moo) - config.add_renderer('foo', foo) - result = config.derive_view(view, renderer='foo') - self.failIf(result is view) - self.assertEqual(result(None, None).body, 'foo') - - def test_derive_view_class_without_attr(self): - class View(object): - def __init__(self, request): - pass - def __call__(self): - return 'OK' - config = self._makeOne() - result = config.derive_view(View) - self.assertEqual(result(None, None), 'OK') - - def test_derive_view_class_with_attr(self): - class View(object): - def __init__(self, request): - pass - def another(self): - return 'OK' - config = self._makeOne() - result = config.derive_view(View, attr='another') - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_as_function_context_and_request(self): - def view(context, request): - return 'OK' - config = self._makeOne() - result = config._derive_view(view) - self.failUnless(result is view) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(view(None, None), 'OK') - - def test__derive_view_as_function_requestonly(self): - def view(request): - return 'OK' - config = self._makeOne() - result = config._derive_view(view) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_as_newstyle_class_context_and_request(self): - class view(object): - def __init__(self, context, request): - pass - def __call__(self): - return 'OK' - config = self._makeOne() - result = config._derive_view(view) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_as_newstyle_class_requestonly(self): - class view(object): - def __init__(self, context, request): - pass - def __call__(self): - return 'OK' - config = self._makeOne() - result = config._derive_view(view) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_as_oldstyle_class_context_and_request(self): - class view: - def __init__(self, context, request): - pass - def __call__(self): - return 'OK' - config = self._makeOne() - result = config._derive_view(view) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_as_oldstyle_class_requestonly(self): - class view: - def __init__(self, context, request): - pass - def __call__(self): - return 'OK' - config = self._makeOne() - result = config._derive_view(view) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_as_instance_context_and_request(self): - class View: - def __call__(self, context, request): - return 'OK' - view = View() - config = self._makeOne() - result = config._derive_view(view) - self.failUnless(result is view) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_as_instance_requestonly(self): - class View: - def __call__(self, request): - return 'OK' - view = View() - config = self._makeOne() - result = config._derive_view(view) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.failUnless('instance' in result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - self.assertEqual(result(None, None), 'OK') - - def test__derive_view_with_debug_authorization_no_authpol(self): - view = lambda *arg: 'OK' - config = self._makeOne() - self._registerSettings(config, - debug_authorization=True, reload_templates=True) - logger = self._registerLogger(config) - result = config._derive_view(view, permission='view') - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - request = self._makeRequest(config) - request.view_name = 'view_name' - request.url = 'url' - self.assertEqual(result(None, request), 'OK') - self.assertEqual(len(logger.messages), 1) - self.assertEqual(logger.messages[0], - "debug_authorization of url url (view name " - "'view_name' against context None): Allowed " - "(no authorization policy in use)") - - def test__derive_view_with_debug_authorization_no_permission(self): - view = lambda *arg: 'OK' - config = self._makeOne() - self._registerSettings(config, - debug_authorization=True, reload_templates=True) - self._registerSecurityPolicy(config, True) - logger = self._registerLogger(config) - result = config._derive_view(view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - request = self._makeRequest(config) - request.view_name = 'view_name' - request.url = 'url' - self.assertEqual(result(None, request), 'OK') - self.assertEqual(len(logger.messages), 1) - self.assertEqual(logger.messages[0], - "debug_authorization of url url (view name " - "'view_name' against context None): Allowed (" - "no permission registered)") - - def test__derive_view_debug_auth_permission_authpol_permitted(self): - view = lambda *arg: 'OK' - config = self._makeOne() - self._registerSettings(config, debug_authorization=True, - reload_templates=True) - logger = self._registerLogger(config) - self._registerSecurityPolicy(config, True) - result = config._derive_view(view, permission='view') - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result.__call_permissive__, view) - request = self._makeRequest(config) - request.view_name = 'view_name' - request.url = 'url' - self.assertEqual(result(None, request), 'OK') - self.assertEqual(len(logger.messages), 1) - self.assertEqual(logger.messages[0], - "debug_authorization of url url (view name " - "'view_name' against context None): True") - - def test__derive_view_debug_auth_permission_authpol_denied(self): - from pyramid.exceptions import Forbidden - view = lambda *arg: 'OK' - config = self._makeOne() - self._registerSettings(config, - debug_authorization=True, reload_templates=True) - logger = self._registerLogger(config) - self._registerSecurityPolicy(config, False) - result = config._derive_view(view, permission='view') - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result.__call_permissive__, view) - request = self._makeRequest(config) - request.view_name = 'view_name' - request.url = 'url' - self.assertRaises(Forbidden, result, None, request) - self.assertEqual(len(logger.messages), 1) - self.assertEqual(logger.messages[0], - "debug_authorization of url url (view name " - "'view_name' against context None): False") - - def test__derive_view_debug_auth_permission_authpol_denied2(self): - view = lambda *arg: 'OK' - config = self._makeOne() - self._registerSettings(config, - debug_authorization=True, reload_templates=True) - self._registerLogger(config) - self._registerSecurityPolicy(config, False) - result = config._derive_view(view, permission='view') - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - request = self._makeRequest(config) - request.view_name = 'view_name' - request.url = 'url' - permitted = result.__permitted__(None, None) - self.assertEqual(permitted, False) - - def test__derive_view_debug_auth_permission_authpol_overridden(self): - view = lambda *arg: 'OK' - config = self._makeOne() - self._registerSettings(config, - debug_authorization=True, reload_templates=True) - logger = self._registerLogger(config) - self._registerSecurityPolicy(config, False) - result = config._derive_view(view, - permission='__no_permission_required__') - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.failIf(hasattr(result, '__call_permissive__')) - request = self._makeRequest(config) - request.view_name = 'view_name' - request.url = 'url' - self.assertEqual(result(None, request), 'OK') - self.assertEqual(len(logger.messages), 1) - self.assertEqual(logger.messages[0], - "debug_authorization of url url (view name " - "'view_name' against context None): False") - - def test__derive_view_with_predicates_all(self): - view = lambda *arg: 'OK' - predicates = [] - def predicate1(context, request): - predicates.append(True) - return True - def predicate2(context, request): - predicates.append(True) - return True - config = self._makeOne() - result = config._derive_view(view, predicates=[predicate1, predicate2]) - request = self._makeRequest(config) - request.method = 'POST' - next = result(None, None) - self.assertEqual(next, 'OK') - self.assertEqual(predicates, [True, True]) - - def test__derive_view_with_predicates_checker(self): - view = lambda *arg: 'OK' - predicates = [] - def predicate1(context, request): - predicates.append(True) - return True - def predicate2(context, request): - predicates.append(True) - return True - config = self._makeOne() - result = config._derive_view(view, predicates=[predicate1, predicate2]) - request = self._makeRequest(config) - request.method = 'POST' - next = result.__predicated__(None, None) - self.assertEqual(next, True) - self.assertEqual(predicates, [True, True]) - - def test__derive_view_with_predicates_notall(self): - from pyramid.exceptions import NotFound - view = lambda *arg: 'OK' - predicates = [] - def predicate1(context, request): - predicates.append(True) - return True - def predicate2(context, request): - predicates.append(True) - return False - config = self._makeOne() - result = config._derive_view(view, predicates=[predicate1, predicate2]) - request = self._makeRequest(config) - request.method = 'POST' - self.assertRaises(NotFound, result, None, None) - self.assertEqual(predicates, [True, True]) - - def test__derive_view_with_wrapper_viewname(self): - from webob import Response - from pyramid.interfaces import IView - from pyramid.interfaces import IViewClassifier - inner_response = Response('OK') - def inner_view(context, request): - return inner_response - def outer_view(context, request): - self.assertEqual(request.wrapped_response, inner_response) - self.assertEqual(request.wrapped_body, inner_response.body) - self.assertEqual(request.wrapped_view, inner_view) - return Response('outer ' + request.wrapped_body) - config = self._makeOne() - config.registry.registerAdapter( - outer_view, (IViewClassifier, None, None), IView, 'owrap') - result = config._derive_view(inner_view, viewname='inner', - wrapper_viewname='owrap') - self.failIf(result is inner_view) - self.assertEqual(inner_view.__module__, result.__module__) - self.assertEqual(inner_view.__doc__, result.__doc__) - request = self._makeRequest(config) - request.registry = config.registry - response = result(None, request) - self.assertEqual(response.body, 'outer OK') - - def test__derive_view_with_wrapper_viewname_notfound(self): - from webob import Response - inner_response = Response('OK') - def inner_view(context, request): - return inner_response - config = self._makeOne() - request = self._makeRequest(config) - request.registry = config.registry - wrapped = config._derive_view( - inner_view, viewname='inner', wrapper_viewname='owrap') - self.assertRaises(ValueError, wrapped, None, request) - def test_override_asset_samename(self): from pyramid.exceptions import ConfigurationError config = self._makeOne() @@ -3557,348 +3216,841 @@ class ConfiguratorTests(unittest.TestCase): for confinst in conflict: yield confinst[2] -class Test__map_view(unittest.TestCase): +class TestViewDeriver(unittest.TestCase): def setUp(self): - from pyramid.registry import Registry - self.registry = Registry() - testing.setUp(registry=self.registry) + self.config = testing.setUp() def tearDown(self): - del self.registry - testing.tearDown() - - def _registerRenderer(self, typ='.txt'): - from pyramid.interfaces import IRendererFactory - from pyramid.interfaces import ITemplateRenderer - from pyramid.renderers import RendererHelper - from zope.interface import implements - class DummyRenderer: - implements(ITemplateRenderer) - def __init__(self, path): - self.__class__.path = path - def __call__(self, *arg): - return 'Hello!' - self.registry.registerUtility(DummyRenderer, IRendererFactory, name=typ) - renderer = RendererHelper(name='abc' + typ, registry=self.registry) - return renderer - + self.config = None + + def _makeOne(self, **kw): + kw['registry'] = self.config.registry + from pyramid.config import ViewDeriver + return ViewDeriver(**kw) + def _makeRequest(self): request = DummyRequest() - request.registry = self.registry + request.registry = self.config.registry return request - def _callFUT(self, view, **kw): - from pyramid.config import _map_view - return _map_view(view, self.registry, **kw) + def _registerLogger(self): + from pyramid.interfaces import IDebugLogger + logger = DummyLogger() + self.config.registry.registerUtility(logger, IDebugLogger) + return logger - def test__map_view_as_function_context_and_request(self): - def view(context, request): + def _registerSecurityPolicy(self, permissive): + from pyramid.interfaces import IAuthenticationPolicy + from pyramid.interfaces import IAuthorizationPolicy + policy = DummySecurityPolicy(permissive) + self.config.registry.registerUtility(policy, IAuthenticationPolicy) + self.config.registry.registerUtility(policy, IAuthorizationPolicy) + + def test_requestonly_function(self): + def view(request): return 'OK' - result = self._callFUT(view) - self.failUnless(result is view) + deriver = self._makeOne() + result = deriver(view) + self.failIf(result is view) self.assertEqual(result(None, None), 'OK') - def test__map_view_as_function_with_attr(self): - def view(context, request): - """ """ - result = self._callFUT(view, attr='__name__') + def test_requestonly_function_with_renderer(self): + class moo(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, 'OK') + self.assertEqual(view_inst, view) + self.assertEqual(ctx, context) + return 'moo' + def view(request): + return 'OK' + deriver = self._makeOne(renderer=moo()) + result = deriver(view) self.failIf(result is view) - self.assertRaises(TypeError, result, None, None) - - def test__map_view_as_function_with_attr_and_renderer(self): - renderer = self._registerRenderer() - view = lambda *arg: 'OK' - result = self._callFUT(view, attr='__name__', renderer=renderer) + request = self._makeRequest() + context = testing.DummyResource() + self.assertEqual(result(context, request), 'moo') + + def test_requestonly_function_with_renderer_request_override(self): + def moo(info): + def inner(value, system): + self.assertEqual(value, 'OK') + self.assertEqual(system['request'], request) + self.assertEqual(system['context'], context) + return 'moo' + return inner + def view(request): + return 'OK' + self.config.add_renderer('moo', moo) + deriver = self._makeOne(renderer='string') + result = deriver(view) self.failIf(result is view) - self.assertRaises(TypeError, result, None, None) + request = self._makeRequest() + request.override_renderer = 'moo' + context = testing.DummyResource() + self.assertEqual(result(context, request).body, 'moo') - def test__map_view_as_function_requestonly(self): + def test_requestonly_function_with_renderer_request_has_view(self): + class moo(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, 'OK') + self.assertEqual(view_inst, 'view') + self.assertEqual(ctx, context) + return 'moo' def view(request): return 'OK' - result = self._callFUT(view) + deriver = self._makeOne(renderer=moo()) + result = deriver(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result(None, None), 'OK') + request = self._makeRequest() + request.__view__ = 'view' + context = testing.DummyResource() + self.assertEqual(result(context, request), 'moo') + self.failIf(hasattr(request, '__view__')) + + def test_class_without_attr(self): + class View(object): + def __init__(self, request): + pass + def __call__(self): + return 'OK' + deriver = self._makeOne() + result = deriver(View) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + self.assertEqual(request.__view__.__class__, View) - def test__map_view_as_function_requestonly_with_attr(self): + def test_class_with_attr(self): + class View(object): + def __init__(self, request): + pass + def another(self): + return 'OK' + deriver = self._makeOne(attr='another') + result = deriver(View) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + self.assertEqual(request.__view__.__class__, View) + + def test_as_function_context_and_request(self): + def view(context, request): + return 'OK' + deriver = self._makeOne() + result = deriver(view) + self.failUnless(result is view) + self.failIf(hasattr(result, '__call_permissive__')) + self.assertEqual(view(None, None), 'OK') + + def test_as_function_requestonly(self): def view(request): - """ """ - result = self._callFUT(view, attr='__name__') + return 'OK' + deriver = self._makeOne() + result = deriver(view) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.assertRaises(TypeError, result, None, None) + self.failIf(hasattr(result, '__call_permissive__')) + self.assertEqual(result(None, None), 'OK') - def test__map_view_as_newstyle_class_context_and_request(self): + def test_as_newstyle_class_context_and_request(self): class view(object): def __init__(self, context, request): pass def __call__(self): return 'OK' - result = self._callFUT(view) + deriver = self._makeOne() + result = deriver(view) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result(None, None), 'OK') + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + self.assertEqual(request.__view__.__class__, view) - def test__map_view_as_newstyle_class_context_and_request_with_attr(self): + def test_as_newstyle_class_requestonly(self): class view(object): def __init__(self, context, request): pass - def index(self): + def __call__(self): return 'OK' - result = self._callFUT(view, attr='index') + deriver = self._makeOne() + result = deriver(view) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result(None, None), 'OK') + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + self.assertEqual(request.__view__.__class__, view) - def test__map_view_as_newstyle_class_context_and_request_attr_and_renderer( - self): - renderer = self._registerRenderer() - class view(object): + def test_as_oldstyle_class_context_and_request(self): + class view: def __init__(self, context, request): pass - def index(self): - return {'a':'1'} - result = self._callFUT(view, attr='index', renderer=renderer) + def __call__(self): + return 'OK' + deriver = self._makeOne() + result = deriver(view) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') + self.assertEqual(result(None, request), 'OK') + self.assertEqual(request.__view__.__class__, view) - def test__map_view_as_newstyle_class_requestonly(self): - class view(object): - def __init__(self, request): + def test_as_oldstyle_class_requestonly(self): + class view: + def __init__(self, context, request): pass def __call__(self): return 'OK' - result = self._callFUT(view) + deriver = self._makeOne() + result = deriver(view) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + self.assertEqual(request.__view__.__class__, view) + + def test_as_instance_context_and_request(self): + class View: + def __call__(self, context, request): + return 'OK' + view = View() + deriver = self._makeOne() + result = deriver(view) + self.failUnless(result is view) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') - def test__map_view_as_newstyle_class_requestonly_with_attr(self): - class view(object): - def __init__(self, request): - pass - def index(self): + def test_as_instance_requestonly(self): + class View: + def __call__(self, request): return 'OK' - result = self._callFUT(view, attr='index') + view = View() + deriver = self._makeOne() + result = deriver(view) self.failIf(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) + self.failUnless('instance' in result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), 'OK') - def test__map_view_as_newstyle_class_requestonly_attr_and_renderer(self): - renderer = self._registerRenderer() - class view(object): + def test_with_debug_authorization_no_authpol(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict( + debug_authorization=True, reload_templates=True) + logger = self._registerLogger() + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): Allowed " + "(no authorization policy in use)") + + def test_with_debug_authorization_no_permission(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict( + debug_authorization=True, reload_templates=True) + self._registerSecurityPolicy(True) + logger = self._registerLogger() + deriver = self._makeOne() + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): Allowed (" + "no permission registered)") + + def test_debug_auth_permission_authpol_permitted(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict( + debug_authorization=True, reload_templates=True) + logger = self._registerLogger() + self._registerSecurityPolicy(True) + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result.__call_permissive__, view) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): True") + + def test_debug_auth_permission_authpol_denied(self): + from pyramid.exceptions import Forbidden + view = lambda *arg: 'OK' + self.config.registry.settings = dict( + debug_authorization=True, reload_templates=True) + logger = self._registerLogger() + self._registerSecurityPolicy(False) + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result.__call_permissive__, view) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertRaises(Forbidden, result, None, request) + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): False") + + def test_debug_auth_permission_authpol_denied2(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict( + debug_authorization=True, reload_templates=True) + self._registerLogger() + self._registerSecurityPolicy(False) + deriver = self._makeOne(permission='view') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + permitted = result.__permitted__(None, None) + self.assertEqual(permitted, False) + + def test_debug_auth_permission_authpol_overridden(self): + view = lambda *arg: 'OK' + self.config.registry.settings = dict( + debug_authorization=True, reload_templates=True) + logger = self._registerLogger() + self._registerSecurityPolicy(False) + deriver = self._makeOne(permission='__no_permission_required__') + result = deriver(view) + self.assertEqual(view.__module__, result.__module__) + self.assertEqual(view.__doc__, result.__doc__) + self.assertEqual(view.__name__, result.__name__) + self.failIf(hasattr(result, '__call_permissive__')) + request = self._makeRequest() + request.view_name = 'view_name' + request.url = 'url' + self.assertEqual(result(None, request), 'OK') + self.assertEqual(len(logger.messages), 1) + self.assertEqual(logger.messages[0], + "debug_authorization of url url (view name " + "'view_name' against context None): False") + + def test_with_predicates_all(self): + view = lambda *arg: 'OK' + predicates = [] + def predicate1(context, request): + predicates.append(True) + return True + def predicate2(context, request): + predicates.append(True) + return True + deriver = self._makeOne(predicates=[predicate1, predicate2]) + result = deriver(view) + request = self._makeRequest() + request.method = 'POST' + next = result(None, None) + self.assertEqual(next, 'OK') + self.assertEqual(predicates, [True, True]) + + def test_with_predicates_checker(self): + view = lambda *arg: 'OK' + predicates = [] + def predicate1(context, request): + predicates.append(True) + return True + def predicate2(context, request): + predicates.append(True) + return True + deriver = self._makeOne(predicates=[predicate1, predicate2]) + result = deriver(view) + request = self._makeRequest() + request.method = 'POST' + next = result.__predicated__(None, None) + self.assertEqual(next, True) + self.assertEqual(predicates, [True, True]) + + def test_with_predicates_notall(self): + from pyramid.exceptions import NotFound + view = lambda *arg: 'OK' + predicates = [] + def predicate1(context, request): + predicates.append(True) + return True + def predicate2(context, request): + predicates.append(True) + return False + deriver = self._makeOne(predicates=[predicate1, predicate2]) + result = deriver(view) + request = self._makeRequest() + request.method = 'POST' + self.assertRaises(NotFound, result, None, None) + self.assertEqual(predicates, [True, True]) + + def test_with_wrapper_viewname(self): + from webob import Response + from pyramid.interfaces import IView + from pyramid.interfaces import IViewClassifier + inner_response = Response('OK') + def inner_view(context, request): + return inner_response + def outer_view(context, request): + self.assertEqual(request.wrapped_response, inner_response) + self.assertEqual(request.wrapped_body, inner_response.body) + self.assertEqual(request.wrapped_view, inner_view) + return Response('outer ' + request.wrapped_body) + self.config.registry.registerAdapter( + outer_view, (IViewClassifier, None, None), IView, 'owrap') + deriver = self._makeOne(viewname='inner', + wrapper_viewname='owrap') + result = deriver(inner_view) + self.failIf(result is inner_view) + self.assertEqual(inner_view.__module__, result.__module__) + self.assertEqual(inner_view.__doc__, result.__doc__) + request = self._makeRequest() + response = result(None, request) + self.assertEqual(response.body, 'outer OK') + + def test_with_wrapper_viewname_notfound(self): + from webob import Response + inner_response = Response('OK') + def inner_view(context, request): + return inner_response + deriver = self._makeOne(viewname='inner', wrapper_viewname='owrap') + wrapped = deriver(inner_view) + request = self._makeRequest() + self.assertRaises(ValueError, wrapped, None, request) + + def test_as_newstyle_class_context_and_request_attr_and_renderer(self): + class renderer(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, {'a':'1'}) + self.assertEqual(view_inst.__class__, View) + self.assertEqual(ctx, context) + return resp + class View(object): + def __init__(self, context, request): + pass + def index(self): + return {'a':'1'} + deriver = self._makeOne(renderer=renderer(), attr='index') + result = deriver(View) + self.failIf(result is View) + self.assertEqual(result.__module__, View.__module__) + self.assertEqual(result.__doc__, View.__doc__) + self.assertEqual(result.__name__, View.__name__) + request = self._makeRequest() + context = testing.DummyResource() + self.assertEqual(result(context, request), {'a':'1'}) + + def test_as_newstyle_class_requestonly_attr_and_renderer(self): + class renderer(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, {'a':'1'}) + self.assertEqual(view_inst.__class__, View) + self.assertEqual(ctx, context) + return resp + class View(object): + def __init__(self, request): + pass + def index(self): + return {'a':'1'} + deriver = self._makeOne(renderer=renderer(), attr='index') + result = deriver(View) + self.failIf(result is View) + self.assertEqual(result.__module__, View.__module__) + self.assertEqual(result.__doc__, View.__doc__) + self.assertEqual(result.__name__, View.__name__) + request = self._makeRequest() + context = testing.DummyResource() + self.assertEqual(result(context, request), {'a':'1'}) + + def test_as_oldstyle_cls_context_request_attr_and_renderer(self): + class renderer(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, {'a':'1'}) + self.assertEqual(view_inst.__class__, View) + self.assertEqual(ctx, context) + return resp + class View: + def __init__(self, context, request): + pass + def index(self): + return {'a':'1'} + deriver = self._makeOne(renderer=renderer(), attr='index') + result = deriver(View) + self.failIf(result is View) + self.assertEqual(result.__module__, View.__module__) + self.assertEqual(result.__doc__, View.__doc__) + self.assertEqual(result.__name__, View.__name__) + request = self._makeRequest() + context = testing.DummyResource() + self.assertEqual(result(context, request), {'a':'1'}) + + def test_as_oldstyle_cls_requestonly_attr_and_renderer(self): + class renderer(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, {'a':'1'}) + self.assertEqual(view_inst.__class__, View) + self.assertEqual(ctx, context) + return resp + class View: def __init__(self, request): pass def index(self): return {'a':'1'} - result = self._callFUT(view, attr='index', renderer=renderer) + deriver = self._makeOne(renderer=renderer(), attr='index') + result = deriver(View) + self.failIf(result is View) + self.assertEqual(result.__module__, View.__module__) + self.assertEqual(result.__doc__, View.__doc__) + self.assertEqual(result.__name__, View.__name__) + request = self._makeRequest() + context = testing.DummyResource() + self.assertEqual(result(context, request), {'a':'1'}) + + def test_as_instance_context_and_request_attr_and_renderer(self): + class renderer(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, {'a':'1'}) + self.assertEqual(view_inst, view) + self.assertEqual(ctx, context) + return resp + class View: + def index(self, context, request): + return {'a':'1'} + deriver = self._makeOne(renderer=renderer(), attr='index') + view = View() + result = deriver(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) + self.assertEqual(result.__module__, view.__module__) + self.assertEqual(result.__doc__, view.__doc__) request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') + context = testing.DummyResource() + self.assertEqual(result(context, request), {'a':'1'}) + + def test_as_instance_requestonly_attr_and_renderer(self): + class renderer(object): + def render_view(inself, req, resp, view_inst, ctx): + self.assertEqual(req, request) + self.assertEqual(resp, {'a':'1'}) + self.assertEqual(view_inst, view) + self.assertEqual(ctx, context) + return resp + class View: + def index(self, request): + return {'a':'1'} + deriver = self._makeOne(renderer=renderer(), attr='index') + view = View() + result = deriver(view) + self.failIf(result is view) + self.assertEqual(result.__module__, view.__module__) + self.assertEqual(result.__doc__, view.__doc__) + request = self._makeRequest() + context = testing.DummyResource() + self.assertEqual(result(context, request), {'a':'1'}) + + def test_with_view_mapper_config_specified(self): + class mapper(object): + def __init__(self, **kw): + self.kw = kw + def __call__(self, view): + def wrapped(context, request): + return 'OK' + return wrapped + def view(context, request): + return 'NOTOK' + deriver = self._makeOne(view_mapper=mapper) + result = deriver(view) + self.failIf(result is view) + self.assertEqual(result(None, None), 'OK') - def test__map_view_as_oldstyle_class_context_and_request(self): - class view: + def test_with_view_mapper_view_specified(self): + def mapper(**kw): + def inner(view): + def superinner(context, request): + self.assertEqual(request, None) + return 'OK' + return superinner + return inner + def view(context, request): + return 'NOTOK' + view.__view_mapper__ = mapper + deriver = self._makeOne() + result = deriver(view) + self.failIf(result is view) + self.assertEqual(result(None, None), 'OK') + + def test_with_view_mapper_default_mapper_specified(self): + def mapper(**kw): + def inner(view): + def superinner(context, request): + self.assertEqual(request, None) + return 'OK' + return superinner + return inner + self.config.set_view_mapper(mapper) + def view(context, request): + return 'NOTOK' + deriver = self._makeOne() + result = deriver(view) + self.failIf(result is view) + self.assertEqual(result(None, None), 'OK') + +class TestDefaultViewMapper(unittest.TestCase): + def setUp(self): + self.config = testing.setUp() + self.registry = self.config.registry + + def tearDown(self): + del self.registry + testing.tearDown() + + def _makeOne(self, **kw): + from pyramid.config import DefaultViewMapper + kw['registry'] = self.registry + return DefaultViewMapper(**kw) + + def _makeRequest(self): + request = DummyRequest() + request.registry = self.registry + return request + + def test_view_as_function_context_and_request(self): + def view(context, request): + return 'OK' + mapper = self._makeOne() + result = mapper(view) + self.failUnless(result is view) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + + def test__view_as_function_with_attr(self): + def view(context, request): + """ """ + mapper = self._makeOne(attr='__name__') + result = mapper(view) + self.failIf(result is view) + request = self._makeRequest() + self.assertRaises(TypeError, result, None, request) + + def test_view_as_function_requestonly(self): + def view(request): + return 'OK' + mapper = self._makeOne() + result = mapper(view) + self.failIf(result is view) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + + def test_view_as_function_requestonly_with_attr(self): + def view(request): + """ """ + mapper = self._makeOne(attr='__name__') + result = mapper(view) + self.failIf(result is view) + request = self._makeRequest() + self.assertRaises(TypeError, result, None, request) + + def test_view_as_newstyle_class_context_and_request(self): + class view(object): def __init__(self, context, request): pass def __call__(self): return 'OK' - result = self._callFUT(view) + mapper = self._makeOne() + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result(None, None), 'OK') + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_oldstyle_class_context_and_request_with_attr(self): - class view: + def test_view_as_newstyle_class_context_and_request_with_attr(self): + class view(object): def __init__(self, context, request): pass def index(self): return 'OK' - result = self._callFUT(view, attr='index') + mapper = self._makeOne(attr='index') + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result(None, None), 'OK') + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_oldstyle_cls_context_request_attr_and_renderer(self): - renderer = self._registerRenderer() - class view: - def __init__(self, context, request): + def test_view_as_newstyle_class_requestonly(self): + class view(object): + def __init__(self, request): + pass + def __call__(self): + return 'OK' + mapper = self._makeOne() + result = mapper(view) + self.failIf(result is view) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + + def test_view_as_newstyle_class_requestonly_with_attr(self): + class view(object): + def __init__(self, request): pass def index(self): - return {'a':'1'} - result = self._callFUT(view, attr='index', renderer=renderer) + return 'OK' + mapper = self._makeOne(attr='index') + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_oldstyle_class_requestonly(self): + def test_view_as_oldstyle_class_context_and_request(self): class view: - def __init__(self, request): + def __init__(self, context, request): pass def __call__(self): return 'OK' - result = self._callFUT(view) + mapper = self._makeOne() + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result(None, None), 'OK') + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_oldstyle_class_requestonly_with_attr(self): + def test_view_as_oldstyle_class_context_and_request_with_attr(self): class view: - def __init__(self, request): + def __init__(self, context, request): pass def index(self): return 'OK' - result = self._callFUT(view, attr='index') + mapper = self._makeOne(attr='index') + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) - self.assertEqual(result(None, None), 'OK') + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_oldstyle_class_requestonly_attr_and_renderer(self): - renderer = self._registerRenderer() + def test_view_as_oldstyle_class_requestonly(self): + class view: + def __init__(self, request): + pass + def __call__(self): + return 'OK' + mapper = self._makeOne() + result = mapper(view) + self.failIf(result is view) + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') + + def test_view_as_oldstyle_class_requestonly_with_attr(self): class view: def __init__(self, request): pass def index(self): - return {'a':'1'} - result = self._callFUT(view, attr='index', renderer=renderer) + return 'OK' + mapper = self._makeOne(attr='index') + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.assertEqual(view.__name__, result.__name__) request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_instance_context_and_request(self): + def test_view_as_instance_context_and_request(self): class View: def __call__(self, context, request): return 'OK' view = View() - result = self._callFUT(view) + mapper = self._makeOne() + result = mapper(view) self.failUnless(result is view) - self.assertEqual(result(None, None), 'OK') + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_instance_context_and_request_and_attr(self): + def test_view_as_instance_context_and_request_and_attr(self): class View: def index(self, context, request): return 'OK' view = View() - result = self._callFUT(view, attr='index') - self.failIf(result is view) - self.assertEqual(result(None, None), 'OK') - - def test__map_view_as_instance_context_and_request_attr_and_renderer(self): - renderer = self._registerRenderer() - class View: - def index(self, context, request): - return {'a':'1'} - view = View() - result = self._callFUT(view, attr='index', renderer=renderer) + mapper = self._makeOne(attr='index') + result = mapper(view) self.failIf(result is view) request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_instance_requestonly(self): + def test_view_as_instance_requestonly(self): class View: def __call__(self, request): return 'OK' view = View() - result = self._callFUT(view) + mapper = self._makeOne() + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.failUnless('instance' in result.__name__) - self.assertEqual(result(None, None), 'OK') + request = self._makeRequest() + self.assertEqual(result(None, request), 'OK') - def test__map_view_as_instance_requestonly_with_attr(self): + def test_view_as_instance_requestonly_with_attr(self): class View: def index(self, request): return 'OK' view = View() - result = self._callFUT(view, attr='index') - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.failUnless('instance' in result.__name__) - self.assertEqual(result(None, None), 'OK') - - def test__map_view_as_instance_requestonly_with_attr_and_renderer(self): - renderer = self._registerRenderer() - class View: - def index(self, request): - return {'a':'1'} - view = View() - result = self._callFUT(view, attr='index', renderer=renderer) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - self.failUnless('instance' in result.__name__) - request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') - - def test__map_view_rendereronly(self): - renderer = self._registerRenderer() - def view(context, request): - return {'a':'1'} - result = self._callFUT(view, renderer=renderer) + mapper = self._makeOne(attr='index') + result = mapper(view) self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') - - def test__map_view_with_registry(self): - renderer = self._registerRenderer() - def view(context, request): - return {'a':'1'} - result = self._callFUT(view, renderer=renderer) - self.failIf(result is view) - self.assertEqual(view.__module__, result.__module__) - self.assertEqual(view.__doc__, result.__doc__) - request = self._makeRequest() - self.assertEqual(result(None, request).body, 'Hello!') + self.assertEqual(result(None, request), 'OK') -class Test_wraps_view(unittest.TestCase): - def _callFUT(self, fn, view): - from pyramid.config import wraps_view - return wraps_view(fn)(None, view) +class Test_preserve_view_attrs(unittest.TestCase): + def _callFUT(self, view, wrapped_view): + from pyramid.config import preserve_view_attrs + return preserve_view_attrs(view, wrapped_view) def test_it_same(self): def view(context, request): """ """ - def afunc(self, view): - return view - result = self._callFUT(afunc, view) + result = self._callFUT(view, view) self.failUnless(result is view) + def test_it_different_with_existing_original_view(self): + def view1(context, request): pass + view1.__original_view__ = 'abc' + def view2(context, request): pass + result = self._callFUT(view1, view2) + self.assertEqual(result.__original_view__, 'abc') + self.failIf(result is view1) + def test_it_different(self): class DummyView1: """ 1 """ @@ -3906,9 +4058,9 @@ class Test_wraps_view(unittest.TestCase): __module__ = '1' def __call__(self, context, request): """ """ - def __call_permissive__(self, context, reuqest): + def __call_permissive__(self, context, request): """ """ - def __predicated__(self, context, reuqest): + def __predicated__(self, context, request): """ """ def __permitted__(self, context, request): """ """ @@ -3918,18 +4070,17 @@ class Test_wraps_view(unittest.TestCase): __module__ = '2' def __call__(self, context, request): """ """ - def __call_permissive__(self, context, reuqest): + def __call_permissive__(self, context, request): """ """ - def __predicated__(self, context, reuqest): + def __predicated__(self, context, request): """ """ def __permitted__(self, context, request): """ """ view1 = DummyView1() view2 = DummyView2() - def afunc(self, view): - return view1 - result = self._callFUT(afunc, view2) + result = self._callFUT(view2, view1) self.assertEqual(result, view1) + self.failUnless(view1.__original_view__ is view2) self.failUnless(view1.__doc__ is view2.__doc__) self.failUnless(view1.__module__ is view2.__module__) self.failUnless(view1.__name__ is view2.__name__) @@ -4395,24 +4546,23 @@ class TestMultiView(unittest.TestCase): response = mv(context, request) self.assertEqual(response, expected_response) - -class TestRequestOnly(unittest.TestCase): - def _callFUT(self, arg): +class Test_requestonly(unittest.TestCase): + def _callFUT(self, view, attr=None): from pyramid.config import requestonly - return requestonly(arg) + return requestonly(view, attr) - def test_newstyle_class_no_init(self): + def test_requestonly_newstyle_class_no_init(self): class foo(object): """ """ self.assertFalse(self._callFUT(foo)) - def test_newstyle_class_init_toomanyargs(self): + def test_requestonly_newstyle_class_init_toomanyargs(self): class foo(object): def __init__(self, context, request): """ """ self.assertFalse(self._callFUT(foo)) - def test_newstyle_class_init_onearg_named_request(self): + def test_requestonly_newstyle_class_init_onearg_named_request(self): class foo(object): def __init__(self, request): """ """ @@ -4488,6 +4638,22 @@ class TestRequestOnly(unittest.TestCase): """ """ self.assertFalse(self._callFUT(foo)) + def test_function_with_attr_false(self): + def bar(context, request): + """ """ + def foo(context, request): + """ """ + foo.bar = bar + self.assertFalse(self._callFUT(foo, 'bar')) + + def test_function_with_attr_true(self): + def bar(context, request): + """ """ + def foo(request): + """ """ + foo.bar = bar + self.assertTrue(self._callFUT(foo, 'bar')) + def test_function_onearg_named_request(self): def foo(request): """ """ diff --git a/pyramid/tests/test_security.py b/pyramid/tests/test_security.py index dd9d48f45..94cefa642 100644 --- a/pyramid/tests/test_security.py +++ b/pyramid/tests/test_security.py @@ -224,6 +224,36 @@ class TestAuthenticatedUserId(unittest.TestCase): result = self._callFUT(request) self.assertEqual(result, 'yo') +class TestUnauthenticatedUserId(unittest.TestCase): + def setUp(self): + cleanUp() + + def tearDown(self): + cleanUp() + + def _callFUT(self, request): + from pyramid.security import unauthenticated_userid + return unauthenticated_userid(request) + + def test_no_authentication_policy(self): + request = _makeRequest() + result = self._callFUT(request) + self.assertEqual(result, None) + + def test_with_authentication_policy(self): + request = _makeRequest() + _registerAuthenticationPolicy(request.registry, 'yo') + result = self._callFUT(request) + self.assertEqual(result, 'yo') + + def test_with_authentication_policy_no_reg_on_request(self): + from pyramid.threadlocal import get_current_registry + request = DummyRequest({}) + registry = get_current_registry() + _registerAuthenticationPolicy(registry, 'yo') + result = self._callFUT(request) + self.assertEqual(result, 'yo') + class TestEffectivePrincipals(unittest.TestCase): def setUp(self): cleanUp() @@ -355,6 +385,9 @@ class DummyAuthenticationPolicy: def effective_principals(self, request): return self.result + def unauthenticated_userid(self, request): + return self.result + def authenticated_userid(self, request): return self.result diff --git a/pyramid/tests/test_testing.py b/pyramid/tests/test_testing.py index ec6fdac5f..d2ed957f2 100644 --- a/pyramid/tests/test_testing.py +++ b/pyramid/tests/test_testing.py @@ -297,7 +297,11 @@ class TestDummySecurityPolicy(unittest.TestCase): def test_authenticated_userid(self): policy = self._makeOne('user') self.assertEqual(policy.authenticated_userid(None), 'user') - + + def test_unauthenticated_userid(self): + policy = self._makeOne('user') + self.assertEqual(policy.unauthenticated_userid(None), 'user') + def test_effective_principals_userid(self): policy = self._makeOne('user', ('group1',)) from pyramid.security import Everyone diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index 7fc066319..33c2b606d 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -229,11 +229,14 @@ class TestViewConfigDecorator(unittest.TestCase): def test_create_nondefaults(self): decorator = self._makeOne(name=None, request_type=None, for_=None, - permission='foo') + permission='foo', view_mapper='mapper', + decorator='decorator') self.assertEqual(decorator.name, None) self.assertEqual(decorator.request_type, None) self.assertEqual(decorator.context, None) self.assertEqual(decorator.permission, 'foo') + self.assertEqual(decorator.view_mapper, 'mapper') + self.assertEqual(decorator.decorator, 'decorator') def test_call_function(self): decorator = self._makeOne() diff --git a/pyramid/url.py b/pyramid/url.py index ac569eecb..c11e39143 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -32,7 +32,7 @@ def route_url(route_name, request, *elements, **kw): enough arguments, for example). For example, if you've defined a route named "foobar" with the path - ``:foo/{bar}/*traverse``:: + ``{foo}/{bar}/*traverse``:: route_url('foobar', request, foo='1') => <KeyError exception> route_url('foobar', request, foo='1', bar='2') => <KeyError exception> diff --git a/pyramid/view.py b/pyramid/view.py index 776185d8b..afd1c6d49 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -17,7 +17,6 @@ from zope.interface import providedBy from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IRendererFactory from pyramid.httpexceptions import HTTPFound from pyramid.renderers import RendererHelper @@ -384,7 +383,8 @@ class view_config(object): route_name=None, request_method=None, request_param=None, containment=None, attr=None, renderer=None, wrapper=None, xhr=False, accept=None, header=None, path_info=None, - custom_predicates=(), context=None): + custom_predicates=(), context=None, decorator=None, + view_mapper=None): self.name = name self.request_type = request_type self.context = context or for_ @@ -401,6 +401,8 @@ class view_config(object): self.header = header self.path_info = path_info self.custom_predicates = custom_predicates + self.decorator = decorator + self.view_mapper = view_mapper def __call__(self, wrapped): settings = self.__dict__.copy() |
