首页 > 其他 > 详细

Django源码分析 路由、视图(一)

时间:2021-02-08 16:57:24      阅读:21      评论:0      收藏:0      [点我收藏+]

上一篇分析了wsgi应用的实现以及中间件加载及调用顺序,wsgi应用在处理request时还涉及了路由以及视图处理等还未分析,今天分析一下路由以及视图相关的代码

class BaseHandler:
    _view_middleware = None
    _template_response_middleware = None
    _exception_middleware = None
    _middleware_chain = Nonedef get_response(self, request):
        """Return an HttpResponse object for the given HttpRequest."""
        # Setup default url resolver for this thread
        set_urlconf(settings.ROOT_URLCONF)
        response = self._middleware_chain(request)
        response._closable_objects.append(request)
        if response.status_code >= 400:
            log_response(
                %s: %s, response.reason_phrase, request.path,
                response=response,
                request=request,
            )
        return response

    def _get_response(self, request):
        """
        Resolve and call the view, then apply view, exception, and
        template_response middleware. This method is everything that happens
        inside the request/response middleware.
        """
        response = None
        # 我的测试环境request对象没有urlconf属性,所以走的是else分支,当然我们可以在中间件中给urlconf赋值,那么此处将走if分支.暂时还不知道会有什么影响
        if hasattr(request, urlconf):
            urlconf = request.urlconf
            set_urlconf(urlconf)
            resolver = get_resolver(urlconf)
        else:
# 获取URLResolver对象,可以理解为路由解析器 resolver
= get_resolver() # 解析请求路径,下面有具体分析,返回ResolverMatch对象,包含了视图函数,请求参数等信息 resolver_match = resolver.resolve(request.path_info)
# 分别为视图函数,args,kwargs callback, callback_args, callback_kwargs
= resolver_match request.resolver_match = resolver_match # Apply view middleware for middleware_method in self._view_middleware: response = middleware_method(request, callback, callback_args, callback_kwargs) if response: break if response is None: wrapped_callback = self.make_view_atomic(callback) try: response = wrapped_callback(request, *callback_args, **callback_kwargs) except Exception as e: response = self.process_exception_by_middleware(e, request) # Complain if the view returned None (a common error). if response is None: if isinstance(callback, types.FunctionType): # FBV view_name = callback.__name__ else: # CBV view_name = callback.__class__.__name__ + .__call__ raise ValueError( "The view %s.%s didn‘t return an HttpResponse object. It " "returned None instead." % (callback.__module__, view_name) ) # If the response supports deferred rendering, apply template # response middleware and then render the response elif hasattr(response, render) and callable(response.render): for middleware_method in self._template_response_middleware: response = middleware_method(request, response) # Complain if the template response middleware returned None (a common error). if response is None: raise ValueError( "%s.process_template_response didn‘t return an " "HttpResponse object. It returned None instead." % (middleware_method.__self__.__class__.__name__) ) try: response = response.render() except Exception as e: response = self.process_exception_by_middleware(e, request) return response
@functools.lru_cache(maxsize=None)
def get_resolver(urlconf=None):
    if urlconf is None:
# 这里urlconf为None 所以会从配置文件中读取我们配置的ROOT_URLCONF urlconf
= settings.ROOT_URLCONF return URLResolver(RegexPattern(r^/), urlconf)

解析请求路径相关代码,以如下配置为例

urlpatterns = [
path(‘admin/‘, admin.site.urls),
path(‘user/‘, include(‘user.urls‘)),
path(‘goods/‘, include(‘goods.urls‘)),
path(‘cart/‘,include(‘cart.urls‘)),
path(‘order/‘,include(‘order.urls‘)),
path(‘‘,views.IndexView.as_view()),
]
goods.urls
urlpatterns = [
path(‘‘, views.IndexView.as_view(), name=‘index‘),
path(‘detail/<int:pk>/‘, views.DetailView.as_view(), name=‘detail‘),
path(‘list/<int:pk>/‘, views.ListView.as_view(), name=‘list‘),
]
class URLResolver:
    def __init__(self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
        self.pattern = pattern
        # urlconf_name is the dotted Python path to the module defining
        # urlpatterns. It may also be an object with an urlpatterns attribute
        # or urlpatterns itself.
        self.urlconf_name = urlconf_name
        self.callback = None
        self.default_kwargs = default_kwargs or {}
        self.namespace = namespace
        self.app_name = app_name
        self._reverse_dict = {}
        self._namespace_dict = {}
        self._app_dict = {}
        # set of dotted paths to all functions and classes that are used in
        # urlpatterns
        self._callback_strs = set()
        self._populated = False
        self._local = threading.local()

    def resolve(self, path):
        path = str(path)  # path may be a reverse_lazy object
        tried = []
# 以path=‘/goods/detail/4/‘为例,此处match为(‘goods/detail/4/‘,(),{})
# 匹配到后会把匹配到的字符删去,以便做进一步的匹配 match
= self.pattern.match(path) if match: new_path, args, kwargs = match
# 迭代项目urls.py中urlpatterns列表中的对象,通过查看源码可知pattern是URLResolver对象或者URLPattern对象,path第二个参数是list或tuple则是URLResolver,
# 如果是可调用对象则是URLPattern
for pattern in self.url_patterns: try:
# 这里遍历到path(‘goods/‘,include(‘goods.urls‘))能够匹配到,接着调用URLRrsolver.resolve方法,匹配到了‘goods/‘,match为(‘detail/4/‘,(),{}),接着遍历goods app中urls的
# urlpatterns,遍历到path(‘detail/<int:pk>/‘,views.DetailView.as_view(),name=‘detail‘),能够匹配到,调用URLPattern.resolve方法,返回ResolverMatcher
# 包含了视图函数,请求参数等信息 sub_match
= pattern.resolve(new_path) except Resolver404 as 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: # Merge captured arguments in match with submatch sub_match_dict = {**kwargs, **self.default_kwargs} # Update the sub_match_dict with the kwargs from the sub_match. sub_match_dict.update(sub_match.kwargs) # If there are *any* named groups, ignore all non-named groups. # Otherwise, pass all non-named arguments as positional arguments. sub_match_args = sub_match.args if not sub_match_dict: sub_match_args = args + sub_match.args current_route = ‘‘ if isinstance(pattern, URLPattern) else str(pattern.pattern) return ResolverMatch( sub_match.func, sub_match_args, sub_match_dict, sub_match.url_name, [self.app_name] + sub_match.app_names, [self.namespace] + sub_match.namespaces, self._join_route(current_route, sub_match.route), ) tried.append([pattern]) raise Resolver404({tried: tried, path: new_path}) raise Resolver404({path: path}) @cached_property def urlconf_module(self):
# 导入对应的urls模块
if isinstance(self.urlconf_name, str): return import_module(self.urlconf_name) else: return self.urlconf_name @cached_property def url_patterns(self):
# 获取urls中的urlpatterns
# urlconf_module might be a valid set of patterns, so we default to it patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module) try: iter(patterns) except TypeError: msg = ( "The included URLconf ‘{name}‘ does not appear to have any " "patterns in it. If you see valid patterns in the file then " "the issue is probably caused by a circular import." ) raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) return patterns

URLPattern相关代码

class URLPattern:
    def __init__(self, pattern, callback, default_args=None, name=None):
        self.pattern = pattern
        self.callback = callback  # the view
        self.default_args = default_args or {}
        self.name = name

    def __repr__(self):
        return <%s %s> % (self.__class__.__name__, self.pattern.describe())def resolve(self, path):
        match = self.pattern.match(path)
        if match:
# 匹配后返回ResolverMatch对象 new_path, args, kwargs
= match # Pass any extra_kwargs as **kwargs. kwargs.update(self.default_args) return ResolverMatch(self.callback, args, kwargs, self.pattern.name, route=str(self.pattern))

 

path和include相关代码,

以path(‘goods/‘,include(‘goods.urls‘))

goods.urls中path(‘detail/<int:pk>/‘,views.DetailView.as_view(),name=‘detail‘)

 

def include(arg, namespace=None):
# arg=‘goods.urls‘ app_name
= None if isinstance(arg, tuple): # Callable returning a namespace hint. try: urlconf_module, app_name = arg except ValueError: if namespace: raise ImproperlyConfigured( Cannot override the namespace for a dynamic module that provides a namespace. ) raise ImproperlyConfigured( Passing a %d-tuple to include() is not supported. Pass a 2-tuple containing the list of patterns and app_name, and provide the namespace argument to include() instead. % len(arg) ) else: # No namespace hint - use manually provided namespace. urlconf_module = arg # 导入goods.urls模块 if isinstance(urlconf_module, str): urlconf_module = import_module(urlconf_module)
# patterns为goods.urls.patterns,app_name为goods.urls.app_name patterns
= getattr(urlconf_module, urlpatterns, urlconf_module) app_name = getattr(urlconf_module, app_name, app_name) if namespace and not app_name: raise ImproperlyConfigured( Specifying a namespace in include() without providing an app_name is not supported. Set the app_name attribute in the included module, or pass a 2-tuple containing the list of patterns and app_name instead., ) namespace = namespace or app_name # Make sure the patterns can be iterated through (without this, some # testcases will break). if isinstance(patterns, (list, tuple)): for url_pattern in patterns: pattern = getattr(url_pattern, pattern, None) if isinstance(pattern, LocalePrefixPattern): raise ImproperlyConfigured( Using i18n_patterns in an included URLconf is not allowed. ) return (urlconf_module, app_name, namespace) def _path(route, view, kwargs=None, name=None, Pattern=None): if isinstance(view, (list, tuple)):
# path(‘goods/‘,include(‘goods.urls‘))会走这个分支
# For include(...) processing. pattern = Pattern(route, is_endpoint=False) urlconf_module, app_name, namespace = view return URLResolver( pattern, urlconf_module, kwargs, app_name=app_name, namespace=namespace, ) elif callable(view):
# path(‘detail/4/‘,views.DetailView.as_view(),name=‘detail‘)会走这个分支 pattern
= Pattern(route, name=name, is_endpoint=True) return URLPattern(pattern, view, kwargs, name) else: raise TypeError(view must be a callable or a list/tuple in the case of include().) path = partial(_path, Pattern=RoutePattern) re_path = partial(_path, Pattern=RegexPattern)

小结:

到这里路由解析有了一个大致的了解

当一个请求到达后,django根据settings中配置的ROOT_URLCONF,加载根路由模块,获取urlpatterns,将请求路由与urlpatterns进行匹配,如果匹配到后还有子路由,将进行进一步的匹配

直至找到最终的路由,最后返回ResolverMatch对象,该对象包含了路由对应的视图函数,参数等信息

下一篇分析视图相关代码

第一次分析源码,还有许多不足之处,欢迎指正

Django源码分析 路由、视图(一)

原文:https://www.cnblogs.com/zhoubin50/p/14388795.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!