Skip to content

Loading…

Add predicate decorator to not match specific view (#20479) #1199

Closed
wants to merge 1 commit into from

3 participants

@rach

Predicates is concept brought back from Pyramid, Pylon.
It has the goal of allowing a better organization of your function based view and get rid of redundant code as if request.method == 'POST

ticket : https://code.djangoproject.com/ticket/20479#ticket

@areski areski commented on an outdated diff
docs/topics/http/decorators.txt
@@ -51,6 +51,39 @@ a :class:`django.http.HttpResponseNotAllowed` if the conditions are not met.
such as link checkers, rely on HEAD requests, you might prefer
using ``require_safe`` instead of ``require_GET``.
+Add predicates view match checks
+================================
+
+The decorators in :mod:`django.views.decorators.predicate` can be used to restrict
+the match of views based on the list of function check list.
+These decorators will attach a list of functions to be checked before returning a match for the url. If the predicates fail then the process continue the url resolving process.
@areski
areski added a note

add CR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@areski areski commented on an outdated diff
docs/topics/http/decorators.txt
@@ -51,6 +51,39 @@ a :class:`django.http.HttpResponseNotAllowed` if the conditions are not met.
such as link checkers, rely on HEAD requests, you might prefer
using ``require_safe`` instead of ``require_GET``.
+Add predicates view match checks
+================================
+
+The decorators in :mod:`django.views.decorators.predicate` can be used to restrict
+the match of views based on the list of function check list.
@areski
areski added a note

The decorators in :mod:django.views.decorators.predicate can be used to restrict the view selection based on a function result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@areski areski commented on the diff
docs/topics/http/decorators.txt
@@ -51,6 +51,40 @@ a :class:`django.http.HttpResponseNotAllowed` if the conditions are not met.
such as link checkers, rely on HEAD requests, you might prefer
using ``require_safe`` instead of ``require_GET``.
+
@areski
areski added a note

What about a title : Predicate url processing

@rach
rach added a note

Updated in the new commit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rach rach Add predicate decorator to not match specific view
Predicates is concept brought back from Pyramid, Pylon.
It has the goal of allowing a better organizatio of your
function based view and get rid of redundant code as
`if request.method == 'POST`

Improve the doc about predicates

Change title about predicate in the docs

Fix predicate explanation in docs
f52227b
@timgraham
Django member

I'm going to close this for now based on the comments from the ticket.

@timgraham timgraham closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 22, 2013
  1. @rach

    Add predicate decorator to not match specific view

    rach committed
    Predicates is concept brought back from Pyramid, Pylon.
    It has the goal of allowing a better organizatio of your
    function based view and get rid of redundant code as
    `if request.method == 'POST`
    
    Improve the doc about predicates
    
    Change title about predicate in the docs
    
    Fix predicate explanation in docs
View
2 django/core/handlers/base.py
@@ -98,7 +98,7 @@ def get_response(self, request):
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
- resolver_match = resolver.resolve(request.path_info)
+ resolver_match = resolver.resolve(request.path_info, request=request)
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match
View
18 django/core/urlresolvers.py
@@ -311,7 +311,7 @@ def app_dict(self):
self._populate()
return self._app_dict[language_code]
- def resolve(self, path):
+ def resolve(self, path, request=None):
tried = []
match = self.regex.search(path)
if match:
@@ -329,10 +329,24 @@ def resolve(self, path):
if sub_match:
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
sub_match_dict.update(sub_match.kwargs)
+
+ # we check for the request to keep retro compatibility
+ # and if the view function has been decorated with
+ # predicates check before matching
+ if request and hasattr(sub_match.func, 'predicates'):
+ predicate_check = True
+ for predicate in sub_match.func.predicates:
+ if not predicate(request, *sub_match.args):
+ predicate_check = False
+ break
+
+ if not predicate_check:
+ continue
+
return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces)
tried.append([pattern])
raise Resolver404({'tried': tried, 'path': new_path})
- raise Resolver404({'path' : path})
+ raise Resolver404({'path': path})
@property
def urlconf_module(self):
View
47 django/views/decorators/predicate.py
@@ -0,0 +1,47 @@
+"""
+Decorators for views based on HTTP headers.
+"""
+
+import logging
+from functools import wraps
+
+from django.utils.decorators import decorator_from_middleware, available_attrs
+from django.utils.http import http_date
+from django.middleware.http import ConditionalGetMiddleware
+
+logger = logging.getLogger('django.request')
+
+
+def url_predicates(predicates_list):
+ """
+ Decorators to make urls matchable if predicates return True
+ Usage::
+
+ url_pattern += url('/', my_view_GET)
+ url_pattern += url('/', my_view_POST)
+
+ ...
+
+ predicate_GET = lambda request: request.method == 'GET'
+ predicate_POST = lambda request: request.method == 'POST'
+
+ @url_predicates([predicate_GET])
+ def my_view_GET(request):
+ # I can assume now that only GET requests get match to the url
+ # associated to this view
+ # ...
+
+ @url_predicates([predicate_POST])
+ def my_view_POST(request):
+ # I can assume now that only POST requests get match to the url
+ # associated to this view
+ # ...
+ """
+ def decorator(func):
+ @wraps(func, assigned=available_attrs(func))
+ def inner(request, *args, **kwargs):
+ return func(request, *args, **kwargs)
+ inner.predicates = predicates_list
+ return inner
+ return decorator
+
View
36 docs/topics/http/decorators.txt
@@ -51,6 +51,42 @@ a :class:`django.http.HttpResponseNotAllowed` if the conditions are not met.
such as link checkers, rely on HEAD requests, you might prefer
using ``require_safe`` instead of ``require_GET``.
+
@areski
areski added a note

What about a title : Predicate url processing

@rach
rach added a note

Updated in the new commit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+Predicates url processing
+=========================
+
+The decorators in :mod:django.views.decorators.predicate can be used to restrict
+the view selection based on a list of function result.
+These decorators will attach a list of functions to be checked before returning
+a match for the url.
+If the predicates fail then the process continue the url resolving process.
+
+.. function:: view_predicates(predicate_function_list)
+
+ Decorator to require that a view only match particular request. Usage::
+
+ url_pattern += url('/', my_view_GET)
+ url_pattern += url('/', my_view_POST)
+
+ ...
+
+ predicate_GET = lambda request: request.method == 'GET'
+ predicate_POST = lambda request: request.method == 'POST'
+
+ @url_predicates([predicate_GET])
+ def my_view_GET(request):
+ # I can assume now that only GET requests get match to the url
+ # associated to this view
+ # ...
+
+ @url_predicates([predicate_POST])
+ def my_view_POST(request):
+ # I can assume now that only POST requests get match to the url
+ # associated to this view
+ # ...
+
+
+
Conditional view processing
===========================
View
12 tests/handlers/tests.py
@@ -89,3 +89,15 @@ def test_request_signals_streaming_response(self):
self.assertEqual(self.signals, ['started'])
self.assertEqual(b''.join(response.streaming_content), b"streaming content")
self.assertEqual(self.signals, ['started', 'finished'])
+
+
+class PredicatesTests(TestCase):
+ urls = 'handlers.urls'
+
+ def test_request_predicates(self):
+ response = self.client.post('/predicate/')
+ self.assertEqual(response.content, b"predicate content")
+
+ def test_request_predicates_fail_return_404(self):
+ response = self.client.get('/predicate/')
+ self.assertEqual(response.status_code, 404)
View
1 tests/handlers/urls.py
@@ -9,4 +9,5 @@
url(r'^streaming/$', views.streaming),
url(r'^in_transaction/$', views.in_transaction),
url(r'^not_in_transaction/$', views.not_in_transaction),
+ url(r'^predicate/$', views.predicate),
)
View
5 tests/handlers/views.py
@@ -2,6 +2,7 @@
from django.db import connection, transaction
from django.http import HttpResponse, StreamingHttpResponse
+from django.views.decorators.predicate import url_predicates
def regular(request):
return HttpResponse(b"regular content")
@@ -15,3 +16,7 @@ def in_transaction(request):
@transaction.non_atomic_requests
def not_in_transaction(request):
return HttpResponse(str(connection.in_atomic_block))
+
+@url_predicates([lambda request: request.method == 'POST',])
+def predicate(request):
+ return HttpResponse(b"predicate content")
Something went wrong with that request. Please try again.