forked from jeffkistler/django-decorator-include
-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathdecorator_include.py
96 lines (80 loc) · 3.42 KB
/
decorator_include.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"""
A replacement for ``django.conf.urls.include`` that takes a decorator,
or an iterable of view decorators as the first argument and applies them, in
reverse order, to all views in the included urlconf.
"""
from importlib import import_module
from django.urls import URLPattern, URLResolver, include
from django.utils.functional import cached_property
def _extract_version(package_name):
try:
import importlib.metadata as importlib_metadata
except ImportError: # for python < 3.8
import importlib_metadata
version = importlib_metadata.version(package_name)
return tuple(int(part) for part in version.split(".") if part.isnumeric())
VERSION = _extract_version("django_decorator_include")
class DecoratedPatterns(object):
"""
A wrapper for an urlconf that applies a decorator to all its views.
"""
def __init__(self, urlconf_module, decorators):
# ``urlconf_module`` may be:
# - an object with an ``urlpatterns`` attribute
# - an ``urlpatterns`` itself
# - the dotted Python path to a module with an ``urlpatters`` attribute
self.urlconf = urlconf_module
try:
iter(decorators)
except TypeError:
decorators = [decorators]
self.decorators = decorators
def decorate_pattern(self, pattern):
if isinstance(pattern, URLResolver):
decorated = URLResolver(
pattern.pattern,
DecoratedPatterns(pattern.urlconf_module, self.decorators),
pattern.default_kwargs,
pattern.app_name,
pattern.namespace,
)
else:
callback = pattern.callback
for decorator in reversed(self.decorators):
callback = decorator(callback)
decorated = URLPattern(
pattern.pattern,
callback,
pattern.default_args,
pattern.name,
)
return decorated
@cached_property
def urlpatterns(self):
# urlconf_module might be a valid set of patterns, so we default to it.
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
return [self.decorate_pattern(pattern) for pattern in patterns]
@cached_property
def urlconf_module(self):
if isinstance(self.urlconf, str):
return import_module(self.urlconf)
else:
return self.urlconf
@cached_property
def app_name(self):
return getattr(self.urlconf_module, "app_name", None)
def decorator_include(decorators, arg, namespace=None):
"""
Works like ``django.conf.urls.include`` but takes a view decorator
or an iterable of view decorators as the first argument and applies them,
in reverse order, to all views in the included urlconf.
"""
if isinstance(arg, tuple) and len(arg) == 3 and not isinstance(arg[0], str):
# Special case where the function is used for something like `admin.site.urls`, which
# returns a tuple with the object containing the urls, the app name, and the namespace
# `include` does not support this pattern (you pass directly `admin.site.urls`, without
# using `include`) but we have to
urlconf_module, app_name, namespace = arg
else:
urlconf_module, app_name, namespace = include(arg, namespace=namespace)
return DecoratedPatterns(urlconf_module, decorators), app_name, namespace