jango框架:forms组件渲染标签、forms组件展示信息、forms组件校验补充、forms组件参数补充、forms组件源码剖析、modelform组件、django中间件

一、forms组件渲染标签

forms组件的渲染标签比较强大, 主要有三种方式

部分代码如下:

使用之前需要在视图层定义函数调用我们自己定义的form模型表

class MyForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=8, label='用户名', initial='假阳比真阳更可怕',
                               )
    password = forms.CharField(min_length=3, max_length=8, label='密码',
                               )
    confirm_pwd = forms.CharField(min_length=3, max_length=8, label='确认密码')

    email = forms.EmailField(required=False)


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())


html文件中的代码
<body>
{#<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>#}
<form action="" method="post" novalidate>
        {% for form in form_obj %}
            <p>
                {{ form.label }}
                {{ form }}
                <span>{{ form.errors.0 }}</span>
            </p>
        {% endfor %}
    <input type="submit">
</form>


</body>

forms组件渲染标签的方式1

<p>forms组件渲染标签的方式1(封装程度过高 扩展性差 主要用于本地测试):</p>
    {#    {{ form_obj.as_p }}#}
    {#    {{ form_obj.as_ul }}#}
    {#    {{ form_obj.as_table }}#}

img

img

图片中的名称之所以是中文,因为在后端设置了label属性,然后在前端使用他充当标签名称。

通过第二张图片,我们可以发现第一种渲染方式,label标签和input是直接绑定的,我们并不能设置前端中Username的渲染方式。同时我们也可以看到所有后端设置过的字段的限制条件,都是可以在前端找到具体代码的。

forms组件渲染标签的方式2

<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 }}#}

img

img

通过上面的图片我们可以发现第二种方法内如果不设置label标签就不自动渲染label标签了,就算我们添加了前端代码进行展示,也可以对他进行自定义

img

后端代码给前端form标签添加样式

如果我们想要在后端给前端的标签添加样式,需要在forms组件的模型表中添加,代码如下:(别忘了导入bootstrap)

class RegForm(forms.Form):
    # 接下来的定义需要与模型表的字段类型一一对应
    username = forms.CharField(
        max_length=15,  # 用户名最大长度为15
        min_length=3,   # 用户名的最小长度为3
        label='用户名',  # 渲染出在页面上的标签的名字
        widget=forms.TextInput(attrs={'class': 'form-control'})
    )
    password = forms.CharField(
        max_length=15,  # 密码最大长度为15
        min_length=3,  # 密码的最小长度为3
        label='密码',  # 渲染出在页面上的标签的名字
        widget=forms.PasswordInput(attrs={'class': 'form-control'}),
    )
    re_password = forms.CharField(
        max_length=15,  # 密码最大长度为15
        min_length=3,  # 密码的最小长度为3
        label='确认密码',  # 渲染出在页面上的标签的名字
        widget=forms.PasswordInput(attrs={'class': 'form-control'}),

    )
    email = forms.EmailField(
        label='邮箱',
        widget=forms.EmailInput(attrs={'class': 'form-control'})
    )

ps:如果想要批量添加样式,需要用派生方法自定义一个双下init方法,在创建对象、添加字段信息的时候统一添加

class RegForm(forms.Form):
    # 接下来的定义需要与模型表的字段类型一一对应
    username = forms.CharField(
        max_length=15,  # 用户名最大长度为15
        min_length=3,  # 用户名的最小长度为3
        label='用户名',  # 渲染出在页面上的标签的名字
    )
    password = forms.CharField(
        max_length=15,  # 密码最大长度为15
        min_length=3,  # 密码的最小长度为3
        label='密码',  # 渲染出在页面上的标签的名字
    )
    
    ...

from django.forms import widgets

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })

forms组件渲染标签的方式3

虽然第二种方法扩展性高,但是当字段个数很多的时候,工作量太大,而第三种渲染方式就能解决这个问题。

第三种方式是用for循环的方式获取字段信息,然后进行渲染。封装程度较高,同时扩展性高,编写简单。

python

<p>forms组件渲染标签的方式3(封装程度较高 扩展性高 编写简单 推荐使用)</p>
    {#    {% for form in form_obj %}#}
    {#        <p>#}
    {#            {{ form.label }}#}
    {#            {{ form }}#}
    {#        </p>#}
    {#    {% endfor %}#}

注意事项

forms组件之负责渲染获取用户数据的标签 也就意味着form标签与按钮都需要自己写

前端的校验是弱不禁风的 最终都需要后端来校验 所以我们在使用forms组件的时候可以直接取消前端帮我们的校验

<form action="" novalidate>

二、forms组件展示信息

当前端获取的数据值不符合我们后端设置的条件时,我们应当把判断的代码放到后端(毕竟前端的代码可以该,不靠谱),然后返回错误信息给前端,给用户提示。

实现这个需求,它的代码中的关键点就是用forms组件的模型表产生对象。当前端发送GET请求的时候定义一个变量名,调用模型表产生一个空对象,当我们接收数据的时候(POST),再次产生一个对象,这个对象名称跟GET请求下的空对象名称一致。这样设置后,前端就不需要进行更多的设置,当我们发送GET请求的时候不会有报错信息,所以不会展示form_obj.errors的信息,当请求为POST的时候如果有报错信息就会展示报错提示信息。

针对这个需求,实现代码如下:

后端不同请求返回的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())

html部分代码
{% for form in form_obj %}
            <p>
                {{ form.label }}
                {{ form }}
                <span>{{ form.errors.0 }}</span>
            </p>
{% endfor %}

同时我们也可以用正则进行筛选,然后设置错误提示信息

先导入模块

from django.core.validators import RegexValidator
    phone = forms.CharField(
                    validators=[
                                RegexValidator(r'^[0-9]+$', '请输入数字'),
                                RegexValidator(r'^159[0-9]+$', '数字必须以159开头'),
                                ],
    )

针对错误信息的提示可以修改成各国语言

方式1:自定义内容

	在模型表中给字段对象添加errors_messages参数

    age = forms.IntegerField(min_value=0, max_value=200, label='年龄',
                             error_messages={
                                 'min_value':'年龄不能小于0岁',
                                 'max_value':'你他喵的200岁以上?',
                                 'required':'年龄不能为空 你妹的'
                             }
                             )
    email = forms.EmailField()

方式2:修改系统语言环境

	from django.conf import global_settings  django内部真正的配置文件
    在这个文件内部我们可以看到每个语言对应的字符,之后我们在配置文件中修改即可更改语言环境

三、forms组件校验补充

forms组件针对字段数据的校验 提供了三种类型的校验方式(可以一起使用)
第一种类型:直接填写参数 max_length
第二种类型:使用正则表达式 validators
第三种类型:钩子函数 编写代码自定义校验规则

这里需要特别说一下三种校验方式的执行顺序,是从上到下依次执行的,其中钩子函数是先执行局部钩子再执行全局钩子。

局部钩子

所谓的局部钩子,就是调取一部分数据出来,对这部分数据设置自定义的筛选条件,筛选后,对应的函数需要把数据return会去(可以看成把某个产品的一部分取出来检查,然后检查完了要塞回去)。

全局钩子

全局钩子类似局部钩子,唯一不同的是,他是把所有的数据都拿来进行校验了,所以在设置返回值的时候,我们需要把所有的数据(也就是要把数据对象返回)都返回。

代码:

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'})

五、forms组件源码剖析

切入口:form_obj.is_valid()
我们需要知道,只有执行了is.valid方法后才能使用errors方法和cleaned_data查看数据的校验情况。

因此我们从这里点进源码,可以发现is_valid()这个函数返回值是self.is_bound和self.errors,两者用and连接,而结果是通过判断两者的数据值所代表的布尔值得出的。

    def is_valid(self):
        """Return True if the form has no errors, or False otherwise."""
        return self.is_bound and not self.errors

而is_bound是BaseForm这个类中的双下init的一个属性,errors是BaseForm的一个内置方法。这BaseForm是Form的父类,而这个form就是模型表的类在创建时候继承的父类。同时我们查看源码我们发现这个Form内部没有代码只是继承了BaseForm。

接着我们深入研究errors函数,我们发现这个函数内部的if语句的判断条件时self._errors这个属性,并且在没有传参设置的情况下是None,回到errors函数这里,if条件在_errors属性为None的时候会执行full_clean方法。这里的代码可以在BaseForm下方一些的方法中找到,如果看不懂代码在干嘛也没事,我们可以看注释嘛。

"""
Clean all of self.data and populate self._errors and self.cleaned_data.
"""

如果想要仔细研究内部的代码我们会发现内部代码只是在设置三个隐藏属性或方法的值。

首先是定义了两个空的字典或对象.

        self._errors = ErrorDict()
        self.cleaned_data = {}

其中第一个隐藏方法_clean_fields是获取内部所有的字段名称和字段对象(for循环的name代表的就是字段名称,field就是字段对象)。接着我们看下面的异常捕获代码,可以发现下面的代码就是用反射的方式获取钩子函数的执行结果,然后把结果返回出去,如果出现异常就会使用ValidationError这个方法进行报错(因此我们可以在外面使用这个方法进行modelform组件的主动报错)

第二个隐藏方法_clean_form是直接在内部上异常捕获了,在第一块代码中发现他执行了self.clean,也就是全局钩子,通过他的异常捕获我们发现如果我们没有传钩子参数,他会给我们传一个cleaned_data回去,因为不传这个参数就获取不到钩子函数的结果了。这也解释了为什么我们要在返回值处返回勾出来的数据对象,同时当我们不返回这个对象的时候代码也会帮我们自动返回数据对象。

第三个方法则是什么都没写。

通过注释我们得知他就是清空这三个属性的作用。因此我们可以推断出在is_valid方法内返回值中的self.errors因为默认情况下值为空所以布尔值为False,因为返回值中的判断条件有个not在前面所以通常来说后面部分的布尔值是True。

接下来研究一个前面部分的is_bound方法的结果,我们可以发现他是类代码创建对象是设置的一个属性,主要是用于判断是否在data和files这里接收到了参数,如果接收了参数就会通过or条件判断布尔值,而data和files是最前面的两个位置参数,因此通常来说数据都是这两个参数接收的,调用的时候布尔值通常都是True。

因此我们可以得知,is_valid方法是通过钩子函数进行校验布尔值结果的,返回的结果虽然是布尔值,但这只是表象。

六、modelform组件

什么是modelform组件?

 这是一个神奇的组件,通过名字我们可以看出来,这个组件的功能就是把model和form组合起来。先来一个简单的例子来看一下这个东西怎么用:

  比如我们的数据库中有这样一张学生表,字段有姓名,年龄,爱好,邮箱,电话,住址,注册时间等等一大堆信息,现在让你写一个创建学生的页面,你的后台应该怎么写呢?

首先我们会在前端一个一个罗列出这些字段,让用户去填写,然后我们从后天一个一个接收用户的输入,创建一个新的学生对象,保存起来。

  用之前学的方式创建模型表,很慢很繁琐。

  我们现在有个更方便的方法:ModelForm

使用校验性组件的目的

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

常用参数介绍

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

代码展示

modelform提供了一个save方法,可以帮我们保存数据。(可以代替ORM的create和update操作)

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())

七、django中间件

什么是中间件?

官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。

我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下图的MIDDLEWARE配置项。

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',
]

如何自定义中间件

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

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

3.在类中编写五个可以自定义的方法(主要的是process_request和process_response)

python

process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)

需要掌握的

process_request

process_request有一个参数,就是request,这个request和视图函数中的request是一样的(在交给Django后面的路由之前,对这个request对象可以进行一系列的操作)。

由于request对象是一样的,所以我们可以对request对象进行一系列的操作,包括request.变量名=变量值,这样的操作,我们可以在后续的视图函数中通过相同的方式即可获取到我们在中间件中设置的值。

它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。

总结:

  1. 中间件的process_request方法是在执行视图函数之前执行的。
  2. 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
  3. 不同中间件之间传递的request都是同一个对象
  4. 如果该方法自己返回了HttpResponse对象那么不再往后执行而是直接原路返回

process_response

多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。

定义process_response方法时,必须给方法传入两个形参,request和response。request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象(也就是说这是Django后台处理完之后给出一个的一个具体的视图)。该方法的返回值(必须要有返回值)也必须是HttpResponse对象。如果不返回response而返回其他对象,则浏览器不会拿到Django后台给他的视图,而是我的中间件中返回的对象

总结:

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

2.该方法有两个先request和response 形参response指代的就是后端想要返回给前端浏览器的数据 该方法必须返回该形参 也可以替换

3.process_response方法在视图函数执行之后执行。

ps:如果在执行process_request方法的时候直接返回了HttpResponse对象那么会原路返回执行process_response 不是执行所有后续中间件的process_response方法

需要了解的

process_view(self, request, view_func, view_args, view_kwargs)

该方法有四个参数

request是HttpRequest对象。

view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)

view_args是将传递给视图的位置参数的列表.

view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。

Django会在调用视图函数之前调用process_view方法。

它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,那么将不会执行Django的视图函数,而是直接在中间件中掉头,倒叙执行一个个process_response方法,最后返回给浏览器

ps:process_view方法是在Django路由系统之后,视图系统之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的

process_exception

process_exception(self, request, exception)

该方法两个参数:

一个HttpRequest对象

一个exception是视图函数异常产生的Exception对象。

这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

process_template_response

process_template_response(self, request, response)

它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。

process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。

4.一定要在配置文件中注册后中间件才可以生效。

posted @ 2023-01-04 21:33  wwwxxx123  阅读(92)  评论(0编辑  收藏  举报