注:以下内容转载自 现代魔法学院 网站的 URLconf处理其二:URL分解器 一文,仅供学习使用。
通常一个 URL 分解器对应一个 URL 配置模块,它可以包含多个 URL 模式,也可以包含多个其他 URL 分解器。通过这种包含结构设计,实现 Django 对 URL 的层级解析。
URL 分解器是 Django 实现 app 与项目解耦的关键。通常由 include 方法操作的 URL 配置模块,最终会被解释成为 URL 分解器。
每个 URL 分解器都需要指定如下几个内容:
- 一个正则表达式字符串。URL 开始部分是否匹配正则表达式,如匹配,去除成功匹配部分后余下部分匹配包含的 URL 模式和 URL 分解器。
- URL 配置模块名或 URL 配置模块的引用。
- 可选的关键参数(字典形式)。
- 可选的 App 名称。
- 可选的名称空间名字。
可以看看 Django 里 URL 分解器的实现,类 django.core.urlresolvers.RegexURLResolver 用来表示 URL 分解器。
class RegexURLResolver(object):
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
# regex is a string representing a regular expression.
# urlconf_name is a string representing the module containing URLconfs.
self.regex = re.compile(regex, re.UNICODE)
self.urlconf_name = urlconf_name
if not isinstance(urlconf_name, basestring):
self._urlconf_module = self.urlconf_name
self.callback = None
self.default_kwargs = default_kwargs or {}
self.namespace = namespace
self.app_name = app_name
self._reverse_dict = None
self._namespace_dict = None
self._app_dict = None
def __repr__(self):
return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)
def _populate(self):
lookups = MultiValueDict()
namespaces = {}
apps = {}
for pattern in reversed(self.url_patterns):
p_pattern = pattern.regex.pattern
if p_pattern.startswith('^'):
p_pattern = p_pattern[1:]
if isinstance(pattern, RegexURLResolver):
if pattern.namespace:
namespaces[pattern.namespace] = (p_pattern, pattern)
if pattern.app_name:
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
else:
parent = normalize(pattern.regex.pattern)
for name in pattern.reverse_dict:
for matches, pat in pattern.reverse_dict.getlist(name):
new_matches = []
for piece, p_args in parent:
new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
lookups.appendlist(name, (new_matches, p_pattern + pat))
for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
for app_name, namespace_list in pattern.app_dict.items():
apps.setdefault(app_name, []).extend(namespace_list)
else:
bits = normalize(p_pattern)
lookups.appendlist(pattern.callback, (bits, p_pattern))
if pattern.name is not None:
lookups.appendlist(pattern.name, (bits, p_pattern))
self._reverse_dict = lookups
self._namespace_dict = namespaces
self._app_dict = apps
def _get_reverse_dict(self):
if self._reverse_dict is None:
self._populate()
return self._reverse_dict
reverse_dict = property(_get_reverse_dict)
def _get_namespace_dict(self):
if self._namespace_dict is None:
self._populate()
return self._namespace_dict
namespace_dict = property(_get_namespace_dict)
def _get_app_dict(self):
if self._app_dict is None:
self._populate()
return self._app_dict
app_dict = property(_get_app_dict)
def resolve(self, path):
tried = []
match = self.regex.search(path)
if match:
new_path = path[match.end():]
for pattern in self.url_patterns:
try:
sub_match = pattern.resolve(new_path)
except Resolver404, e:
sub_tried = e.args[0].get('tried')
if sub_tried is not None:
tried.extend([[pattern] + t for t in sub_tried])
else:
tried.append([pattern])
else:
if sub_match:
sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
sub_match_dict.update(self.default_kwargs)
for k, v in sub_match.kwargs.iteritems():
sub_match_dict[smart_str(k)] = v
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})
def _get_urlconf_module(self):
try:
return self._urlconf_module
except AttributeError:
self._urlconf_module = import_module(self.urlconf_name)
return self._urlconf_module
urlconf_module = property(_get_urlconf_module)
def _get_url_patterns(self):
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
try:
iter(patterns)
except TypeError:
raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
return patterns
url_patterns = property(_get_url_patterns)
def _resolve_special(self, view_type):
callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
if not callback:
# No handler specified in file; use default
# Lazy import, since urls.defaults imports this file
from django.conf.urls import defaults
callback = getattr(defaults, 'handler%s' % view_type)
try:
return get_callable(callback), {}
except (ImportError, AttributeError), e:
raise ViewDoesNotExist("Tried %s. Error was: %s" % (callback, str(e)))
def resolve404(self):
return self._resolve_special('404')
def resolve500(self):
return self._resolve_special('500')
def reverse(self, lookup_view, *args, **kwargs):
if args and kwargs:
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
try:
lookup_view = get_callable(lookup_view, True)
except (ImportError, AttributeError), e:
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
possibilities = self.reverse_dict.getlist(lookup_view)
for possibility, pattern in possibilities:
for result, params in possibility:
if args:
if len(args) != len(params):
continue
unicode_args = [force_unicode(val) for val in args]
candidate = result % dict(zip(params, unicode_args))
else:
if set(kwargs.keys()) != set(params):
continue
unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
candidate = result % unicode_kwargs
if re.search(u'^%s' % pattern, candidate, re.UNICODE):
return candidate
# lookup_view can be URL label, or dotted path, or callable, Any of
# these can be passed in at the top, but callables are not friendly in
# error messages.
m = getattr(lookup_view, '__module__', None)
n = getattr(lookup_view, '__name__', None)
if m is not None and n is not None:
lookup_view_s = "%s.%s" % (m, n)
else:
lookup_view_s = lookup_view
raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
"arguments '%s' not found." % (lookup_view_s, args, kwargs))