forms组件渲染标签 form表单展示信息 forms组件校验方式 form组件源码 modelform组件 django自定义中间件

forms组件渲染标签

表单组件的核心思想是:
通过后端的一个表单类对应前端的一个表单。表单类中的一个字段,对应前端的一个input输入框。
这有些类似于orm的将后端类对应数据库中的一张表。

关于表单组件的渲染方式有三种:

<p>forms组件渲染标签的方式1(封装程度过高 扩展性差 主要用于本地测试):</p>
    {#    {{ form_obj.as_p }}#}
    {#    {{ form_obj.as_ul }}#}
    {#    {{ form_obj.as_table }}#}
<p>forms组件渲染标签的方式2(封装程度过低 扩展性高 编写麻烦)</p>
    {#    {{ form_obj.username.label }}#}
    {#    {{ form_obj.username }}#}
    {#    {{ form_obj.age.label }}#}
    {#    {{ form_obj.age }}#}
    {#    {{ form_obj.email.label }}#}
    {#    {{ form_obj.email }}#}
<p>forms组件渲染标签的方式3(封装程度较高 扩展性高 编写简单 推荐使用)</p>
    {#    {% for form in form_obj %}#}
    {#        <p>#}
    {#            {{ form.label }}#}
    {#            {{ form }}#}
    {#        </p>#}
    {#    {% endfor %}#}

注意事项
	forms组件之负责渲染获取用户数据的标签 也就意味着form标签与按钮都需要自己写
	前端的校验是弱不禁风的 最终都需要后端来校验 所以我们在使用forms组件的时候可以直接取消前端帮我们的校验
	<form action="" novalidate>

使用Django的forms组件需要在视图层提前导入:from django import forms
然后自己写一个类,这个类继承:forms.Form

image-20221221085951842

在视图函数,使用MyForm类示例化表单对象form_obj,
前端可以通过模板语法识别出表单对象,自动生成html代码,再渲染到页面上

MyForm类中有多少个字段,前端页面就会出现多少个输入框。

方式一:全自动渲染表单

as_p

通过form对象.as_p:

image-20221221090105300

可以得到如下效果:

image-20221221090046523

只需使用对象的as_p方法,就可以通过模板渲染,在前端页面产生3个输入框。

查看通过表单对象生成的html代码:
image-20221221090216913
可见每个input输入框都是包裹在p标签内的。
并且输入框依据我们的表单类字段,在前端做了很多限制。
比如maxlength=8控制前端输入的用户名长度不能超过8个字符。

as_ul

通过form对象.as_ul:
image-20221221154307182

查看效果:
image-20221221154409379
可见每个input输入框都是包裹在li标签内的。

as_table

通过form对象.as_table:
image-20221221154858838
查看效果:image-20221221154835890

可见input标签、label标签都没有被任何标签包裹。

补充:
在后端我们可以用print方法打印表单对象,对象被打印,会执行双下str方法:
image-20221221155312631
因此在后端打印表单对象,会给你展示as_table方法返回的html字符串。

全自动渲染表单的特点:
封装程度过高、扩展性很差、主要用于本地测试(快速)。

表单类的label标签

想把输入框前面的提示信息变成中文,可以给字段添加label参数(这个label不填默认值是字段名):

image-20221221090653873

查看效果:
image-20221221090635501

方式二:手动渲染

手动渲染也就是不动用上面的三种全自动方法,而是调用表单类中的字段对象。
获取字段username的label属性对应的值:
image-20221221090905068

查看效果:

image-20221221163316261

可见:
表单对象.字段名.label ---> 字段名字符串
表单对象.字段名 ---> input输入框

手动渲染的特点:
封装程度低 扩展性高 编写麻烦。
表单输入框多的情况下,不适用。

方式三:for循环表单对象(推荐)

封装程度较高 扩展性高 编写简单 推荐使用。

对form_obj进行for循环:
image-20221221091142490

在表单类中字段多的情况下:
image-20221221091207644

也可以快速生成多个输入框:
image-20221221091214352

对form表单做for循环,相当于拿出了每一个字段对象。

查看源码

表单对象可被for循环,证明是表单可迭代对象,
其表单类中肯定有__iter_方法。
查看源码可知表单类中重写了__iter__方法:
image-20221221170010122

先查看self.fields是什么:
image-20221221170256548

发现是self.fields是个有序字典,for循环有序字典,可以得到字典的键('username'、'age'、'email')。
所以第一次循环中,name就是字符串'username'。
然后是一个yield关键字,也就是说每经历一次for循环就返回一次yield后面的值。后面的值是:self[name]
此时的self是表单对象,对象想使用中括号取值,需要在类中定义__getitem__方法。

查看__getitem__:

image-20221221172655877

Return a BoundField with the given name.
通过得到的name,返回一个绑定字段。

后端查看:
image-20221221173246452

也就是for循环表单对象会得到很多个绑定字段。
绑定字段也就是表单类中的字段。

渲染标签的注意事项

form组件只渲染获取用户输入的部分(input标签、label标签)
没有生成form标签、提交按钮,这些标签都需要自己写。

image-20221221091557173

form组件帮你写了前端的数据校验功能:

image-20221221091619019

image-20221221091643234

如图是给标签添加属性实现的:image-20221221091726154

我们希望前端不要进行任何的校验:
需要给form标签添加参数novalidate(关闭校验)。
image-20221221091820930

前端的校验是弱不禁风的 最终都需要后端来校验。

form表单展示信息

表单类校验POST请求

通常表单提交数据我们使用request.POST方法获取:

image-20221221093734967

这里我们把request.POST字典传入MyForm类,直接让form类帮我们校验:
image-20221221093850340

后端校验表单信息:
满足字段限制条件的信息,会被放在cleaned_data。
校验失败的会放入errors字典。

image-20221221093948703

后端展示错误信息:
image-20221221094134614

form_obj.error实际会得到一个错误字典ErrorDict()
打印错误字典会调用其__str__:

image-20221221192115516

前端展示错误信息

也可以将这个错误信息ErrorDict传递到前端,在前端显示报错信息:
image-20221221094236862

如下图,
不做数据提交时(get请求),form_obj是个空对象。
数据提交之后(post请求),form_obj是包含数据的,此时span标签内就可以展示错误信息。
两次请求,form_obj这个变量,所绑定的值不一样。
这样提交表单之后,前端的span标签就会发送变化,提示错误信息。

image-20221221094441032

注意:这两个变量名是必须一样的。否则无法实现此效果。

防止打乱前端布局

防止打乱前端布局(估计是因为ErrorDict被调用会产生ul标签):
image-20221221094752449

加上一个0,就不会自动渲染ul标签而打乱前端布局。

后端查看form.errorsimage-20221221094950496

form.error.0的意思就是只获取标签中的文本。

错误提示信息自定义

错误提示信息自定义,使用error_massages参数:
image-20221221095148637

如果校验失败,会展示我们自定义的错误信息:
image-20221221095219027

更多例子:
image-20221221095426897

image-20221221095720707

切换语言环境

表单组件默认内部错误提示信息都是英文,我们手动一个一个自定义错误信息很麻烦,所以可以选择切换django语言环境。

查看django具备的语言环境:
from django.conf import global_settings查看global_settings文件(django底层配置文件)。

image-20221221201323884

修改settings文件中的LANGUAGE_CODE:
image-20221221201532764

global_settings和settings的关系:
global_settings相当于是settings的一个父集。
django暴露给你可以定义的配置,都是来源于global_settings。
settings定义的配置,会对global_settings的配置进行覆盖。

image-20221221202026259

代码:

后端不同请求返回的forms对象一定要是相同的变量名
def ab_forms_func(request):
    # 1.产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        form_obj = MyForm(request.POST)  # request.POST可以看成是一个字典 直接传给forms类校验 字典中无论有多少键值对都没关系 之在乎类中编写的
        if form_obj.is_valid():  # 校验数据是否合法
            print(form_obj.cleaned_data)
        else:
            print(form_obj.errors)
    # 2.将该对象传递给html文件
    return render(request, 'formsPage.html', locals())


{% for form in form_obj %}
            <p>
                {{ form.label }}
                {{ form }}
                <span>{{ form.errors.0 }}</span>
            </p>
{% endfor %}
针对错误信息的提示可以修改成各国语言

方式1:自定义内容
	给字段对象添加errors_messages参数
	username = forms.CharField(min_length=3, max_length=8, label='用户名',
                               error_messages={
                                   'min_length': '用户名最少三个字符',
                                   'max_length': '用户名最多八个字符',
                                   'required': '用户名不能为空'
                               }
                               )
方式2:修改系统语言环境
	from django.conf import global_settings  django内部真正的配置文件

forms组件校验方式

之前我们学到对数据校验的方法较为简单,有很多需求无法实现,比如:
针对手机号怎么校验?
form组件提供了3种校验方式(可以一起使用):

第一种:直接填写参数

比如:

from django import forms
class MyForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8)
    age = forms.IntegerField(min_value=0, max_value=200)
    email = forms.EmailField()

第二种:使用正则表达式 RegexValidator

使用正则表达式需要导入正则校验器:
from django.core.validators import RegexValidator

给字段添加validators参数可以使用正则校验器 并且可以添加多个正则校验条件:image-20221221100629232

第三种:钩子函数(重要)

钩子函数:编写代码自定义校验规则

为什么叫钩子函数?
一个数据正常的在代码中传递,你可以通过钩子函数,将数据给勾上来,做一些处理再放回去。有点类似于面向对象的super方法。

首先,钩子函数也要写在表单类中 。钩子函数有两种分类。

局部钩子

每次只检验一个字段数据。
需求:校验用户名是否在数据库中存在、两次密码输入比对。
如下有三个字段:用户名、密码、确认密码:

image-20221221101244921

想做两次输入密码的比对,这是无法通过给字段加参数实现的。
加参数只能校验单个字段是否输入合规。

钩子函数的触发条件

用户输入的数据经过字段里面的参数校验之后,才会触发钩子函数。
触发优先级:max_length > 正则校验器 > 钩子函数
钩子函数是基于cleaned_data做操作的,是最后一道关卡。

image-20221221101751298

注意:钩子函数必须把username返回出去。

cleaned_data是一个字典如:
{'username': 'jason', 'age': 18, 'email': '123@qq.com'}
通过add_error方法给username字段添加错误信息。
前端就可以通过{{ form.errors.0 }}展示你给这个字段添加的错误信息:
image-20221221204959397

查看效果:

image-20221221205520320

全局钩子

一次校验多个字段数据。
钩子函数的函数名:clean_字段名这种就是只校验一个字段。
如下使用clean函数名,就可以一次性校验多个字段(所有cleaned_data字段):

image-20221221101952609

注意这里使用add_error方法将错误信息添加到了confirm_pwd字段。
全局钩子最后必须将数据全部返回:self.cleaned_data

代码:

forms组件针对字段数据的校验 提供了三种类型的校验方式(可以一起使用)
	第一种类型:直接填写参数 		  max_length
	第二种类型:使用正则表达式	     validators	
 	第三种类型:钩子函数				编写代码自定义校验规则
	class MyForm(forms.Form):
        username = forms.CharField(min_length=3, max_length=8)
        password = forms.CharField(min_length=3, max_length=8)
        confirm_pwd = forms.CharField(min_length=3, max_length=8)
        # 钩子函数>>>:校验的最后一环 是在字段所有的校验参数之后触发
        # 局部钩子:每次只校验一个字段数据       校验用户名是否已存在
        def clean_username(self):
            username = self.cleaned_data.get('username')
            if username == 'jason':
                self.add_error('username', '用户名jason已存在')
            return username

        # 全局钩子:一次可以校验多个字段数据     校验两次密码是否一致
        def clean(self):
            password = self.cleaned_data.get('password')
            confirm_pwd = self.cleaned_data.get('confirm_pwd')
            if not password == confirm_pwd:
                self.add_error('confirm_pwd', '两次密码不一致')
            return self.cleaned_data

forms组件参数补充

min_length			最小字符
max_length			最大字符
min_value			最小值
max_value			最大值
label				字段注释
error_messages		错误提示
validators			正则校验器
initial				默认值
required			是否必填
widget				控制标签的各项属性

widget=forms.widgets.PasswordInput(attrs={'class': 'form-control', 'username': 'jason'})

initial 默认值

给输入框(字段)添加默认值:
image-20221221210555107

requied

规定这个字段是否可以不填。required参数不填的情况默认为True。
针对于设置required = False的字段,没必要写钩子函数校验,因为在用户不输入的情况下,cleaned_data是没有值的,钩子函数也就勾不到。
required = False的字段的意思是:
这个字段可以不写,但是你填了值,就要按照规则校验。

image-20221221211343831

widget 添加样式

怎么给渲染好的表单添加样式?
widget 控制生成标签的各项属性:
建议导入:from django.forms import widgets否则pycharm将没有代码补全。
image-20221221103157698

想把表单输入框变成密文type='password'
image-20221221103255850

widget默认是type='text'
image-20221221103332287

使用attrs参数给input标签添加默认属性或者自定义属性:

image-20221221103515135

一次性给标签添加多个class值:
image-20221221103724122

渲染更多输入框

单选Select:

class LoginForm(forms.Form):
    ...
    hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )

radioSelect:

    gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

多选Select:

class LoginForm(forms.Form):
    ...
    hobby = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )

单选checkbox:

class LoginForm(forms.Form):
    ...
    keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

多选checkbox:

class LoginForm(forms.Form):
    ...
    hobby = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

form组件源码

从is_valid方法入手:
is_valid用于校验表单数据是否全部合规。其返回值是bool值。

image-20221221105849585

is_valid的返回值:self.is_bound and not self.errors 当左右两边的条件都为True,is_valid才会返回True。
self.is_bound:
image-20221221214525572

只要我们给表单类传入了数据,此时表单类的__init__会用data参数接受这个数据并且将其赋给self.is_bound,其生成的表单对象的self.is_bound肯定是有值的。

not self.errors:

image-20221221215031732

self.errors的返回值是self._errors
self._errors的值默认为None,所以肯定会走if分支。

image-20221221215150934

full_clean():全部清洗、校验数据的方法。

image-20221221215958532

  1. full_clean函数内将ErrorDict赋值给self._errors
  2. 由于self.is_bound有值,所以不走if分支。

form组件核心方法

image-20221221220055414

_clean_fields 字段校验 局部钩子校验

image-20221221220554286

self.fields:拿到字段类中的所有字段名,字段对象。self.fields是个有序字典。这里的value是你的字段获取到的用户传过来的数据。
image-20221221220628551

这句话帮你做数据校验。

image-20221221220835830

如果是文件字段,需要做额外的操作。
如果是普通字段执行field.clean(value)
value是你的字段获取到的用户传过来的数据。

image-20221221221020566

如果校验成功,会添加到cleaned_data。
如果校验失败,会有异常捕获,并且将报错信息添加到errors。

在把数据添加到cleaned_data的代码后面,紧接着就是局部钩子:
image-20221221221255549

cleaned_data的数据在局部钩子的上面就准备好了。
此时的name是字段名,如果对象中有方法clean_字段名。那就使用反射getattr拿到这个局部钩子方法,然后加括号执行。将钩子函数的返回值重新添加到cleaned_data中。

局部钩子是写在异常捕获里面的:
所以我们可以在局部钩子里主动报错:
主动报错需要导入模块:
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError

image-20221221222134666

你主动报错的报错信息,会被django源码捕获,然后通过add_error方法,添加到errors信息里。
可以在前端使用{{ form_obj.errors.0 }} 显示该报错信息

_clean_form

这里通过self.clean()调用了我们在类中写的全局钩子(如果我们写了的话)

image-20221221222602780

self.clean是全局钩子。一旦报错也会使用add_error添加报错信息。
如果全局钩子不返回cleaned_data,源码底层会自己找一个cleaned_data补全。

钩子函数执行顺序:先局部钩子、后全局钩子

_post_clean

image-20221221223135991

这个方法留着以后做扩展,什么也没写。

modelform组件

"""
我们学习校验性组件的目的 绝大部分是为了数据录入数据库之前的各项审核
forms组件使用的时候需要对照模型类编写代码 不够方便
"""
forms组件的强化版本 更好用更简单更方便!!!
from django import forms
from app01 import models


class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        labels = {
            'username':'用户名'
        }


def ab_mf_func(request):
    modelform_obj = MyModelForm()
    if request.method == 'POST':
        modelform_obj = MyModelForm(request.POST,instance=User_obj)
        if modelform_obj.is_valid():
            modelform_obj.save()  # models.UserInfo.objects.create(...)/update(...)
        else:
            print(modelform_obj.errors)
    return render(request, 'modelFormPage.html', locals())

我们往模型层的类中添加数据时,需要进行校验:

image-20221221223902738

modelform组件将model类和form组件组合在一起:(UserInfo是模型层的一个类)
image-20221221111909332

在MyModelForm类中声明你要对UserInfo这张表做校验。

对表中所有的字段都想校验,需要添加fields = '__all__'
image-20221221111950683

使用方式类似于form组件,生成modelform对象:

image-20221221112053736

前端通过表单对象生成表单:

image-20221221112138309

获取前端提交的数据:校验是否合规。

image-20221221112222567

好处:针对一张字段很多的模型表做数据校验时,不需要在表单类中填写很多字段。modelform类帮你一次搞定所有字段。

labels参数 widgets参数

作用和表单类中的label参数、widget参数是一样的。

class BookForm(forms.ModelForm):

    class Meta:
        model = models.Book
        fields = "__all__"
        labels = {
            "title": "书名",
            "price": "价格"
        }
        widgets = {
            "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
        }

class Meta下常用参数:

model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息

封装orm save方法

如果经过is_valid()校验,发现数据没有问题:

image-20221221112648217

可以直接使用save方法,将数据保存到数据库。

使用POST更新数据:

image-20221221112806382

如果给ModelForm传参数时,添加了参数instance,那么再使用save方法就不是创建数据而是修改数据。
以我们的用户信息表举例,一个对象对应着一条用户数据。将这个用户对象传入instance,再使用save方法,将会更新数据库中的某一条用户数据。

django中间件

img

django默认有七个中间件 并且还支持用户自定义中间件
中间件主要可以用于:网站访问频率的校验 用户权限的校验等全局类型的功能需求
  
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
'''
什么时候用:全局相关功能时。
用户访问频率校验:中间件查看IP,计算访问频率。
什么是用户权限? 
在web领域中权限就是url vip才能访问某个url
没有访问某网址的权限只能买vip,
服务器给这个账号添加可以访问的路由 也就是添加权限
'''

django用字符串的形式导模块:
image-20221221230714895

查看源码发现,这些中间件都继承MiddlewareMixin,并且基本上都有process_requestprocess_response方法:
image-20221221114206515

自定义中间件

如何自定义中间件
	1.创建存储自定义中间件代码的py文件或者目录(如果中间件很多)
	2.参考自带中间件的代码编写类并继承
 	3.在类中编写五个可以自定义的方法
    	需要掌握的
        	  process_request
            	1.请求来的时候会从上往下依次经过每一个注册了的中间件里面的该方法 如果没有则直接跳过
            	2.如果该方法自己返回了HttpResponse对象那么不再往后执行而是直接原路返回
 				process_response
             	1.响应走的时候会从下往上依次经过每一个注册了的中间件里面的该方法 如果没有则直接跳过
             	2.该方法有两个先request和response 形参response指代的就是后端想要返回给前端浏览器的数据 该方法必须返回该形参 也可以替换
             '''如果在执行process_request方法的时候直接返回了HttpResponse对象那么会原路返回执行process_response 不是执行所有'''
       需要了解的
           	 process_view
            process_exception
            process_template_response
	4.一定在配置文件中注册中间件才可以生效

django支持自定义中间件,自定义中间件中支持定义五个方法。process_requestprocess_response方法也都支持自定义。

1.创建存储自定义中间件代码的py文件或者目录(如果中间件很多)

image-20221221120057551

2.参考自带中间件的代码编写类并继承
image-20221221231531583

3.在类中编写五个可以自定义的方法

需要掌握的:
    process_request
    process_response
需要了解的:	
    process_view
    process_exception
    process_template_response

从上往下重要程度依次降低。
中间件要在settings注册之后才能生效:
image-20221221120549270

process_request

当向服务端发送请求,请求的途径路线及顺序:

image-20221221120811194

请求来的时候 会从上往下依次经过每个中间件的 process_request方法。
image-20221221231952956

当这些process_request方法全部执行完,才能进入路由层,进入视图层。
中间件中,也能获取到request对象的所有数据:
image-20221221232139723

可以进行各种操作,添加逻辑等。

如果process_request方法自己返回了Httpresponse对象。
image-20221221121325928

就会让请求原路返回,不能继续进入下一个中间件。

process_response

process_response需要两个形参:requestresponse

image-20221221121710323

并且如果形参里携带response就必须要把这个responce返回。

请求来了之后查看执行顺序:
image-20221221121724555

响应走的时候会从下往上依次经过每一个注册了的中间件里面的该方法 如果没有则直接跳过。

image-20221221233957539

形参response指代的就是后端想要返回给前端浏览器的数据。后端发送的数据,会赋值给中间件的response。如果某个中间件不返回response,后面的中间件也就拿不到response,最后服务器也不会返回响应。

可以在中间件玩狸猫换太子:
在中间件进行一些额外的操作,修改后端返回的数据,也就是用中间件的response代替视图函数的HttpResponse。

特殊情况:
请求经过process_request方法时,process_request直接拒绝该请求,不让其进入下一个中间件。那此时响应会如何发送回客户端?

中间件响应的返回方式

两种方式:
1.执行拒绝该请求的中间件的process_response,以及上面中间件的process_response
2.执行所有的process_response

image-20221221122251040

这个操作在不同的框架里是不一样的。

在django里面是第一种方式:

在中间件001被拒绝,就从中间件001的process_response返回:image-20221221122406874

基于当前中间件的process_response原路返回
对于flask会执行所有中间件的process_response。

posted @ 2022-12-21 23:47  passion2021  阅读(91)  评论(2编辑  收藏  举报