Django框架View

摘要

Django框架中View知识点

  • FBV和CBV
  • View
1
2
3
4
'View', 'TemplateView', 'RedirectView', 'ArchiveIndexView',
'YearArchiveView', 'MonthArchiveView', 'WeekArchiveView', 'DayArchiveView',
'TodayArchiveView', 'DateDetailView', 'DetailView', 'FormView',
'CreateView', 'UpdateView', 'DeleteView', 'ListView', 'GenericViewError',

FBV和CBV

Django中有以下两种视图使用方式

  • FBV(function base view) 函数视图
  • CBV(class base view) 类视图

FBV

函数视图比较容易理解,相当于将对应的路由请求分发到指定函数。

路由配置

1
2
3
urlpatterns = [
path('index/',index),
]

视图函数

1
2
3
4
5
6
7
8
9
from django.shortcuts import render


def index(req):
if req.method == ‘POST‘:
print(‘method is :‘ + req.method)
elif req.method == ‘GET‘:
print(‘method is :‘ + req.method)
return render(req, ‘index.html‘)

CBV

用类其实是为了抽象,抽象出通用的,将可变的暴露出来,这样我们就可以用最少的代码实现复杂的功能了

类视图在配置路由时需要使用as_view方法。

路由配置

1
2
3
urlpatterns = [
path('index/',IndexView.as_view()),
]

视图类

1
2
3
4
5
6
7
8
9
10
11
from django.views import View


class IndexView(View):
def get(self, req):
print(‘method is :‘ + req.method)
return render(req, ‘index.html‘)

def post(self, req):
print(‘method is :‘ + req.method)
return render(req, ‘index.html‘)

CBV原理解析

as_view方法是继承的View类中定义的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (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))

def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs

# take name and docstring from class
update_wrapper(view, cls, updated=())

# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view

其实在as_view方法中定义了一个view函数,这个函数接受的参数跟FBV中的函数接受的参数一样,但是这个函数并不处理对应的http请求,而是把这项工作交给了dispatch方法去完成,最后as_view方法返回了这个view函数(闭包)。

1
2
3
4
5
6
7
8
9
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)

在dispatch方法中,dispatch通过反射的方式找到类中对应的方法(如:get、post等),执行对应的业务逻辑并返回执行结果

CBV常用的view

Django中,对那些我们平时经常用的View进行了封装,比如用于渲染一个template的TemplateView,用于处理重定向的RedirectView,用于处理表单的FormView,用于处理数据库对象的DetailView和ListView等,这些View有一个共同的父类:View

TemplateView

TemplateView继承了三个类

  • TemplateResponseMixin:提供render_to_response等方法
  • ContextMixin:提供get_context_data方法
  • View: 提供as_viewdispatch等方法

所以as_view()方法之后调用dispatch方法的过程不变,接下来是对应的http方法略有不同

1
2
3
4
5
6
7
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
Render a template. Pass keyword arguments from the URLconf to the context.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)

dispatch方法调用get方法,先执行get_context_data方法

1
2
3
4
5
6
7
8
9
10
11
12
class ContextMixin:
"""
A default context mixin that passes the keyword arguments received by
get_context_data() as the template context.
"""
extra_context = None

def get_context_data(self, **kwargs):
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs

ContextMixin类则只是实现了一个方法,get_context_data(),这是为在渲染template的时候,提供了一个默认的context,一般子类都会重写这个方法的。

得到context之后,传给render_to_response方法去构造TemplateResponse对象,渲染模板,返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class TemplateResponseMixin:
"""A mixin that can be used to render a template."""
template_name = None
template_engine = None
response_class = TemplateResponse
content_type = None

def render_to_response(self, context, **response_kwargs):
"""
Return a response, using the `response_class` for this view, with a
template rendered with the given context.

Pass response_kwargs to the constructor of the response class.
"""
response_kwargs.setdefault('content_type', self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs
)

def get_template_names(self):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if render_to_response() is overridden.
"""
if self.template_name is None:
raise ImproperlyConfigured(
"TemplateResponseMixin requires either a definition of "
"'template_name' or an implementation of 'get_template_names()'")
else:
return [self.template_name]

使用

1
2
3
4
# 指定页面
template_name="blog/index.html"
# 添加额外的内容到上下文变量中
get_context_data(self,**kwargs)

ListView

ListView继承了以下几个类

  • BaseListView:提供get方法
    • View:提供as_viewdispatch等方法
    • MultipleObjectMixin:提供get_querysetget_context_data等相关方法
      • ContextMixin:提供get_context_data方法
  • MultipleObjectTemplateResponseMixin:提供get_template_names方法
    • TemplateResponseMixin:提供render_to_response等方法
1
2
3
4
5
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
"""
Render some list of objects, set by `self.model` or `self.queryset`.
`self.queryset` can actually be any iterable of items, not just a queryset.
"""

入口依旧是as_view()方法之后调用dispatch方法的过程不变,接下来是对应的http方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BaseListView(MultipleObjectMixin, View):
"""A base view for displaying a list of objects."""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()

if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
'class_name': self.__class__.__name__,
})
context = self.get_context_data()
return self.render_to_response(context)

get方法中先调用get_queryset获取model中的数据,然后调用get_context_data方法将数据封装到context中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

class MultipleObjectMixin(ContextMixin):
"""A mixin for views manipulating multiple objects."""
allow_empty = True
queryset = None
model = None
paginate_by = None
paginate_orphans = 0
context_object_name = None
paginator_class = Paginator
page_kwarg = 'page'
ordering = None

def get_queryset(self):
"""
Return the list of items for this view.

The return value must be an iterable and may be an instance of
`QuerySet` in which case `QuerySet` specific behavior will be enabled.
"""
if self.queryset is not None:
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
}
)
ordering = self.get_ordering()
if ordering:
if isinstance(ordering, str):
ordering = (ordering,)
queryset = queryset.order_by(*ordering)

return queryset

...


def get_context_data(self, *, object_list=None, **kwargs):
"""Get the context for this view."""
queryset = object_list if object_list is not None else self.object_list
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super().get_context_data(**context)

得到context之后,传给render_to_response方法去构造TemplateResponse对象,渲染模板,返回。

MultipleObjectTemplateResponseMixin类中重新定义了render_to_response方法调用的get_template_names方法,添加了一个模板${app_name}/${model_name}_list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
"""Mixin for responding with a template and list of objects."""
template_name_suffix = '_list'

def get_template_names(self):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if render_to_response is overridden.
"""
try:
names = super().get_template_names()
except ImproperlyConfigured:
# If template_name isn't specified, it's not a problem --
# we just start with an empty list.
names = []

# If the list is a queryset, we'll invent a template name based on the
# app and model name. This name gets put at the end of the template
# name list so that user-supplied names override the automatically-
# generated ones.
if hasattr(self.object_list, 'model'):
opts = self.object_list.model._meta
names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
elif not names:
raise ImproperlyConfigured(
"%(cls)s requires either a 'template_name' attribute "
"or a get_queryset() method that returns a QuerySet." % {
'cls': self.__class__.__name__,
}
)
return names

使用

1
2
3
4
5
6
7
8
9
10
# 指定了数据表。他的功能相当于取出了Article中的所有数据
model=Atticle
# 指定页面
template_name="blog/index.html"
# listview默认使用object_list作为上下文变量。可使用context_object_name重命名。
context_object_name="artcle_list"
# 默认取出该表所有数据。
get_queryset(self)
# 这个方法用来添加额外的内容到上下文变量中。
get_context_data(self,**kwargs)

DetailView

DetailView继承了以下几个类

  • BaseDetailView:提供get方法
    • View:提供as_viewdispatch等方法
    • SingleObjectMixin:提供get_querysetget_context_data等相关方法
      • ContextMixin:提供get_context_data方法
  • SingleObjectTemplateResponseMixin:提供get_template_names方法
    • TemplateResponseMixin:提供render_to_response等方法
1
2
3
4
5
6
7
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
"""
Render a "detail" view of an object.

By default this is a model instance looked up from `self.queryset`, but the
view will support display of *any* object by overriding `self.get_object()`.
"""

入口依旧是as_view()方法之后调用dispatch方法的过程不变,接下来是对应的http方法

1
2
3
4
5
6
class BaseDetailView(SingleObjectMixin, View):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)

get方法中先调用get_object获取model中的数据,然后调用get_context_data方法将数据封装到context中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class SingleObjectMixin(ContextMixin):
"""
Provide the ability to retrieve a single object for further manipulation.
"""
model = None
queryset = None
slug_field = 'slug'
context_object_name = None
slug_url_kwarg = 'slug'
pk_url_kwarg = 'pk'
query_pk_and_slug = False

def get_object(self, queryset=None):
"""
Return the object the view is displaying.

Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
Subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()

# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)

# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})

# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError(
"Generic detail view %s must be called with either an object "
"pk or a slug in the URLconf." % self.__class__.__name__
)

try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj

def get_queryset(self):
"""
Return the `QuerySet` that will be used to look up the object.

This method is called by the default implementation of get_object() and
may not be called if get_object() is overridden.
"""
if self.queryset is None:
if self.model:
return self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
}
)
return self.queryset.all()

def get_slug_field(self):
"""Get the name of a slug field to be used to look up by slug."""
return self.slug_field

def get_context_object_name(self, obj):
"""Get the name to use for the object."""
if self.context_object_name:
return self.context_object_name
elif isinstance(obj, models.Model):
return obj._meta.model_name
else:
return None

def get_context_data(self, **kwargs):
"""Insert the single object into the context dict."""
context = {}
if self.object:
context['object'] = self.object
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
context[context_object_name] = self.object
context.update(kwargs)
return super().get_context_data(**context)

得到context之后,传给render_to_response方法去构造TemplateResponse对象,渲染模板,返回。

SingleObjectTemplateResponseMixin类中重新定义了render_to_response方法调用的get_template_names方法,添加了一个模板${app_name}/${model_name}_detail.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
template_name_field = None
template_name_suffix = '_detail'

def get_template_names(self):
"""
Return a list of template names to be used for the request. May not be
called if render_to_response() is overridden. Return the following list:

* the value of ``template_name`` on the view (if provided)
* the contents of the ``template_name_field`` field on the
object instance that the view is operating upon (if available)
* ``<app_label>/<model_name><template_name_suffix>.html``
"""
try:
names = super().get_template_names()
except ImproperlyConfigured:
# If template_name isn't specified, it's not a problem --
# we just start with an empty list.
names = []

# If self.template_name_field is set, grab the value of the field
# of that name from the object; this is the most specific template
# name, if given.
if self.object and self.template_name_field:
name = getattr(self.object, self.template_name_field, None)
if name:
names.insert(0, name)

# The least-specific option is the default <app>/<model>_detail.html;
# only use this if the object in question is a model.
if isinstance(self.object, models.Model):
object_meta = self.object._meta
names.append("%s/%s%s.html" % (
object_meta.app_label,
object_meta.model_name,
self.template_name_suffix
))
elif getattr(self, 'model', None) is not None and issubclass(self.model, models.Model):
names.append("%s/%s%s.html" % (
self.model._meta.app_label,
self.model._meta.model_name,
self.template_name_suffix
))

# If we still haven't managed to find any template names, we should
# re-raise the ImproperlyConfigured to alert the user.
if not names:
raise

return names

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
url(r'^persons/(?P<pk>\d+)/$', PersonDetailView.as_view(), name='detail'),                                                                               

# 指定了数据表。他的功能相当于取出了Article中的所有数据
model=Atticle
# 指定页面
template_name="blog/index.html"
# 默认取出该表所有数据。
get_object(self)
# 这个方法用来添加额外的内容到上下文变量中。
get_context_data(self,**kwargs)
context_object_name = None

# 指定url中传递的参数名,默认数据库中的字段是'pk'
pk_url_kwarg = 'pk'
# 使用自定义字段去数据库中查找数据
query_pk_and_slug = False
# 指定去数据库中过滤数据的字段
slug_field = 'slug'
# 指定去数据库中过滤数据的字段中,url传递的参数名
slug_url_kwarg = 'slug'