Document directory
- Ii. 1. url Mode
- Ii. Binary URL Splitter
- Ii. matching results
- Ii. url configuration process
Django source code parsing (1)
Django source code parsing (2) manage. py
Django source code parsing (3) Django development server, wsgi standard implementation
Django source code parsing (4) Middleware
Django source code parsing (5) URL Configuration
1. What is URL configuration?
URL configuration (urlconf) Is like the directory of the website supported by Django. It is essentially a ing table between the URL mode and the view functions to be called for this URL mode. In this way, you tell Django that this code is called for this URL and that code is called for that URL. For example, when a user accesses/Foo/, the view function foo_view () is called. This view function exists in the view. py file of the python module.
RunStartproject django-admin.pyThe script automatically creates a urlconf (that isURLs. pyFile ). In the settings. py file automatically created at the same time, create a variable root_urlconf whose value is the module name of the root urlconf. The default value is the module name of the URLs. py file.
For example, the root directory of my Django project is "Pearl", and the default value of root_urlconf is "Pearl. URLs ".
2. How does Django handle URL configuration?
To understand how Django handles URL configuration, we must first understand several concepts.
Ii. 1. url Mode
The URL mode refers to every value contained in the tuples named urlpatterns in the Django URLs module. The URL mode usually generates the content of the urlpatterns tuples by the patterns method.
You must specify the following content for each URL mode:
- A regular expression string.
- A callable object is usually a view function or a string that specifies the path of the view function.
- Optional default parameter (Dictionary form) to be passed to the view function ).
- An optional name parameter.
- Path prefix, which is added before the view function path string to form a complete view function path. You can specify the path by using the first parameter of the patterns method.
As you can see, those who know about Django may ask, "Isn't there a second URL mode using the include method? Dude, you didn't hold it. Wow ?" O (distinct _ distinct) O ~ I will try again later.
Class Django. Core. urlresolvers. regexurlpattern is used to represent the Django URL mode.
class RegexURLPattern(object): def __init__(self, regex, callback, default_args=None, name=None): # regex is a string representing a regular expression. # callback is either a string like 'foo.views.news.stories.story_detail' # which represents the path to a module and a view function name, or a # callable object (view). self.regex = re.compile(regex, re.UNICODE) if callable(callback): self._callback = callback else: self._callback = None self._callback_str = callback self.default_args = default_args or {} self.name = name def __repr__(self): return '<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern) def add_prefix(self, prefix): """ Adds the prefix string to a string-based callback. """ if not prefix or not hasattr(self, '_callback_str'): return self._callback_str = prefix + '.' + self._callback_str def resolve(self, path): match = self.regex.search(path) if match: # If there are any named groups, use those as kwargs, ignoring # non-named groups. Otherwise, pass all non-named arguments as # positional arguments. kwargs = match.groupdict() if kwargs: args = () else: args = match.groups() # In both cases, pass any extra_kwargs as **kwargs. kwargs.update(self.default_args) return ResolverMatch(self.callback, args, kwargs, self.name) def _get_callback(self): if self._callback is not None: return self._callback try: self._callback = get_callable(self._callback_str) except ImportError, e: mod_name, _ = get_mod_func(self._callback_str) raise ViewDoesNotExist("Could not import %s. Error was: %s" % (mod_name, str(e))) except AttributeError, e: mod_name, func_name = get_mod_func(self._callback_str) raise ViewDoesNotExist("Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))) return self._callback callback = property(_get_callback)
Ii. Binary URL Splitter
Generally, a URL parser corresponds to a URL configuration module. It can contain multiple URL modes or multiple other URL Resolvers. through this design of the Inclusion structure, Django can parse the URL hierarchy.
The URL parser is the key for Django to decouple apps from projects. Generally, the URL configuration module operated by the include method will be interpreted as a URL parser.
Each URL splitter must specify the following content:
- Whether the start part of a regular expression string matches a regular expression. For example, if a regular expression is matched, after the successful match is removed, the remaining part matches the URL pattern and URL parser.
- URL configuration module name or URL configuration module reference.
- Optional key parameters (Dictionary form ).
- Optional app name.
- Optional namespace name.
Class Django. Core. urlresolvers. regexurlresolver is used to represent the URL parser.
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))
Ii. matching results
The matching result is returned when the URL is correctly matched.
The following content must be specified for the matching result:
- A callable object, usually a view function.
- View function parameters. These parameters are usually the values matching the regular expression naming groups in URL mode.
- View function keyword parameters. These parameters are usually set in the URL method to be passed to the view function (in dictionary form ).
- Optional URL name parameters.
- Optional app name parameters.
- Optional namespace parameters.
The class Django. Core. urlresolvers. resolvermatch is used to indicate matching results. The resolvermatch class implements the _ getitem _ method, which can be used to obtain view function references and view function parameters, just like the metadata operation,
This allows you to call view functions.
class ResolverMatch(object): def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None): self.func = func self.args = args self.kwargs = kwargs self.app_name = app_name if namespaces: self.namespaces = [x for x in namespaces if x] else: self.namespaces = [] if not url_name: if not hasattr(func, '__name__'): # An instance of a callable class url_name = '.'.join([func.__class__.__module__, func.__class__.__name__]) else: # A function url_name = '.'.join([func.__module__, func.__name__]) self.url_name = url_name def namespace(self): return ':'.join(self.namespaces) namespace = property(namespace) def view_name(self): return ':'.join([ x for x in [ self.namespace, self.url_name ] if x ]) view_name = property(view_name) def __getitem__(self, index): return (self.func, self.args, self.kwargs)[index] def __repr__(self): return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name='%s', app_name='%s', namespace='%s')" % ( self.func, self.args, self.kwargs, self.url_name, self.app_name, self.namespace)
Ii. url configuration process
Through understanding the URL pattern, URL parser, and URL matching results, we have basically understood the processing process of URL configuration.
Let's look at the code to start processing URL Configuration:
if hasattr(request, "urlconf"): # Reset url resolver with a custom urlconf. urlconf = request.urlconf urlresolvers.set_urlconf(urlconf) resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)callback, callback_args, callback_kwargs = resolver.resolve( request.path_info)
Thanks to the hierarchical design of the URL parser, these lines of code complete the URL configuration process.
Insert: URL matching error page
When the URL is not correctly matched, Django usually reports a 404 error. If the development mode is used, an abnormal page is displayed, including many contents such as the URL module. How can this problem be achieved?
When the URL parser is used to parse the URL configuration, if there is no completely matched pattern, a resolver404 exception will be thrown.
The handling of resolver404 exceptions may be included in the exception middleware introduced in the previous article <Django source code parsing (4) middleware> (just a guess. If you are interested, you can study it .)