我们来深入解析 Django 中 View 方法的源码,特别是它的方法分发机制。这是理解 Django 基于类的视图 (Class-Based Views, CBV) 工作原理的核心。
1. 核心类:View
所有 Django 的基于类的视图都继承自 django.views.generic.base.View。这是整个 CBV 体系的基石。
位置: django/views/generic/base.py
2. 入口点:as_view 类方法
当我们这样定义 URL 时:
urlpatterns = [
path('my-url/', MyView.as_view(), name='my-view'),
]
as_view() 是一个关键的类方法,它负责创建视图的可调用入口。当请求来时,调用url对应的类方法as_view(),我们在视图函数定义的类中没有as_view()方法,所以会在它继承的View父类中找,找到后执行父类中的as_view()方法,在这里的as_view()函数做了一个闭包,里面套着一个view()函数,as_view()方法返回了view()函数。所以请求来时调用as_view()方法返回的是view()函数。在view()函数中调用了dispatch方法进行分发。后面将会介绍到dispatch方法。
class View:
# ... 其他代码 ...
@classonlymethod
def as_view(cls, **initkwargs):
"""主入口点,返回一个视图函数。"""
# 1. 验证传入的关键字参数是否合法
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(
"The method name %s is not accepted as a keyword argument "
"to %s()." % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError(
"%s() received an invalid keyword %r. as_view only "
"accepts arguments that are already attributes of the class."
% (cls.__name__, key)
)
# 2. 定义内部的视图函数
def view(request, *args, **kwargs):
# 3. 创建视图类的实例
self = cls(**initkwargs)
# 4. 设置实例属性
self.setup(request, *args, **kwargs)
# 5. 如果没有request对象,抛出错误
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 6. 调用dispatch方法进行方法分发!
return self.dispatch(request, *args, **kwargs)
# 7. 给返回的函数添加一些属性,便于调试和识别
view.view_class = cls
view.view_initkwargs = initkwargs
# 8. 复制类的一些重要属性到函数上
update_wrapper(view, cls, updated=())
update_wrapper(view, cls.dispatch, assigned=())
return view
3. 方法分发的核心:dispatch 方法
dispatch 方法是真正实现"根据 HTTP 方法调用不同处理函数"的地方。
class View:
# 支持的HTTP方法列表
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def dispatch(self, request, *args, **kwargs):
# 1. 检查请求方法是否在允许的列表中
if request.method.lower() in self.http_method_names:
# 2. 使用getattr动态获取对应的处理方法
# 例如:对于GET请求,获取self.get方法
# 对于POST请求,获取self.post方法
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 3. 如果是不支持的HTTP方法,使用http_method_not_allowed
handler = self.http_method_not_allowed
# 4. 调用获取到的处理方法,并返回结果
return handler(request, *args, **kwargs)
4. 辅助方法
http_method_not_allowed: 当请求使用了不被支持的 HTTP 方法时调用。
def http_method_not_allowed(self, request, *args, **kwargs):
# 记录警告日志
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
# 返回405 Method Not Allowed响应
return HttpResponseNotAllowed(self._allowed_methods())
_allowed_methods: 返回视图支持的 HTTP 方法列表。
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
setup (Django 3.2+): 初始化视图实例的属性。
def setup(self, request, *args, **kwargs):
"""初始化视图实例的通用属性。"""
self.request = request # Django的HttpRequest对象
self.args = args # URL捕获的位置参数
self.kwargs = kwargs # URL捕获的关键字参数
5. 完整的工作流程
- URL 匹配:Django 收到请求,匹配到
path('my-url/', MyView.as_view(), ...) - 创建视图函数:
as_view()被调用,返回内部的view函数 - 调用视图函数:Django 调用
view(request, *args, **kwargs) - 实例化视图类:在
view函数内部创建MyView的实例 - 设置属性:调用
setup()设置request,args,kwargs - 方法分发:调用
dispatch()方法 - 获取处理函数:
dispatch()根据请求方法找到对应的处理函数(如get(),post()) - 执行业务逻辑:调用具体的处理函数
- 返回响应:处理函数返回
HttpResponse对象
6. 一个简单的示例
from django.views import View
from django.http import HttpResponse
class MyView(View):
def get(self, request, *args, **kwargs):
return HttpResponse("这是GET请求的响应")
def post(self, request, *args, **kwargs):
return HttpResponse("这是POST请求的响应")
def put(self, request, *args, **kwargs):
return HttpResponse("这是PUT请求的响应")
7. 源码设计的优点
- 简洁清晰:整个分发机制非常简洁,只有几十行代码
- 可扩展性:可以通过重写
dispatch方法来自定义分发逻辑 - 一致性:所有基于类的视图都遵循相同的模式
- 灵活性:支持所有标准的 HTTP 方法
评论区 0