Django之通用视图组件

前面我们说到过Django之CBV,说到过Django的视图层,也说到过DRF视图组件使用,其中在DRF中提到过通用视图组件,刚开始不太明白,封装太多层了,直到后面看见Django自己的通用视图。

其实在Django的文档中,这部分内容还挺多了,我直接将所有类列在这里,大家有兴趣可以查看官方文档的介绍,我这里只说明常用的几种,做出这么多通用类无非是帮我们节省书写代码,看了下源码还是很容易懂的。

常用视图类

类名功能例子
View基本View, 可以在任何时候使用见后面详细介绍
RedirectView重新定向到其他URL将访问”/log-in/“的用户重新定向到”/login/“
TemplateView显示Django HTML template一般网站中使用模板显示的页
ListView显示对象列表文章列表页
DetailView显示对象详情文章详细页
FormView提交From网站联系我们或emai订阅form
CreateView创建对象创建新文章页
UpdateView更新对象修改文章页
DeleteView删除对象删除文章页
Generic date views显示一段时间内的对象按时间归类的博客

ListView视图

表示对象列表的一个页面

属性

  • model: 指定模型
  • tempalta_name:模板文件
  • queryset:指定一个经过过滤的对象列表,将取代model提供的值
  • context_object_name: 指定要在上下文中使用的变量的名称(默认目标对象名为:object_list)

方法

  • get_queryset():获取此视图的对象列表.必须是可迭代或者可以使查询集.默认返回queryset属性.可以通过重写该方法实现动态过滤.让这种方式能够工作的关键点,在于当类视图被调用时,各种有用的对象被存储在self上,同request(self.request)一样,其中包含了从URLconf中获取到的位置参数(self.args)和关键字参数(self.kwargs).
  • *get_context_data(kwargs)*: 返回显示对象的上下文数据,通过覆盖**该方法返回额外的上下文

例子

不指定模型显示列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ArticleListView(ListView):
template_name = "blog/index.html"
# 上下文对象名
context_object_name = "article_list"

# 返回对象列表
def get_queryset(self):
articles= Article.objects.all()
return articles

# 返回上下文对象(以下操作返回给模板了两个对象:article_list列表对象;author对象)
def get_context_data(self, **kwargs):
kwargs['author'] = '托小尼'
return super(IndexView, self).get_context_data(**kwargs)

模板调用

1
2
3
4
5
6
7
8
9
<h1>
{{ author }}
</h1>
{% for article in article_list %}
<tr>
<td>{{ article.title }}</td>
<td>{{ data.update_time }}</td>
</tr>
{% endfor %}
指定模型显示列表

以下代码我们指定了它的模型对象,所以就没有必要在用get_queryset()去返回了,除非需要重新过滤数据。

1
2
3
4
5
6
7
8
class ArticleListView(ListView):
# 指定模型对象
model = "ArticleModel"
template_name = "article/index.html"
# 返回上下文对象(以下操作返回给模板了两个对象:article_list列表对象;author对象)
def get_context_data(self, **kwargs):
kwargs['author'] = '托小尼'
return super(IndexView, self).get_context_data(**kwargs)

模板调用

视图中我们没有定义列表对象context_object_name,所以模板中默认要使用object_list。

1
2
3
4
5
6
7
8
9
<h1>
{{ author }}
</h1>
{% for article in object_list %}
<tr>
<td>{{ article.title }}</td>
<td>{{ data.update_time }}</td>
</tr>
{% endfor %}

FormView视图

用于提交表单,其实这个就是CreateView和UpdateView的低配版,大概看了下源码,差别就是FromView提交表单验证通过后直接跳转到指定视图了,而后者则是执行了数据库添加或更新操作。

前提说下我们这里定义过了form组件类,所以别再疑惑我从哪儿来的ArticleForm类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ArticleCreateView(FormView):
"""添加移民类型"""
# 表单组件类
form_class = ArticleForm
# 模板文件
template_name = 'article/type_create.html'
# 成功跳转页
success_url = reverse_lazy('article:list')

# 验证通过方法
def form_valid(self, form):
# 添加表单数据至数据表中
Article.objects.create(**form.cleaned_data)
# 传递成功的闪现消息给模板
messages.success(self.request, '哈哈哈,添加成功')
return super().form_valid(form)

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<form method="post" novalidate>
{% if form.errors %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<ul class="list-unstyled">
{% for err in form.errors.values %}
<li>{{ err }}</li>
{% endfor %}
</ul>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endif %}
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>

{% endfor %}
<button type="submit" class="btn btn-primary">添加</button>
</form>

我这里使用的是bootcss,所以自定义了样式,如用默认样式,直接

1
2
3
4
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send message">
</form>

CreateView视图

这个真的跟FormView差别不大,直接上例子吧。

1
2
3
4
5
6
7
8
9
class ArticleCreateView(CreateView):
form_class = ArticleForm
template_name = 'article/type_create.html'
success_url = reverse_lazy('article:list')

def form_valid(self, form):
# 传递成功的闪现消息给模板
messages.success(self.request, '哈哈哈,我也照样行')
return super().form_valid(form)

CreateView中的form_valid已经帮我们把数据保存至数据库了,如果你不需要像我一样添加成功输出提示,那就完全没必要重写form_valid方法了。

注意:此处有坑

  1. success_url复制为跳转链接地址,可以直接字符串写链接地址,可以使用reverse_lazy()反向解析,但是直接使用reverse()会报错;
  2. form_class中定义的ArticleForm继承的是forms.ModelForm,如果直接继承forms.Form同样会报错,两处出错有兴趣的朋友可以自己查下原因哈。

后面在使用通用视图的时候感受到不是太灵便,索性还是自定义cbv吧。仁者见仁智者见智吧,喜欢用这种方式的参考如下详细的介绍。

详解通用视图

Class-based views是Django为解决建站过程中的常见的呈现模式而建立的。具有如下几个原则:

  • 代码越少越好
  • 永远不要重复代码
  • View应当只包含呈现逻辑, 不应包括业务逻辑
  • 保持view逻辑清晰简单
  • 不要将CBVs用作403, 404, 500的错误处理程序
  • 保持mixin简单明了

django自带的view如下表所示:

类名功能例子
View基本View, 可以在任何时候使用见后面详细介绍
RedirectView重新定向到其他URL将访问”/log-in/“的用户重新定向到”/login/“
TemplateView显示Django HTML template一般网站中使用模板显示的页
ListView显示对象列表文章列表页
DetailView显示对象详情文章详细页
FormView提交From网站联系我们或emai订阅form
CreateView创建对象创建新文章页
UpdateView更新对象修改文章页
DeleteView删除对象删除文章页
Generic date views显示一段时间内的对象按时间归类的博客

ListView

表示对象列表的一个页面.
执行这个视图的时候,self.object_list将包含视图正在操作的对象列表(通常是一个查询集,但不是必须).
属性:

  • model: 指定模型
  • tempalta_name:模板文件
  • queryset:指定一个经过过滤的对象列表,将取代model提供的值
  • context_object_name: 指定要在上下文中使用的变量的名称

方法:

  • get_queryset():获取此视图的对象列表.必须是可迭代或者可以使查询集.默认返回queryset属性.可以通过重写该方法实现动态过滤.让这种方式能够工作的关键点,在于当类视图被调用时,各种有用的对象被存储在self上,同request(self.request)一样,其中包含了从URLconf中获取到的位置参数(self.args)和关键字参数(self.kwargs).
  • *get_context_data(kwargs)*: 返回显示对象的上下文数据,通过覆盖**该方法返回额外的上下文

DetailView

执行这个视图的时候,self.object将包含视图正在操作的对象.
属性:

  • model: 视图要显示的模型
  • queryset: 表示对象的一个查询集.queryset的值优先于model的值.
  • template_name: 字符串表示的模板名称.
  • context_object_name: 指定在上下文中使用的变量的名称.
  • pk_url_kwarg: URLconf中,包含逐渐的关键字参数的名称.默认为’pk’.
  • get_context_data:返回显示对象的上下文数据.

方法:

  • get_queryset():返回用来获取本视图显示对象的queryset.如果设置了queryset属性,get_queryset()默认返回它的值.
  • get_object(queryset=None):返回该视图要显示的单个对象.如果提供了queryset,该queryset将作为对象的查询源,否则,将使用get_queryset().get_object()从视图的所有参数中查找pk_url_kwarg参数,如果找到了这个参数,该方法使用这个参数的值执行一个基于逐渐的查询.如果这个参数没有找到,该方法查找slug_url_kwarg参数,使用slug_field字段执行针对slug的查询.当query_pk_and_slugTrue时,get_object()将使用主键和slug执行查询.
  • *get_context_data(*kwargs):返回显示对象的上下文数据.这个方法的基本实现需要object属性被视图赋值(即使是None).它返回一个包含这些内容的字典:
       object:这个视图显示的对象(self.object)
      context_object_name:self.object也将存储在get_context_object_name()返回的名称中,该名称默认为模型的小写名称.

FormView

显示表单的视图,验证错误时,重新显示表单并显示错误信息;成功时,重定向到一个新的URL.
属性:

  • *form_class:*要实例化的Form类.

  • *success_url:*表单成功处理后重定向到的URL.

  • *tamplate_name:*字符串表示的模板名称.

    方法:

  • *get_success_url():*决定在表单成功验证后重定向到的URL,默认返回success_url.

  • *form_valid(form):*在表单验证成功后调用该方法(注意并没有对数据进行操作,例如保存!),并重定向到get_success_url(),可以覆盖该方法在以上行为之间添加额外的动作.该方法必须返回一个HttpResponse.

  • *form_invalid(form):*如果表单验证失败,则使用已填充的表单数据和错误信息重新渲染上下文.

  • *get_context_data(**kwargs):*返回显示对象的上下文数据.

CreateView

显示用于创建对象的表单的视图,通过验证错误信息重新显示视图,并且保存对象.
属性:

tamplate_name:

form_class:

*fields:*字段名称列表,其解释方式与ModelFormMeta.fields相同.如果你是自动生成表单类,那么该属性不能省略.

success_url:

model:

queryset:

context_object_name:

pk_url_kwarg:

方法:

get_queryset():

get_object():

form_valid():

*get_context_data(*kwargs):
(未填写属性和方法的使用同上)

UpdateView

显示用于编辑现有对象的表单的视图,重新显示具有验证错误信息的视图,并且保存对象.这里使用从对象模型自动生成的表单(除非手动制定表单类).
该视图用法与CreateView基本相同,仅在BaseUpdateView中对get()post()的内部实现有区别.

DeleteView

显示确认页面并删除现有对象的视图.仅当请求方法为POST时,才会删除给定的内容.如果此视图是通过GET提取的,它将显示一个确认页面,其中包含POST到同一网址的表单.
属性:

  • tamplate_name:

  • success_url:

  • model:

  • queryset:

  • context_object_name:

  • pk_url_kwarg:

    *方法:

  • get_object():

  • get_queryset():

  • *get_context_data(*kwargs):

  • delete(request, args, **kwargs):*在获取到的对象上调用delete()方法,然后重定向到success_url.

实例

1、创建django工程,创建名为crud的app,此处略过

2、 修改models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class employee(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=30)
address = models.CharField(max_length=60)
city = models.CharField(max_length=60)
email = models.EmailField(max_length=30)
phone = models.CharField()
wechat = models.CharField(max_length=30)
QQ = models.CharField()
def __unicode__(self):
return self.name

class Meta:
ordering = ["-id"]

3、配置

1
2
3
4
5
6
7
8
9
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'crud',
]

4、数据迁移

1
2
python manage.py makemigrations crud
python manage.py migrate crud

5、修改urls,views

crud中的urls

1
2
3
4
5
6
7
8
9
10
from django.conf.urls import url
from crud import views

urlpatterns = [
url(r'^$', views.EmployeeList.as_view(), name='employee_list'),
url(r'^add', views.EmployeeCreate.as_view(), name='employee_add'),
url(r'^update/(?P<pk>\d+)$', views.EmployeeUpdate.as_view(), name='employee_update'),
url(r'^delete/(?P<pk>\d+)$', views.EmployeeDelete.as_view(), name='employee_delete'),
url(r'employee_detail/(?P<pk>\d+)/$', views.EmployeeDetail.as_view(), name='employee_detail'),
]

工程中urls

1
2
3
4
5
6
7
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^crud/', include('crud.urls')),
]
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
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.shortcuts import render, redirect
# Create your views here.
from django.http import HttpResponse, HttpResponseRedirect
from django.views.generic import TemplateView,ListView
from django.utils import timezone
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy
from crud.models import employee

class EmployeeList(ListView):
model = employee

class EmployeeCreate(CreateView):
model = employee
success_url = reverse_lazy('employee_list')
fields = ['id', 'name', 'address', 'city', 'email', 'phone', 'wechat', 'QQ']
def add(request):
form = employee(request.POST or None)
if form.is_valid():
instance.save()
context = {
'form': form,
}
#如果没有有效提交,则仍留在原来页面
return render(request, 'employee_list.html', context)

class EmployeeUpdate(UpdateView):
model = employee
success_url = reverse_lazy('employee_list')
fields = ['id', 'name', 'address','city', 'email', 'phone','wechat','QQ']

class EmployeeDelete(DeleteView):
model = employee
success_url = reverse_lazy('employee_list')

class EmployeeDetail(DetailView):
model = employee #将 model 指定为Publisher,告诉 Django 我要获取的模型是 Publisher。
context_object_name = 'employee_detail' #指定获取的模型列表数据保存的变量名。这个变量会被传递给模板。
def get_context_data(self, **kwargs):
context = super(EmployeeDetail, self).get_context_data(**kwargs)
context['now'] = timezone.now()
return context

6、新建模板
employee_list.html

1
2
3
4
5
6
7
8
9
10
11
<ul>
{% for emp in object_list %}
<li>{{ emp.id }} &nbsp &nbsp {{ emp.name }} &nbsp &nbsp{{ emp.address }}&nbsp &nbsp{{ emp.city }} &nbsp &nbsp
{{ emp.email }} &nbsp &nbsp{{ emp.phone }}&nbsp &nbsp{{ emp.wechat }} &nbsp &nbsp{{ emp.QQ }}:
<a href="{% url "employee_update" emp.id %}">修改</a>
<a href="{% url "employee_delete" emp.id %}">删除</a>
</li>&nbsp
{% endfor %}
</ul>

<a href="{% url "employee_add" %}">新增</a>

employee_form.html

1
2
3
4
5
6
7
8
    <title>Title</title>
<form method="post">
<table>
{% csrf_token %}
{{ form }}
</table>
<input type="submit" value="Submit" />
</form>

employee_confirm_delete.html

1
2
3
4
<form method="post">{% csrf_token %}
确实要删除 "{{ object }}" ?
<input type="submit" value="Submit" />
</form>

employee_detail.html

1
2
3
4
5
6
7
8
9
<h1>你好,{{ object.name }}</h1>
<h2>工号:{{ object.id }}</h2>
<p>姓名:{{ object.name }}</p>
<p>邮箱地址:{{ object.email }}</p>
<p>所在城市: {{ object.city }}</p>
<p>电话号码: {{ object.phone }}</p>
<p>详细地址: {{ object.address }}</p>
<p>微信: {{ object.wechat }}</p>
<p>QQ: {{ object.QQ }}</p>

7、启动服务python manage.py runserver

浏览器中输入 http://127.0.0.1:8000/crud/ 即可对员工进行增加、删除、修改

—–UpdateView和CreateView—–

我们有了Project和Schedule列表显示后,现在需要添加新增和修改的操作。可以直接用Django通用视图里的UpdateView和CreateView。根据官网文档的例子,我们先在projtrack/views.py中添加项目新增和修改视图的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# projtrack/views.py
...
# 修改项目视图
class ProjUpdView(generic.UpdateView):
model = Project
fields = ['project_name', 'description', 'project_status']
template_name = 'projtrack/update_form.html'

# 新增项目视图
class ProjNewView(generic.CreateView):
model = Project
fields = ['project_name', 'description', 'project_type', 'project_status']
template_name = 'projtrack/update_form.html'
...

因为编辑和新增页面的前端代码基本一样,我们在这里都指定了template为update_form。也可指定不同的模板。另外也可用template_name_suffix参数去指定模板。比如,如果设定template_name_suffix = ‘_create_form’, 则用到的模板将会是projtrack/project_create_form.html。

这里projtrack/update_form.html代码如下:

1
2
3
4
5
6
7
8
9
{% extends "projtrack/index.html" %}
{% block body %}
{% if user.is_authenticated %}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input class="btn btn-default" type="submit" value="Update" />
</form>
{% endif %}
{% endblock %}
注意: 在所有的 POST 表单元素时,需要加上一个 {% csrf_token %} tag。这是Django提供的CSRF防护机制。 {{ form.as_p }} 表示渲染表单为一系列的p标签,每个p标签包含一个字段:
1
2
3
4
<p>
<label for="id_subject">Subject:</label>
<input id="id_subject" type="text" name="subject" maxlength="100" />
</p>

其它form在模版中的渲染方式还包括:

form.as_ul:渲染表单为一系列的li标签,每个li 标签包含一个字段
form.as_table:输出表单为一个HTML的table。
for field in form:通过迭代form,获取其中的所有field。field可引用的包括: , , 。输出为field的label元素,输出为field的input,为field的errors元素(errors一般在form验证出错的时候显示)。
form.fieldname:直接将form作为一个dict,引用其每一个field,比如 引用form中的title这个field。这种方式一般用于form需要更加精准的样式的时候,逐个元素逐个元素的编排到html中。

完成后可在页面上新增或修改项目信息。不过点击Update后会有一个报错:191989-055d8b7a362f2603.png

意思是新增后修改完成后我们没有定义一个redirect的url去跳转。根据报错的提示,我们可以直接在视图下给success_url参数赋值,或在模型中去定义get_absolute_url()方法,去设置成功后跳转的url。这里我们在模型中添加:

1
2
3
4
5
# projtrack/model.py    
class Project(models.Model):
...
def get_absolute_url(self):
return reverse('projtrack:project')

这样,在新增或修改项目成功就就跳转到项目列表页面