一、原生form实现书城增删改查
1、构建模型并完成数据库迁移
(1)构建书城模型
from django.db import models # Create your models here. # 创建一个图书管理系统的模型 class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8, decimal_places=2) # 8个有效数,两个浮点 date = models.DateField() publish = models.ForeignKey("Publish", on_delete=models.CASCADE) authors = models.ManyToManyField("Author") def __str__(self): return self.title class Publish(models.Model): name = models.CharField(max_length=32) def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=32) def __str__(self): return self.name
(2)数据库迁移
$ python3 manage.py makemigrations
$ python3 manage.py migrate
2、运用admin模块录入数据
from django.contrib import admin # Register your models here. from .models import * admin.site.register(Book) admin.site.register(Author) admin.site.register(Publish)
创建超级用户:
manage.py@formsDemo2 > createsuperuser
Username (leave blank to use 'hqs'): yuan
Email address:
Warning: Password input may be echoed.
Password: yuan1234
Warning: Password input may be echoed.
Password (again): yuan1234
Superuser created successfully.
3、录入作者、出版社、书籍信息
4、代码
from django.contrib import admin from django.urls import path from django.conf.urls import url from app01 import views urlpatterns = [ path('admin/', admin.site.urls), url(r'^books/$', views.book), url(r'^books/add', views.addbook), url(r'^books/edit/(\d+)', views.editbook), ]
from django.shortcuts import render, redirect # Create your views here. from .models import * def book(request): book_list = Book.objects.all() return render(request, 'books.html', locals()) def addbook(request): if request.method == "POST": title = request.POST.get("title") price = request.POST.get("price") date = request.POST.get("date") publish_id = request.POST.get("publish_id") # getlist(): Return the list of values for the key. author_pk_list = request.POST.getlist("author_pk_list") # 取到用户输入的书籍信息后添加到数据库Book表中 book_obj = Book.objects.create(title=title, price=price, date=date, publish_id=publish_id) # 直接绑定主键,将作者对象添加到这本书的作者集合中 book_obj.authors.add(*author_pk_list) return redirect("/books/") publish_list = Publish.objects.all() author_list = Author.objects.all() return render(request, "addbook.html", locals()) def editbook(request, edit_book_id): if request.method == "POST": title = request.POST.get("title") price = request.POST.get("price") date = request.POST.get("date") publish_id = request.POST.get("publish_id") # getlist(): Return the list of values for the key. author_pk_list = request.POST.getlist("author_pk_list") # 取到用户输入的书籍信息后更新到数据库Book表中 book_obj = Book.objects.filter(edit_book_id).update(title=title, price=price, date=date, publish_id=publish_id) # 直接绑定主键,将作者对象更新到这本书的作者集合中 book_obj.authors.set(*author_pk_list) # set()先清空,再设置 return redirect("/books/") # 去数据库中取出数据 edit_book = Book.objects.filter(pk=edit_book_id).first() publish_list = Publish.objects.all() author_list = Author.objects.all() return render(request, "editbook.html", locals())
"en"> "UTF-8">Title "/books/add">
{{ book.title }} | {{ book.price }} | {{ book.date|date:"Y-m-d" }} | {{ book.publish.name }} | {{ book.authors.all }} | "/books/edit/{{ book.pk }}">编辑 |
"en"> "UTF-8">Title 添加页面
"en"> "UTF-8">Title 编辑页面
5、显示效果
6、构建表单总结
假设你想在你的网站上创建一个简单的表单,以获得用户的名字。你需要类似这样的模板:
这是一个非常简单的表单。实际应用中,一个表单可能包含几十上百个字段,其中大部分需要预填充,而且我们预料到用户将来回编辑-提交几次才能完成操作。
我们可能需要在表单提交之前,在浏览器端作一些验证。我们可能想使用非常复杂的字段,以允许用户做类似从日历中挑选日期这样的事情,等等。
这个时候,让Django 来为我们完成大部分工作是很容易的。
二、利用forms组件实现
参考:
https://www.cnblogs.com/yuanchenqi/articles/7614921.html
https://www.cnblogs.com/wupeiqi/articles/6144178.html
1、视图中引入forms组件设计Form类
(1)定义一个Form
类,只带有三个普通字段
from django import forms
from django.forms import widgets
class BookForm(forms.Form):
# 三个普通字段类型
title = forms.CharField(max_length=32, label="书籍名称")
price = forms.DecimalField(max_digits=8, decimal_places=2, label="价格")
date = forms.DateField(label="日期",
widget=widgets.TextInput(attrs={"type": "date"}) #
)
注意:
1)字段允许的最大长度通过max_length
定义。它完成两件事情。首先,它在HTML 的 上放置一个
maxlength="100"
(这样浏览器将在第一时间阻止用户输入多于这个数目的字符)。它还意味着当Django 收到浏览器发送过来的表单时,它将验证数据的长度。
2)Form
的实例具有一个is_valid()
方法,它为所有的字段运行验证的程序。当调用这个方法时,如果所有的字段都包含合法的数据,它将:
- 返回
True
- 将表单的数据放到
cleaned_data
属性中。
3)每个表单字段都有一个对应的Widget
类,它对应一个HTML 表单Widget
,例如。
在大部分情况下,字段都具有一个合理的默认Widget。例如,默认情况下,CharField
具有一个TextInput Widget
,它在HTML 中生成一个。
(2)在Form类中添加特殊字段
from django import forms
from django.forms import widgets
class BookForm(forms.Form):
# 三个普通字段类型
title = forms.CharField(max_length=32, label="书籍名称")
price = forms.DecimalField(max_digits=8, decimal_places=2, label="价格")
date = forms.DateField(label="日期",
widget=widgets.TextInput(attrs={"type": "date"}) #
)
# 特殊字段类型
# ChoiceField渲染select标签
# gender = forms.ChoiceField(choices=((1, "男"), (2, "女"), (3, "其他")))
# 出版社:渲染select标签
# publish = forms.ChoiceField(choices=Publish.objects.all().values_list("pk", "name"))
publish = forms.ModelChoiceField(queryset=Publish.objects.all())
# 作者
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
注意:
1)ChoiceField(field)渲染select标签,一般在数据与数据库没关系的时候使用
publish = forms.ChoiceField(choices=Publish.objects.all().values_list("pk", "name"))
2)ModelChoiceField(ChoiceField)继承ChoiceFiled,用以下方式改写,因为传递的参数是queryset因此不需要使用values_list了。
queryset传递的值是主键值和对象,如果类中有设置__str__则取其中的值。
publish = forms.ModelChoiceField(queryset=Publish.objects.all())
3)ModelMultipleChoiceField(ModelChoiceField)继承ModelChoiceField,与数据库打交道且是多选时使用。
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
2、视图
addbook视图函数:
def addbook(request):
if request.method == "POST":
form = BookForm(request.POST) # 实例化BookForm
if form.is_valid(): # 如果是合格的数据
print("clean_data", form.cleaned_data)
# 输出:clean_data {'title': '水浒传', 'price': Decimal('88'), 'date': datetime.date(2014, 4, 20),
# 'publish': , 'authors': ]>}
title = form.cleaned_data.get("title")
price = form.cleaned_data.get("price")
date = form.cleaned_data.get("date")
publish = form.cleaned_data.get("publish")
authors = form.cleaned_data.get("authors")
# 取到用户输入的书籍信息后添加到数据库Book表中
book_obj = Book.objects.create(title=title, price=price, date=date, publish=publish)
# 将作者对象添加到这本书的作者集合中
book_obj.authors.add(*authors)
return redirect("/books/")
form = BookForm() # 实例化BookForm
publish_list = Publish.objects.all()
author_list = Author.objects.all()
return render(request, "addbook.html", locals())
3、模板
(1)form类只含有三个普通字段
addbook.html:
根据{{ form }}
,所有的表单字段和它们的属性将通过Django 的模板语言拆分成HTML 标记 。
form标签novalidate属性:novalidate 属性规定当提交表单时不对其进行验证。如果使用该属性,则表单不会验证表单的输入。
Django 原生支持一个简单易用的跨站请求伪造的防护。当提交一个启用CSRF 防护的POST
表单时,你必须使用上面例子中的csrf_token
模板标签。
完整的表单,第一次渲染时,效果如下:
(2)form类包含特殊字段
通过{{ form.publish }}和{{ form.authors }}获取出版社和作者信息。通过Django 的模板语言拆分成HTML 标记。
初始addbook.html写法:
Title
添加页面
addbook.html精简后写法:
Title
添加页面
显示效果:
三、引入ModelForm组件进一步改写
1、引入ModelForm组件改写BookForm类
views.py:
from django.forms import ModelForm
class BookForm(ModelForm):
class Meta:
model = Book
fields = "__all__" # 对所有字段转换
在视图函数中,定义一个类,比如就叫BookForm,这个类要继承ModelForm,在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的)。
注意对所有字段转换是写字符串,其他时候是写列表:
fields = ["title", "price"]
改写addbook视图函数:
def addbook(request):
form = BookForm() # 实例化BookForm
return render(request, "addbook.html", locals())
页面访问解析效果:
2、修改添加和编辑视图,处理post请求
def addbook(request):
if request.method == "POST":
form = BookForm(request.POST) # 实例化BookForm
if form.is_valid(): # 如果是合格的数据
form.save()
return redirect("/books/")
form = BookForm() # 实例化BookForm
return render(request, "addbook.html", locals())
def editbook(request, edit_book_id):
edit_book = Book.objects.filter(pk=edit_book_id).first()
if request.method=="POST":
# 默认是create操作,如果是想update,需要添加一个instance参数把要编辑的对象传递过来
form = BookForm(request.POST, instance=edit_book) # 传递需要更新的对象
if form.is_valid():
form.save()
return redirect("/books/")
# 创建form对象,传入参数就可以显示之前客户写入的值,ModelForm具备instance参数
form = BookForm(instance=edit_book) # 接收实例对象
return render(request, "editbook.html", locals())
注意:
(1)ModelForm的instance参数
可以接收实例对象,仅对edit视图做如下改写,就可以拿到客户之前提交的信息。
def editbook(request, edit_book_id):
# 去数据库中取出数据
edit_book = Book.objects.filter(pk=edit_book_id).first()
# 创建form对象,传入参数就可以显示之前客户写入的值,ModelForm具备instance参数
form = BookForm(instance=edit_book) # 接收实例对象
return render(request, "editbook.html", locals())
显示效果:
(2)处理POST请求,添加和编辑操作代码
if request.method == "POST":
form = BookForm(request.POST) # 实例化BookForm
if form.is_valid(): # 如果是合格的数据
form.save()
return redirect("/books/")
区别就在于一点:实例化form类的时候,是否传递instance参数。
如果不用ModelForm,编辑的时候要显示之前的数据,还得挨个取一遍值,如果ModelForm,只需要加一个instance=obj(obj是要修改的数据库的一条数据的对象)就可以得到同样的效果
保存的时候要注意,一定要注意有这个对象(instance=obj),否则不知道更新哪一个数据。默认情况下是执行create操作,如果是想update,需要添加一个instance参数把要编辑的对象传递过来。
(3)model和form之间耦合
ModelForm用起来是非常方便的,比如增加修改之类的操作。但是也带来额外不好的地方,model和form之间耦合了。
如果不耦合的话,form.save()方法也无法直接提交保存。 但是耦合的话使用场景通常局限用于小程序,写大程序就最好不用了。
(4)添加记录的方法——save()
保存数据的时候不需要挨个取数据了,直接save一下,就完成了取数据生成记录的操作。
form.model.objects.create(request.POST)
编辑功能的editbook视图函数,不需要做任何变动,save一下就执行了更新操作
form = BookForm(request.POST, instance=edit_book) # 传递需要更新的对象
if form.is_valid():
form.save() # edit_book.update(request.POST)
3、模板改写
由于编辑和添加模板中的form标签内的内容完全一样,将其拷贝出来放在新创建的form.html模板中:
在addbook.html和editbook.html中改写如下:
编辑页面
{% include 'form.html' %}
注意:
(1){% include %} 标签允许在(模板中)包含其它的模板的内容。 标签的参数是所要包含的模板名称,可以是一个变量,也可以是用单/双引号硬编码的字符串。
4、修改BookForm类,利用元类属性设置优化渲染
from django.forms import ModelForm
from django.forms import widgets as wid # 因为重名,所以起个别名
wid_text = wid.TextInput(attrs={'class':'form-control'})
required_msg = {'required':'不能为空'}
class BookForm(ModelForm):
class Meta:
model = Book
fields = "__all__" # 对所有字段转换
labels = {
"title": "书籍名称",
"price": "书籍价格",
"date": "日期",
"publish": "出版社",
"authors": "作者"
}
# widgets用法,比如把输入用户名的input框给为Textarea
widgets = { # 这个是参数widgets
"title": wid_text,
"price": wid_text,
"date": wid.TextInput(attrs={"class": "form-control", 'type': 'date'}),
"publish": wid.Select(attrs={"class": "form-control"}),
"authors": wid.SelectMultiple(attrs={"class": "form-control"}),
}
# error_messages用法:
error_messages = {
'title': {'required': "用户名不能为空", },
'price': {'required': "价格不能为空", },
'date': {'required': "日期不能为空", 'invalid': "日期格式错误"},
'publish': {'required': "出版社不能为空", },
'authors': {'required': "作者不能为空", },
}
(1)导入widgets模块用法
这是因为ModelForm有一个参数是widgets与之重名,所以需要给它一个别名:
from django.forms import widgets as wid # 因为重名,所以起个别名
在input标签添加form-contral样式,可以考虑在后台的widget里面添加,比如这样:
from django.forms import widgets as wid #因为重名,所以起个别名
widgets = {
"name":wid.TextInput(attrs={'class':'form-control'}),
"age":wid.NumberInput(attrs={'class':'form-control'}),
"email":wid.EmailInput(attrs={'class':'form-control'})
}
当然也可以在js中,找到所有的input框,加上这个样式,也行。
(2)使用error_messages参数修改错误提示
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
# error_messages用法:
error_messages = {
'title': {'required': "用户名不能为空", },
'price': {'required': "价格不能为空", },
'date': {'required': "日期不能为空", 'invalid': "日期格式错误"},
'publish': {'required': "出版社不能为空", },
'authors': {'required': "作者不能为空", },
}
5、修改form.html模板和addbook视图函数,添加错误信息显示
注意在模板中通过{{ field.errors.0 }}取到错误提示信息。.0表示只取第一条。视图函数也需要做修改才能显示错误信息:
def addbook(request):
form = BookForm() # 实例化BookForm
if request.method == "POST":
form = BookForm(request.POST) # 实例化BookForm
if form.is_valid(): # 如果是合格的数据
form.save()
return redirect("/books/")
# form = BookForm() # 实例化BookForm
return render(request, "addbook.html", locals())
需要注意的是,要把form=BookForm()这一步移到最上方,否则校验失败返回的form对象就变成没有值的form对象了,也可以改写为下面更清晰的写法:
def addbook(request):
if request.method == "POST":
form = BookForm(request.POST) # 实例化BookForm
if form.is_valid(): # 如果是合格的数据
form.save()
return redirect("/books/")
else:
# 检验失败,不写下面的语句,返回的form对象就变成没有值的form对象了
return render(request, "addbook.html", locals())
form = BookForm() # 实例化BookForm
return render(request, "addbook.html", locals())
显示效果:
四、前端form表单,后台forms组件
前端手写一个form表单,后台使用forms组件,进行校验也是可以的!!
注意:
名称
和 title = forms.CharField()views.py:
from django import forms
class BookForms(forms.Form):
title = forms.CharField()
price = forms.FloatField()
def addbook(request):
form = BookForms()
if request.method == 'POST':
form = BookForms(request.POST)
# form = BookForm({'title':'php','price':111,'xxx':'egon'})
if form.is_valid():
print('clean_data',form.cleaned_data)
# clean_data {'title': '水浒传', 'price': 123.0}
else:
print('error',form.errors)
return render(request,'addbook.html',locals())
addbook.html:
Title
添加书籍
五、modelform详解
Django之Form组件
Django-组件拾遗
Django-form表单
六、form组件补充
1、Django内置字段如下
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀
CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
BaseTemporalField(Field)
input_formats=None 时间格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允许空文件
ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换
empty_value= '' 空值的默认值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值
ComboField(Field)
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
SlugField(CharField) 数字,字母,下划线,减号(连字符)
...
UUIDField(CharField) uuid类型
...
2、Django内置插件
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
3、常用选择插件
# 单radio,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 单radio,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 单select,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 单select,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多选select,值为列表
# user = fields.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 单checkbox
# user = fields.CharField(
# widget=widgets.CheckboxInput()
# )
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
六、django请求流程
Django 和其他 Web 框架的 HTTP 处理的流程大致相同,Django 处理一个 Request 的过程是首先通过中间件,然后再通过默认的 URL 方式进行的。我们可以在 Middleware 这个地方把所有 Request 拦截住,用我们自己的方式完成处理以后直接返回 Response。
1、http协议
请求协议格式:请求首行,请求头,请求体!
响应协议格式:响应首行,响应头,响应体!
2、HTTP请求处理流程
1. 加载配置
Django 的配置都在 “Project/settings.py” 中定义,可以是 Django 的配置,也可以是自定义的配置,并且都通过 django.conf.settings 访问,非常方便。
2. 启动
最核心动作的是通过 django.core.management.commands.runfcgi 的 Command 来启动,它运行 django.core.servers.fastcgi 中的 runfastcgi,runfastcgi 使用了 flup 的 WSGIServer 来启动 fastcgi 。而 WSGIServer 中携带了 django.core.handlers.wsgi 的 WSGIHandler 类的一个实例,通过 WSGIHandler 来处理由 Web 服务器(比如 Apache,Lighttpd 等)传过来的请求,此时才是真正进入 Django 的世界。
3. 处理 Request
当有 HTTP 请求来时,WSGIHandler 就开始工作了,它从 BaseHandler 继承而来。WSGIHandler 为每个请求创建一个 WSGIRequest 实例,而 WSGIRequest 是从 http.HttpRequest 继承而来。接下来就开始创建 Response 了。
4. 创建 Response
BaseHandler 的 get_response 方法就是根据 request 创建 response,而具体生成 response 的动作就是执行 urls.py 中对应的view函数了,这也是 Django 可以处理“友好 URL ”的关键步骤,每个这样的函数都要返回一个 Response 实例。此时一般的做法是通过 loader 加载 template 并生成页面内容,其中重要的就是通过 ORM 技术从数据库中取出数据,并渲染到 Template 中,从而生成具体的页面了。
5. 处理 Response
Django 返回 Response 给 flup,flup 就取出 Response 的内容返回给 Web 服务器,由后者返回给浏览器。
总之,Django 在 fastcgi 中主要做了两件事:处理 Request 和创建 Response,而它们对应的核心就是“ urls 分析”、“模板技术”和“ ORM 技术”。
3、流程图说明
如图所示,一个 HTTP 请求,首先被转化成一个 HttpRequest 对象,然后该对象被传递给 Request 中间件处理,如果该中间件返回了Response,则直接传递给 Response 中间件做收尾处理。否则的话 Request 中间件将访问 URL 配置,确定哪个 view 来处理,在确定了哪个 view 要执行,但是还没有执行该 view 的时候,系统会把 request 传递给 view 中间件处理器进行处理,如果该中间件返回了 Response,那么该 Response 直接被传递给 Response 中间件进行后续处理,否则将执行确定的 view 函数处理并返回 Response,在这个过程中如果引发了异常并抛出,会被 Exception 中间件处理器进行处理。