|
|
@@ -1,14 +1,11 @@
|
|
1
|
|
-import inspect
|
|
2
|
1
|
import logging
|
|
3
|
|
-import sys
|
|
4
|
|
-from collections import OrderedDict
|
|
|
2
|
+from functools import lru_cache
|
|
5
|
3
|
|
|
6
|
4
|
from django.apps import apps
|
|
7
|
5
|
from django.core.exceptions import ImproperlyConfigured
|
|
8
|
6
|
from django.urls import NoReverseMatch, resolve, reverse
|
|
9
|
7
|
|
|
10
|
8
|
from oscar.core.application import OscarDashboardConfig
|
|
11
|
|
-from oscar.core.exceptions import AppNotFoundError
|
|
12
|
9
|
from oscar.views.decorators import check_permissions
|
|
13
|
10
|
|
|
14
|
11
|
logger = logging.getLogger('oscar.dashboard')
|
|
|
@@ -62,78 +59,53 @@ class Node(object):
|
|
62
|
59
|
return len(self.children) > 0
|
|
63
|
60
|
|
|
64
|
61
|
|
|
65
|
|
-def default_access_fn(user, url_name, url_args=None, url_kwargs=None): # noqa C901 too complex
|
|
|
62
|
+@lru_cache(maxsize=1)
|
|
|
63
|
+def _dashboard_url_names_to_config():
|
|
|
64
|
+ dashboard_configs = (
|
|
|
65
|
+ config
|
|
|
66
|
+ for config in apps.get_app_configs()
|
|
|
67
|
+ if isinstance(config, OscarDashboardConfig)
|
|
|
68
|
+ )
|
|
|
69
|
+ urls_to_config = {}
|
|
|
70
|
+ for config in dashboard_configs:
|
|
|
71
|
+ for url in config.urls[0]:
|
|
|
72
|
+ # includes() don't have a name attribute
|
|
|
73
|
+ # We skipped them because they come from other AppConfigs
|
|
|
74
|
+ name = getattr(url, 'name', None)
|
|
|
75
|
+ if not name:
|
|
|
76
|
+ continue
|
|
|
77
|
+
|
|
|
78
|
+ if name in urls_to_config:
|
|
|
79
|
+ if urls_to_config[name] != config:
|
|
|
80
|
+ raise ImproperlyConfigured(
|
|
|
81
|
+ "'{}' exists in both {} and {}!".format(
|
|
|
82
|
+ name, config, urls_to_config[name]
|
|
|
83
|
+ )
|
|
|
84
|
+ )
|
|
|
85
|
+
|
|
|
86
|
+ urls_to_config[name] = config
|
|
|
87
|
+ return urls_to_config
|
|
|
88
|
+
|
|
|
89
|
+
|
|
|
90
|
+def default_access_fn(user, url_name, url_args=None, url_kwargs=None):
|
|
66
|
91
|
"""
|
|
67
|
|
- Given a url_name and a user, this function tries to assess whether the
|
|
|
92
|
+ Given a user and a url_name, this function assesses whether the
|
|
68
|
93
|
user has the right to access the URL.
|
|
69
|
|
- The application instance of the view is fetched via the Django app
|
|
70
|
|
- registry.
|
|
71
|
94
|
Once the permissions for the view are known, the access logic used
|
|
72
|
95
|
by the dashboard decorator is evaluated
|
|
73
|
|
-
|
|
74
|
|
- This function might seem costly, but a simple comparison with DTT
|
|
75
|
|
- did not show any change in response time
|
|
76
|
96
|
"""
|
|
77
|
97
|
if url_name is None: # it's a heading
|
|
78
|
98
|
return True
|
|
79
|
99
|
|
|
80
|
|
- # get view module string.
|
|
|
100
|
+ url = reverse(url_name, args=url_args, kwargs=url_kwargs)
|
|
|
101
|
+ url_match = resolve(url)
|
|
|
102
|
+ url_name = url_match.url_name
|
|
81
|
103
|
try:
|
|
82
|
|
- url = reverse(url_name, args=url_args, kwargs=url_kwargs)
|
|
83
|
|
- except NoReverseMatch:
|
|
84
|
|
- # In Oscar 1.5 this exception was silently ignored which made debugging
|
|
85
|
|
- # very difficult. Now it is being logged and in future the exception will
|
|
86
|
|
- # be propagated.
|
|
87
|
|
- logger.exception('Invalid URL name {}'.format(url_name))
|
|
88
|
|
- return False
|
|
89
|
|
-
|
|
90
|
|
- view_module = resolve(url).func.__module__
|
|
91
|
|
-
|
|
92
|
|
- # We can't assume that the view has the same parent module as the app
|
|
93
|
|
- # config, as either the app config or view can be customised. So we first
|
|
94
|
|
- # look it up in the app registry using "get_containing_app_config", and if
|
|
95
|
|
- # it isn't found, then we walk up the package tree, looking for an
|
|
96
|
|
- # OscarDashboardConfig class, from which we get an app label, and use that
|
|
97
|
|
- # to look it up again in the app registry using "get_app_config".
|
|
98
|
|
- app_config_instance = apps.get_containing_app_config(view_module)
|
|
99
|
|
- if app_config_instance is None:
|
|
100
|
|
- try:
|
|
101
|
|
- app_config_class = get_app_config_class(view_module)
|
|
102
|
|
- except AppNotFoundError:
|
|
103
|
|
- raise ImproperlyConfigured(
|
|
104
|
|
- "Please provide an OscarDashboardConfig subclass in the apps "
|
|
105
|
|
- "module or set a custom access_fn")
|
|
106
|
|
- if hasattr(app_config_class, 'label'):
|
|
107
|
|
- app_label = app_config_class.label
|
|
108
|
|
- else:
|
|
109
|
|
- app_label = app_config_class.name.rpartition('.')[2]
|
|
110
|
|
- try:
|
|
111
|
|
- app_config_instance = apps.get_app_config(app_label)
|
|
112
|
|
- except LookupError:
|
|
113
|
|
- raise AppNotFoundError(
|
|
114
|
|
- "Couldn't find an app with the label %s" % app_label)
|
|
115
|
|
- if not isinstance(app_config_instance, OscarDashboardConfig):
|
|
116
|
|
- raise AppNotFoundError(
|
|
117
|
|
- "Couldn't find an Oscar Dashboard app with the label %s" % app_label)
|
|
118
|
|
-
|
|
119
|
|
- # handle name-spaced view names
|
|
120
|
|
- if ':' in url_name:
|
|
121
|
|
- view_name = url_name.split(':')[1]
|
|
122
|
|
- else:
|
|
123
|
|
- view_name = url_name
|
|
124
|
|
- permissions = app_config_instance.get_permissions(view_name)
|
|
125
|
|
- return check_permissions(user, permissions)
|
|
126
|
|
-
|
|
|
104
|
+ app_config_instance = _dashboard_url_names_to_config()[url_name]
|
|
|
105
|
+ except KeyError:
|
|
|
106
|
+ raise NoReverseMatch(
|
|
|
107
|
+ "{} is not a valid dashboard URL".format(url_match.view_name)
|
|
|
108
|
+ )
|
|
|
109
|
+ permissions = app_config_instance.get_permissions(url_name)
|
|
127
|
110
|
|
|
128
|
|
-def get_app_config_class(module_name):
|
|
129
|
|
- apps_module_name = module_name.rpartition('.')[0] + '.apps'
|
|
130
|
|
- if apps_module_name in sys.modules:
|
|
131
|
|
- oscar_dashboard_config_classes = []
|
|
132
|
|
- apps_module_classes = inspect.getmembers(sys.modules[apps_module_name], inspect.isclass)
|
|
133
|
|
- for klass in OrderedDict(apps_module_classes).values():
|
|
134
|
|
- if issubclass(klass, OscarDashboardConfig):
|
|
135
|
|
- oscar_dashboard_config_classes.append(klass)
|
|
136
|
|
- if oscar_dashboard_config_classes:
|
|
137
|
|
- return oscar_dashboard_config_classes[-1]
|
|
138
|
|
- raise AppNotFoundError(
|
|
139
|
|
- "Couldn't find an app to import %s from" % module_name)
|
|
|
111
|
+ return check_permissions(user, permissions)
|