🍖Django之forms组件

一.Django的forms组件应用场景

1.应用

  • 一般应用于前端的登入、注册界面, 对用户输入的字段进行校验, 快速的判断用户输入的内容是否合法, 并返回信息

2.为何不在前端直接使用JS进行校验

  • 前端的校验可以没有, 但后端的校验必须要有
  • 因为前端的校验弱不禁风, 有很多种方式可以伪装成浏览器发送请求传递数据
  • 或者通过爬虫程序绕过前端页面直接朝后端提交数据

二.forms组件的基本使用(步骤)

  • 导入forms组件
  • 定义一个类, 并继承Form
  • 在类中书写要校验的字段, 字段的属性就是要校验的规则
  • 实例化得到一个Form对象, 把要校验的数据传入
  • 调用[form对象].is_valid( )方法进行校验, 校验通过返回True
  • 校验通过调用[form对象].cleaned_data获得校验后的数据
  • 校验失败调用[form对象].errors获得错误信息

三.forms组件基本使用示例

简单的用户注册界面示例

1.常用字段属性

属性 释义
min_length 最小长度
max_length 最大长度
require 该字段必须得填
label 该字段的标签

2.组件文件 form.py

  • 建议在应用文件夹下创建一个专门存放forms组件的文件 (不创建直接写在views.py中也可以)

  • 导入组件并创建类

from django import forms


class RegisterForm(forms.Form):
    # 校验name字段,最大长度为8,最短为3,下面的字段类似
    name = forms.CharField(max_length=8, min_length=3, lable='用户名')
    password = forms.CharField(max_length=10, min_length=4, lable='密码')
    re_password = forms.CharField(max_length=10, min_length=4, lable='确认密码')
    email = forms.EmailField(lable='邮箱')

3.视图层 views.py 文件

  • 实例forms对象, 使用对方法进行校验
from app01.myform import RegisterForm


def register(request):
    if request.method == 'POST':
        # 实例得到form对象,将需要校验的数据传入(数据可以是前端传过来的数据,也可以是自己后端已有的数据)
        # dic = {'name':'shawn','password':'1111','re_password':'1111','email':'123@qq.com'}
        # form_data = RegisterForm(dic)  # 示例:直接将已有的数据传入
        form_data = RegisterForm(request.POST)  # 将前端post请求的数据传入

        # 进行校验 is_valid()
        if form_data.is_valid():
            print('校验成功')
            # 获得校验成功的数据 cleaned_data
            print(form_data.cleaned_data)
            return HttpResponse('校验成功')
        else:
            print('检验失败')
            # 获得检验失败的错误提示 errors
            print(form_data.errors)

    # forms组件可以自动生成HTML代码,因此可以直接在页面上进行显示
    form_data = RegisterForm()  # 先产生form空对象,再将其传给前端
    return render(request, 're_form.html', {'form': form_data})

ps : cleaned_data属性必须是经过is_valid( )校验之后才产生的, 在校验之前使用会报错 : 属性不存在

4.路由层 urls.py 文件

re_path('^re_form/', views.register)

5.模板层 re_form.html 文件

<div class="container">
    <div class="row">
        <div class="col-md-4">
            <div class="panel panel-info">
                <div class="panel-heading">注册页面</div>
                <div class="panel-body">
                    <form action="" method="post">
                        <p>用户名:{{ form.name }}</p>
                        <p>密码:{{ form.password }}</p>
                        <p>确认密码:{{ form.re_password }}</p>
                        <p>邮箱:{{ form.email }}</p>
                        <br>
                        <input type="submit" class="btn btn-block btn-success" value="注册">
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

6.测试效果

  • 检验失败展示

  • 检验成功展示

四.forms组件之渲染标签方式

1.自动渲染方式一

  • 就是上面示例中的写法
  • 可扩展性很强, 但是需要书写的代码太多
<form action="" method="post">
    <p>用户名:{{ form.name }}</p>
    <p>密码:{{ form.password }}</p>
    <p>确认密码:{{ form.re_password }}</p>
    <p>邮箱:{{ form.email }}</p>
    <br>
    <input type="submit" class="btn btn-block btn-success" value="注册">
</form>
  • 也可以使用label标签替代前缀(''用户名''、''密码''等等)
<form action="" method="post">
    <p>{{ form.name.label }}:{{ form.name }}</p>
    <p>{{ form.password.label }}:{{ form.password }}</p>
    <p>{{ form.re_password.label }}:{{ form.re_password }}</p>
    <p>{{ form.email.label }}:{{ form.email }}</p>
    <br>
    <input type="submit" class="btn btn-block btn-success" value="注册">
</form>

2.自动渲染方式二(常用方式)

  • 使用for循环
  • 代码书写简单, 并且扩展性也高 (推荐使用)
<form action="" method="post" novalidate>
    {% for foo in form %}
        <div class="form-group">
            {# 循环取出 name,password,re_password,email #}
            <p>{{ foo.label }}:{{ foo }}</p>
        </div>
    {% endfor %}
	<input type="submit" class="btn btn-block btn-success" value="注册">
</form>

3.自动渲染方式三(不常用)

  • 代码书写极少, 封装程度太高, 不便于后续的扩展, 一般情况下只在本地测试使用
<form action="" method="post" novalidate>
    {{ form.as_p }}       {# 与上两种显示方式相同 #}
    {{ form.as_ul }}      {# 前面带点 #}
    {{ form.as_table }}   {# 在一行显示 #}
    <input type="submit" class="btn btn-block btn-success" value="注册">
</form>

image-20210327191311016

五.forms组件之渲染错误信息

1.字段参数(举例)

  • required : 是否允许为空,默认False
  • error_messages : 自定义错误信息
  • widget : 使用HTML插件
    • TextInput
    • PasswordInput

2.代码演示

  • myform.py 文件
from django import forms
from django.forms import widgets

class RegisterForm(forms.Form):
    # 校验name字段,最大长度为8,最短为3,下面的字段类似
    name = forms.CharField(max_length=8, min_length=3, label='用户名',
                           # 自定义错误信息
                           error_messages={
                               'min_length': '用户名最少3位!',
                               'max_length': '用户账户最大8位',
                               'required': '用户名不能为空!',
                           },
                           # 添加一个form-control类
                           widget=widgets.TextInput(attrs={'class':'form-control'}))
    password = forms.CharField(max_length=10, min_length=4, label='密码',
                               error_messages={
                                   'min_length': '密码最少4位!',
                                   'max_length': '密码最大10位',
                                   'required': '密码不能为空!',
                               },
                               widget=widgets.PasswordInput(attrs={'class':'form-control'}))
    re_password = forms.CharField(max_length=10, min_length=4, label='确认密码',
                                  error_messages={
                                      'min_length': '密码最少4位!',
                                      'max_length': '密码最大10位',
                                      'required': '密码不能为空!',
                                  },
                                  widget=widgets.PasswordInput(attrs={'class':'form-control'}))
    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'invalid': '邮箱格式不正确',
                                 'required': '邮箱不能为空',
                             },
                             widget=widgets.TextInput(attrs={'class':'form-control'}))
  • views.py 文件
from app01.myform import RegisterForm


def register(request):
    if request.method == 'POST':
        form_data = RegisterForm(request.POST)  # 将前端post请求的数据传入

        if form_data.is_valid():
            print(form_data.cleaned_data)
            return HttpResponse('校验成功')
        else:
            # 将带有错误信息的form对象返回前端页面
            return render(request, 're_form.html', {'form': form_data})

    form_data = RegisterForm()  # 将form对象传给前端
    return render(request, 're_form.html', {'form': form_data})
  • re_form.html 文件
<form action="" method="post" novalidate>
    {% for foo in form %}
        {# 在后方设置一个span标签用来放错误提示 #}
        <p>{{ foo.label }}:{{ foo }}
            <span style="color: red">{{ foo.errors.0 }}</span>
        </p>
    {% endfor %}
    <input type="submit" class="btn btn-block btn-success" value="注册">
</form>

ps : <form action="" method="post" novalidate> : form 表单标签内的 novalidate表示前端不进行校验

3.效果展示

六.forms组件之全局钩子, 局部钩子

1.钩子函数

  • 全局和局部钩子都是定义在forms组件之内的函数
  • 钩子函数类似于校验的第二道关卡, 能够让我们自定义校验规则

2.两种钩子函数

  • 局部钩子 : 用于给单个字段增加校验规则
  • 全局钩子 : 用于给多个字段增加校验规则

3.两种钩子函数的书写方式

  • 局部钩子
def clean_字段名(self):
    字段值 = self.cleaned_data.get('字段名')
    [判断逻辑代码]
    [不通过抛出 ValidationError 异常]
    [通过则返回该字段值]
  • 全局钩子
def clean(self):
    字段值1 = self.cleaned_data.get('字段1')
    字段值2 = self.cleaned_data.get('字段2')
    [判断逻辑代码]
    [不通过抛出 ValidationError 异常]
    [通过则 return self.cleaned_data]

4.钩子示例

  • 需求一 : 校验用户名不能有敏感字眼
  • 需求二 : 校验两次输入的密码需一致

myform.py 新增钩子函数

# 局部钩子
def clean_name(self):
    name = self.cleaned_data.get('name')
    for i in ['爱德华','sb']:
        if i in name:
            raise ValidationError(f'用户名中包含敏感词汇:"{i}"')
    else:
        return name

# 全局钩子
def clean(self):
    password = self.cleaned_data.get('password')
    re_password = self.cleaned_data.get('re_password')
    if password == re_password:
        return self.cleaned_data
    else:
        raise ValidationError('两次密码不一致')

views.py 文件

from app01.myform import RegisterForm


def register(request):
    if request.method == 'POST':
        form_data = RegisterForm(request.POST)  # 将前端post请求的数据传入

        if form_data.is_valid():
            return HttpResponse('校验成功')
        else:
            # 获取到 ValidationError 中的 error 信息传给前端
            error = form_data.errors.get('__all__')
            return render(request, 're_form.html', {'form': form_data,'error':error})

    form_data = RegisterForm()  # 将form空对象传给前端
    return render(request, 're_form.html', {'form': form_data})

re_form.html 文件

<form action="" method="post" novalidate>
{% for foo in form %}
    <p>{{ foo.label }}:{{ foo }}
        <span style="color: red; display:inline-block">{{ foo.errors.0 }}</span>
    </p>
{% endfor %}

<input type="submit" class="btn btn-block btn-success" value="注册">
{# 判断错误信息是否为None,是None就不渲染error #}
{% if error is None %}
    <span style="color: red; display:inline-block"></span>
{% else %}
  <span style="color: red; display:inline-block">{{ error }}</span>
{% endif %}
</form>

需求一演示 :

需求二演示 :

七.error错误提示为何是ul标签分析

  • 上面我们在 views.py 文件中写到的,教研室表打印错误提示信息
else:
    print('检验失败')
    # 获得检验失败的错误提示 errors
    print(form_data.errors)
  • 查看打印结果 :

image-20210328000932090

为什么是一个ul标签格式, 一定是重写了 __str__方法

  • 我们导入 ErrorDict 来查看内部源码
from django.forms.utils import ErrorDict

image-20210328001258090

发现其内部重写了__str__方法, 返回的是 as_ul方法, 该方法内部就是定义了html的格式并返回

并且还有一些其他的方法 : 比如 json 格式, data 数据, text 格式等等

而返回 as_ul 方法则是为了方便渲染模板

八.常用字段及插件

1. 初始值 : initial

username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="shawn"  # 设置默认值
    )

2. 自定义错误提示 : error_massages

username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="shawn",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )

3. 密码框 : password

# 设置render_value在输入错误的时候, 保留输入的内容value值
pwd = forms.CharField(
        min_length=4,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'a1'}, render_value=True)
    )

4. 反选radio

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

5. 单选checkbox

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

6. 多选checkbox

Copyhobbies1 = forms.MultipleChoiceField(
    choices=((1, "read"), (2, "run"), (3, "play"),),
    label="爱好",
    initial=[1, 3],
    widget=forms.widgets.CheckboxSelectMultiple()
)

7. 单选Select

Copyeducation  = forms.ChoiceField(
    choices=((1, "高中"), (2, "中专"), (3, "大专"), (4, "本科"), (5, "研究生"), (6, "硕士")),
    label="学历",
    initial=3,
    widget=forms.widgets.Select(attrs={'class': 'form-control'})
)

8. 多选Select

Copyhobbies2 = forms.MultipleChoiceField(
    choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
    initial=[1, 2],
    widget=forms.widgets.SelectMultiple(attrs={'class': 'form-control'})
)

9.更多插件(了解)

插件名称 对应input
TextInput input type="text"
PasswordInput input type="password"
HiddenInput input type="hidden"
NumberInput input type="number"
EmailInput input type="email"
URLInput input type="url"
Textarea textarea
DateInput input type="data"
DateTimeInput input type="datetime"
TimeInput 只获取时分秒
CheckboxInput input type="checkbox"
CheckboxSelectMultiple 多选按钮
Select select框
NullBooleanSelect select框 三个选项 0 1 null
SelectMultiple 可多选的select框
RadioSelect input type="radion"
FileInput 可以查看当前目录下的所有文件。作用不大
posted @ 2021-03-28 18:19  给你骨质唱疏松  阅读(305)  评论(0编辑  收藏  举报