Django 组建 forms

一、校验字段功能

  通过注册用户这个实例来学习校验字段功能。

1、模型:models.py

1
2
3
4
5
6
7
8
9
from django.db import models
 
# Create your models here.
 
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    email = models.EmailField()
    tel = models.CharField(max_length=32)

  修改配置后,完成数据库迁移操作,可以查看到UserInfo表已经创建完成。

2、模板:reg.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    <p>用户名<input type="text" name="name"></p>
    <p>密码<input type="text" name="pwd"></p>
    <p>确认密码<input type="text" name="r_pwd"></p>
    <p>邮箱<input type="text" name="email"></p>
    <p>手机号<input type="text" name="tel"></p>
    <input type="submit">
</form>
</body>
</html>

3、视图函数:views.py

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
from django.shortcuts import render,HttpResponse
# Create your views here.
from django import forms   # 引入forms组件
 
class UserForm(forms.Form):   # 必须继承forms.Form
    name = forms.CharField(min_length=4)
    pwd = forms.CharField(min_length=4)
    r_pwd = forms.CharField(min_length=4)
    email = forms.EmailField()
    tel = forms.CharField()
 
def reg(request):
    if request.method=="POST":
        print(request.POST)
        # 生成forms对象后,传入字典就可以做一个个值的校验
        # form = UserForm({"name": "yuan", "email": "123@qq.com", "xxx":"alex"})
        form = UserForm(request.POST)   # form表单的name属性值应该与forms组件的字段名称一致,不一致的键值不会去做校验
        print(form.is_valid())     # 返回True/False
        # 帮忙校验
        if form.is_valid():  # 注意:form.is_valid是帮忙校验返回布尔值的,true或false(所有都通过才返回true)
            # 类定义的字段均符合要求,返回true,有多的字段直接忽略
            print(form.cleaned_data)   # {'name': 'yuan', 'pwd': '1234', 'r_pwd': '1234', 'email': '1234@163.com', 'tel': '123123'}
        else:
            # 有正确也有错误信息也是返回false
            print(form.cleaned_data)   # 字段值符合要求的放在cleaned_data    {'name': 'yuan', 'pwd': '123456', 'tel': '123123'}
            print(form.errors)         # 字段不符合要求的对应的键作为键,错误信息作为值   <ul class="errorlist"><li>r_pwd<ul class="errorlist">...
            print(type(form.errors))   # <class 'django.forms.utils.ErrorDict'>  还是一个字段类型
            print(form.errors.get("r_pwd"))     # <ul class="errorlist"><li>This field is required.</li></ul>
            print(type(form.errors.get("r_pwd")))    # <class 'django.forms.utils.ErrorList'> 用列表存放错误信息
            print(form.errors.get("r_pwd")[0])   # 获取到报错信息:This field is required.
        """
        form.is_valid()  校验返回布尔值
        if 所有的字段校验成功,则form.cleaned_data:{"name": "yuan", "email": "123@qq.com"}
        如果校验失败,则form.errors放入所有错误的字段。
        """
        return HttpResponse("OK")
    return render(request, "reg.html")

注意:

(1)引入forms组件,定义的类必须继承forms.Form,forms类定义过的属性默认非空。

1
2
3
4
5
6
7
8
from django import forms   # 引入forms组件
 
class UserForm(forms.Form):   # 必须继承forms.Form
    name = forms.CharField(min_length=4)
    pwd = forms.CharField(min_length=4)
    r_pwd = forms.CharField(min_length=4)
    email = forms.EmailField()
    tel = forms.CharField()

(2)生成forms对象,需要注意的是给对象传入字典就可以做一个个值的校验:

1
form = UserForm({"name": "yuan", "email": "123@qq.com", "xxx":"alex"})

  但是我们需要的是form表单属性与forms组件字段的校验,因此需要把request.POST传入forms对象:

1
form = UserForm(request.POST) 

  form表单的name属性值应该与forms组件的字段名称一致,不一致的键值不会去做校验。

(3)form.is_valid() :做校验返回布尔值的,所有都通过才返回True,否则返回False。与forms组件字段无关的键值不影响返回结果。

1
2
3
4
5
6
7
if form.is_valid():  # 注意:form.is_valid是帮忙校验返回布尔值的,true或false(所有都通过才返回true)
    # 类定义的字段均符合要求,返回true,有多的字段直接忽略
    print(form.cleaned_data)   # {'name': 'yuan', 'pwd': '1234', 'r_pwd': '1234', 'email': '1234@163.com', 'tel': '123123'}
else:
    # 有正确也有错误信息也是返回false
    print(form.cleaned_data)   # 字段值符合要求的放在cleaned_data    {'name': 'yuan', 'pwd': '123456', 'tel': '123123'}
    print(form.errors)         # 字段不符合要求的对应的键作为键,错误信息作为值   <ul class="errorlist"><li>r_pwd<ul class="errorlist">...

(4)form.cleaned_data:字段值符合要求的放在cleaned_data中。字典数据类型。

(5)form.errors:字段不符合要求的对应的键作为键,错误信息作为值。虽然返回结果比较复杂,但依然是字典数据类型,可以通过form.errors.get(“不符合的键”)来拿到键值,键值为列表数据类型。因此可以通过form.errors.get("不符合键")[0]拿到错误信息。

二、渲染标签功能

1、渲染方式一:

视图函数:将forms空对象传给模板

1
2
3
4
5
6
7
8
9
10
11
12
13
from django import forms   # 引入forms组件
 
class UserForm(forms.Form):   # 必须继承forms.Form
    # forms.CharField和forms.EmailField会渲染为input标签
    name = forms.CharField(min_length=4)    # 默认label是字段名
    pwd = forms.CharField(min_length=4, label="密码")   # 如果需要中文label可以手动设置
    r_pwd = forms.CharField(min_length=4, label="确认密码")
    email = forms.EmailField(label="邮箱")
    tel = forms.CharField(label="手机")
 
def reg(request):
    form = UserForm()
    return render(request, "reg.html", locals())

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<h3>form组件渲染方式1</h3>
<form action="" method="post">
    {% csrf_token %}
    <p>{{ form.name.label }}
        {{ form.name }}
    </p>
    <p>{{ form.pwd.label }}
        {{ form.pwd }}
    </p>
    <p>{{ form.r_pwd.label }}
        {{ form.r_pwd }}
    </p>
    <p>{{ form.email.label }}
        {{ form.email }}
    </p>
    <p>{{ form.tel.label }}
        {{ form.tel }}
    </p>
    <input type="submit">
</form>

渲染效果:

  

2、渲染方式二

  其他与方式一同理,但是用for循环获取forms对象内字段。

1
2
3
4
5
6
7
8
9
10
11
<h3>forms组件渲染方式2</h3>
<form action="" method="post">
    {% csrf_token %}
    {% for field in form %}
        <p>
            <label for="">{{ field.label }}</label>
            {{ field }}
        </p>
    {% endfor %}
    <input type="submit">
</form>

  渲染效果:

  

3、渲染方式三

  调用form对象的组件:as_p,即完成渲染。缺点是结构固定。(还有一种是as_ul)

1
2
3
4
5
6
<hr>
<h3>forms组件渲染方式3</h3>
<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
</form>

  渲染效果:

  

三、显示错误与重置输入信息功能

1、视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def reg(request):
    if request.method=="POST":
        form = UserForm(request.POST)   # 绑定数据的form表单
        print(form.is_valid())     # 返回True/False
        if form.is_valid():
            # 类定义的字段均符合要求,返回true,有多的字段直接忽略
            print(form.cleaned_data)     #所有干净字段及对应的值
        else:
            # 有正确也有错误信息也是返回false
            print(form.cleaned_data)
            # print(form.errors)                 # ErrorDict : {"校验错误的字段":["错误信息",]}     
            # print(form.errors.get("name"))     # ErrorList ["错误信息",]
        return render(request, "reg.html", locals())
 
    form = UserForm()   # 未绑定数据的form表单
    return render(request, "reg.html", locals())

2、模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<h3>显示错误与重置输入信息</h3>
<form action="" method="post">
    {% csrf_token %}
    <p>{{ form.name.label }}
        {{ form.name }} <span>{{ form.name.errors.0 }}</span>
    </p>
    <p>{{ form.pwd.label }}
        {{ form.pwd }} <span>{{ form.pwd.errors.0 }}</span>
    </p>
    <p>{{ form.r_pwd.label }}
        {{ form.r_pwd }} <span>{{ form.r_pwd.errors.0 }}</span>
    </p>
    <p>{{ form.email.label }}
        {{ form.email }} <span>{{ form.email.errors.0 }}</span>
    </p>
    <p>{{ form.tel.label }}
        {{ form.tel }} <span>{{ form.tel.errors.0 }}</span>
    </p>
    <input type="submit">
</form>

四、forms组件的参数配置

  上面完成的注册页面,显示效果比较糟糕。因此需要引入widgets模块来调整组件参数配置,优化小时效果。

1、reg.html引入样式

  模板引入bootstrap,并仅保留渲染方式一生成的注册登录页面。

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
<head>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.css">
</head>
 
<div class="container">
    <div class="row">
        <div class="col-md-6 col-lg-offset-3">
            <hr>
            <h3>form组件渲染方式1</h3>
            <form action="" method="post">
                {% csrf_token %}
                <p>{{ form.name.label }}
                    {{ form.name }} <span>{{ form.name.errors.0 }}</span>
                </p>
                <p>{{ form.pwd.label }}
                    {{ form.pwd }} <span>{{ form.pwd.errors.0 }}</span>
                </p>
                <p>{{ form.r_pwd.label }}
                    {{ form.r_pwd }} <span>{{ form.r_pwd.errors.0 }}</span>
                </p>
                <p>{{ form.email.label }}
                    {{ form.email }} <span>{{ form.email.errors.0 }}</span>
                </p>
                <p>{{ form.tel.label }}
                    {{ form.tel }} <span>{{ form.tel.errors.0 }}</span>
                </p>
                <input type="submit">
            </form>

2、在视图层引入widgets模块,配置修改forms类参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.shortcuts import render,HttpResponse
# Create your views here.
from django import forms   # 引入forms组件
from django.forms import widgets
 
class UserForm(forms.Form):   # 必须继承forms.Form
    # forms.CharField和forms.EmailField会渲染为input标签
    name = forms.CharField(min_length=4, label="用户名", error_messages={"requried": "该字段不能为空"},
                           widget=widgets.TextInput(attrs={"class": "form-control"})
                           )
    pwd = forms.CharField(min_length=4, label="密码",   # 如果需要中文label可以手动设置
                          widget=widgets.PasswordInput(attrs={"class": "form-control"})  # 生成密文文本输入框
                          )
    r_pwd = forms.CharField(min_length=4, label="确认密码",
                            widget=widgets.TextInput(attrs={"class": "form-control"}))
    email = forms.EmailField(label="邮箱", error_messages={"requried": "该字段不能为空", "invalid": "格式错误"},
                             widget=widgets.EmailInput(attrs={"class": "form-control"})
                             # invalid修改格式错误提示
    tel = forms.CharField(label="手机",error_messages={"requried": "该字段不能为空"},
                          widget=widgets.TextInput(attrs={"class": "form-control"})

注意:

  (1)在字段构造参数中设置"error_messages"修改字段错误提示消息:其中"requried"负责字段不能为空消息提示;"invalid"负责格式错误消息提示等。

  (2)在字段构造函数中配置input类型:设置为文本域、密码域、单选框、复选框等等类型。

1
2
3
widget=widgets.TextInput()
widget=widgets.PasswordInput()
widget=widgets.EmailInput()

  (2)给字段构造函数中配置修改属性。比如给type="text"的input标签设置bootstrap样式,给input标签添加类"form-control"。

显示效果:

  

五、局部钩子和全局钩子

  为了将代码解耦可以选择自己创建一个文件比如app01/myform.py存放钩子。

1、myform.py:

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
# forms组件
from django.forms import widgets
 
wid_01=widgets.TextInput(attrs={"class":"form-control"})
wid_02=widgets.PasswordInput(attrs={"class":"form-control"})
 
from django.core.exceptions import ValidationError
class UserForm(forms.Form):
    name=forms.CharField(max_length=32,
                         widget=wid_01
                         )
    pwd=forms.CharField(max_length=32,widget=wid_02)
    r_pwd=forms.CharField(max_length=32,widget=wid_02)
    email=forms.EmailField(widget=wid_01)
    tel=forms.CharField(max_length=32,widget=wid_01)
 
 
    # 局部钩子
    def clean_name(self):
        val=self.cleaned_data.get("name")
        if not val.isdigit():
            return val
        else:
            raise ValidationError("用户名不能是纯数字!")
 
    # 全局钩子
    def clean(self):
        pwd=self.cleaned_data.get("pwd")
        r_pwd=self.cleaned_data.get("r_pwd")
 
        if pwd==r_pwd:
            return self.cleaned_data
        else:
            raise ValidationError('两次密码不一致!')

views.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.shortcuts import render,HttpResponse
# Create your views here.
from app01.myforms import *
 
def reg(request):
    if request.method=="POST":
        form = UserForm(request.POST)   # 绑定数据的form表单
        if form.is_valid():
            # 类定义的字段均符合要求,返回true,有多的字段直接忽略
            print(form.cleaned_data)
        else:
            print(form.cleaned_data)   # 字段值符合要求的放在cleaned_data    {'name': 'yuan', 'pwd': '123456', 'tel': '123123'}
 
            # 全局钩子错误
            # print("error", form.errors.get("__all__")[0])
            errors = form.errors.get("__all__")
 
            return render(request, "reg.html", locals())
 
    form = UserForm()   # 未绑定数据的form表单
    return render(request, "reg.html", locals())

reg.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<form action="" method="post" novalidate>
            {% csrf_token %}
 
            {% for field in form %}
                <div>
                    <label for="">{{ field.label }}</label>
                    {{ field }}
                    <span class="pull-right" style="color: red">
                          {% if field.label == 'R pwd' %}
                          <span>{{ clean_error.0 }}</span>
                          {% endif %}
                          {{ field.errors.0 }}
                    </span>
                </div>
            {% endfor %}
            <input type="submit" class="btn btn-default">
 
</form>

六、Forms组件归类及补充

Django的Form主要具有一下几大功能:

  • 生成HTML标签
  • 验证用户数据(显示错误信息)
  • HTML Form提交保留上次提交数据
  • 初始化页面显示内容

1、Django内置字段:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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内置插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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、常用选择插件

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
46
# 单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
# )
posted @ 2019-07-31 15:35  一米八大高个儿  阅读(202)  评论(0编辑  收藏  举报