Django – Form组件

  • A+

一、form组件介绍

我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。

与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。

Django form组件就实现了上面所述的功能。

总结一下,其实form组件的主要功能如下:

  • 生成页面可用的HTML标签
  • 对用户提交的数据进行校验
  • 保留上次输入内容

二、使用forms组件实现注册功能

2.1、views.py文件内容

from django.shortcuts import render,HttpResponse
from app01 import models
from django import forms
from django.forms import widgets
from django.core.validators import RegexValidator         #django自带正则校验的模块

# 1.按照Django form组件的要求自己写一个类
class RegForm(forms.Form):
    # 字段,对应的form表单里最终的input框
    name = forms.CharField(        #对应的输入的文本字段
        max_length=12,
        label="用户名",
        widget = widgets.TextInput(attrs={"class":"form-control"}),        #通过attrs={"class":"类名"}指定样式类
        error_messages={
            "required": "该字段不能为空",  # 不填写时候的提示
        })
    pwd = forms.CharField(          #对应输入的密码字段
        min_length=6,
        max_length=12,
        label="密  码",
        # 告诉input标签,这是一个passwd类型,输入是密文的。
        #attrs={"class":"form-control"}:设置css属性
        #render_value=True:密码输入错误后输入框保留输入的密码
        widget = widgets.PasswordInput(attrs={"class":"form-control"},render_value=True),
        error_messages = {
            "min_length": "密码不能少于6位",
            "max_length": "密码不能超过12位",
            "required": "该字段不能为空",  # 不填写时候的提示
        })
    re_pwd = forms.CharField(          #对应输入的密码字段
        min_length=6,
        max_length=12,
        label="重复密码",
        # 告诉input标签,这是一个passwd类型,输入是密文的。
        #attrs={"class":"form-control"}:设置css属性
        #render_value=True:密码输入错误后输入框保留输入的密码
        widget = widgets.PasswordInput(attrs={"class":"form-control"},render_value=True),
        error_messages = {
            "min_length": "密码不能少于6位",     # min_length 限制对应的提示
            "max_length": "密码不能超过12位",    # max_length 限制对应的提示
            "required":"该字段不能为空",         # 不填写时候的提示
        })
    email = forms.EmailField(          #邮箱字段
        label="邮箱",
        widget=widgets.EmailInput(attrs={"class":"form-control"}),
        error_messages = {
            "required": "该字段不能为空",  # 不填写时候的提示
        }
    )

    mobile = forms.CharField(      # 手机号字段
        label="手机号",
        widget=widgets.TextInput(attrs={"class": "form-control"}),
        min_length=11,
        max_length=11,
        validators=[        #手机号校验
            RegexValidator(r'^[0-9]+$', '请输入数字'),
            RegexValidator(r'^1[3-9][0-9]{9}$', '手机号码格式有误')
        ],
        error_messages={
            "min_length": "不能少于11位",  # min_length 限制对应的提示
            "max_length": "不能超过11位",  # max_length 限制对应的提示
            "required": "该字段不能为空",  # 不填写时候的提示
        })

    city = forms.ChoiceField(           #select单选

        choices=models.City.objects.all().values_list(),      #获取城市
        label="城市",
        initial=1,
        widget=forms.widgets.Select()
    )
    #重写父类的init方法
    def __init__(self,*args,**kwargs):
        '''
        说明:
            当该字段的静态属性是从数据库里获取的时候,如果数据库里新加一个字段时,页面上获取不到新加的字段
            此时就需要使用重写父类的init方法(即该方法)
        '''
        super().__init__(*args,**kwargs)
        self.fields["city"].widget.choices = models.City.objects.all().values_list()

    #局部钩子方法(判断用户名是否合法)
    def clean_name(self):       #我们在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
        value = self.cleaned_data.get("name")       #获取name字段的值
        if "金瓶梅" in value:       #如果"金瓶梅"在name字段的值里就抛出异常
            raise ValidationError("不符合社会主义核心价值观!!")
        return value      #如果"金瓶梅"不在name字段里,就返回name字段的值给后端,让后端处理

    #全局钩子(实现校验两次输入的密码)
    def clean(self):        #重写父类的clean方法
        #此时通过校验的数据都存放在:self.cleaned_data里
        pwd = self.cleaned_data.get("pwd")     #获取第一次输入的密码和第二次输入的密码
        re_pwd = self.cleaned_data.get("re_pwd")
        if pwd != re_pwd:      #如果两次密码不一致就抛出异常
            self.add_error("re_pwd",ValidationError("两次密码不一致!!"))      #给固定的字段添加一个固定的错误
            raise ValidationError("两次密码不一致!!")
        return self.cleaned_data      #如果两次密码一致就将所有数据返回到后端

#2.URL对应的函数
def reg(request):
    form_obj = RegForm()       #实例化form对象
    if request.method == "POST":
        form_obj = RegForm(request.POST)      #POST请求时,实例化form对象
        #让form帮我们做校验
        print()
        if form_obj.is_valid():
            #检验通过要做的事(将数据存入数据库)
            #所有经过校验的数据都存放在:form_obj.cleaned_data 的字典里
            del form_obj.cleaned_data["re_pwd"]        #删除form_obj.cleaned_data里的re_pwd字段
            models.UserInfo.objects.create(**form_obj.cleaned_data)        #在数据库里创建数据,用**将form_obj.cleaned_data里的数据解开
            return HttpResponse("注册成功!!")

    return render(request,"login.html",{"from_obj":form_obj})       #将form对象传递到模板语言中

关于is_valid()校验顺序说明:
1.先按照form类中定义的字段顺序依次校验每个字段(内置的校验规则)
2.每个字段先校验内置规则,然后再校验clean_开头的规则(局部钩子)
3.整个for循环走完之后,再调用form类中定义的clean()方法(全局钩子)

2.2、login.html内容如下

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">        <!-- 加这个标签,bootstrap自动支持移动端的适配 -->
    <title>form组件实现登陆页面</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <style>
        .c3 {
            list-style-type: none;
        }
        .pd {
            margin-top: 80px;
        }
    </style>
</head>
<body>

<!-- 使用form组件自己逐个字段写 -->
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">

            <div class="panel panel-primary pd">
        <div class="panel-heading">
            <h3 class="panel-title">请登录</h3>
        </div>

        <div class="panel-body">
            <form action="/reg/" method="post" novalidate>
                {% csrf_token %}

                <!--用户名标签-->
                <div class="form-group {% if from_obj.name.errors.0 %}has-error{% endif %}">
                    {{ from_obj.name.label }}:
                    {{ from_obj.name }}
                    <span class="help-block">{{ from_obj.name.errors.0 }}</span>         <!--将多个条件的错误信息放到errors的列表里,通过下标取出第一个错误-->
                </div>

                <!--密码标签-->
                <div class="form-group {% if from_obj.pwd.errors.0 %}has-error{% endif %}">
                    {{ from_obj.pwd.label }}:
                    {{ from_obj.pwd }}
                    <span class="help-block">{{ from_obj.pwd.errors.0 }}</span>         <!--将多个条件的错误信息放到errors的列表里,通过下标取出第一个错误-->
                </div>

                <!--重复密码标签-->
                <div class="form-group {% if from_obj.re_pwd.errors.0 %}has-error{% endif %}">
                    {{ from_obj.re_pwd.label }}:
                    {{ from_obj.re_pwd }}
                    <span class="help-block">{{ from_obj.re_pwd.errors.0 }}</span>         <!--将多个条件的错误信息放到errors的列表里,通过下标取出第一个错误-->
                </div>

                <!--邮箱标签-->
                <div class="form-group {% if from_obj.email.errors.0 %}has-error{% endif %}">
                    {{ from_obj.email.label }}:
                    {{ from_obj.email }}
                    <span class="help-block">{{ from_obj.email.errors.0 }}</span>         <!--将多个条件的错误信息放到errors的列表里,通过下标取出第一个错误-->
                </div>

                <!--手机号标签-->
                <div class="form-group {% if from_obj.mobile.errors.0 %}has-error{% endif %}">
                    {{ from_obj.mobile.label }}:
                    {{ from_obj.mobile }}
                    <span class="help-block">{{ from_obj.mobile.errors.0 }}</span>         <!--将多个条件的错误信息放到errors的列表里,通过下标取出第一个错误-->
                </div>

                <!--城市单选标签-->
                <div class="form-group {% if from_obj.city.errors.0 %}has-error{% endif %}">
                    {{ from_obj.city.label }}:
                    {{ from_obj.city }}
                    <span class="help-block">{{ from_obj.city.errors.0 }}</span>         <!--将多个条件的错误信息放到errors的列表里,通过下标取出第一个错误-->
                </div>

                <!--提交按钮-->
                <div class="form-group">
                    <input type="submit" value="提 交" class="btn btn-block btn-info">
                </div>
            </form>
        </div>
    </div>
        </div>
    </div>
</div>

<script src="/static/jquery-3.2.1.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>

</body>
</html>

看网页效果发现 也验证了form的功能:
• 前端页面是form类的对象生成的 -->生成HTML标签功能
• 当用户名和密码输入为空或输错之后 页面都会提示 -->用户提交校验功能
• 当用户输错之后 再次输入 上次的内容还保留在input框 -->保留上次输入内容

2.3、Form常用字段说明

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

class RegForm(forms.Form):

    # name字段
    name = forms.CharField(        #对应的输入的文本字段
        max_length=12,     #最大长度
        label="用户名",     #此输入框对应的说明信息
        widget = widgets.TextInput(attrs={"class":"form-control"}),        #通过attrs={"class":"类名"}指定样式类(form-control:输入框的样式)
        error_messages={     #错误提示信息
            "required": "该字段不能为空",  # 不填写时候的提示
        })

    pwd = forms.CharField(          #密码字段对应的输入框
        min_length=6,     #最小长度
        max_length=12,    #最大长度
        label="密  码",    #此输入框对应的提示信息
        #attrs={"class":"form-control"}:设置css属性,
        #render_value=True:密码输入错误后输入框保留之前输入的密码
        #widgets.PasswordInput:告诉input标签,这是一个passwd类型,输入是密文的。
        widget = widgets.PasswordInput(attrs={"class":"form-control"},render_value=True),
        error_messages = {
            "min_length": "密码不能少于6位",
            "max_length": "密码不能超过12位",
            "required": "该字段不能为空",  # 不填写时候的提示
        })

    re_pwd = forms.CharField(          #再一次输入密码字段
        min_length=6,     #最小长度和最大长度
        max_length=12,
        label="重复密码",
        #attrs={"class":"form-control"}:设置css属性
        #render_value=True:密码输入错误后输入框保留输入的密码
        # widgets.PasswordInput:告诉input标签,这是一个passwd类型,输入是密文的。
        widget = widgets.PasswordInput(attrs={"class":"form-control"},render_value=True),
        error_messages = {
            "min_length": "密码不能少于6位",     # min_length 限制对应的提示
            "max_length": "密码不能超过12位",    # max_length 限制对应的提示
            "required":"该字段不能为空",         # 不填写时候的提示
        })

    email = forms.EmailField(          #邮箱字段
        label="邮箱",
        widget=widgets.EmailInput(attrs={"class":"form-control"}),
        error_messages = {
            "required": "该字段不能为空",  # 不填写时候的提示
        }
    )

    mobile = forms.CharField(      # 手机号字段
        label="手机号",
        widget=widgets.TextInput(attrs={"class": "form-control"}),
        min_length=11,
        max_length=11,
        validators=[        #手机号校验(需要导入:RegexValidator 模块)
            RegexValidator(r'^[0-9]+$', '请输入数字'),
            RegexValidator(r'^1[3-9][0-9]{9}$', '手机号码格式有误')
        ],
        error_messages={
            "min_length": "不能少于11位",  # min_length 限制对应的提示
            "max_length": "不能超过11位",  # max_length 限制对应的提示
            "required": "该字段不能为空",  # 不填写时候的提示
        })

    #城市字段
    city = forms.ChoiceField(           #select单选
        choices=models.City.objects.all().values_list(),      #获取城市
        label="城市",
        initial=1,     #默认选择ID是1的城市
        widget=forms.widgets.Select()    #select单选
    )
    #重写父类的init方法
    def __init__(self,*args,**kwargs):
        '''
        说明:
            当该字段的静态属性是从数据库里获取的时候,如果数据库里新加一个字段时,页面上获取不到新加的字段
            此时就需要使用重写父类的init方法(即该方法)
        '''
        super().__init__(*args,**kwargs)
        self.fields["city"].widget.choices = models.City.objects.all().values_list()

    #性别字段
    gender = forms.ChoiceField(           #radio单选
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,     #默认选择3对应的性别
        widget=forms.widgets.RadioSelect(attrs={"class":"c3"})      #select单选
    )

    #爱好字段(select单选)
    hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )

    #爱好2字段(select多选)
    hobby2 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好2",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
    #是否记住密码(checkbox单选)
    keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",      #默认勾选
        widget=forms.widgets.CheckboxInput()
    )

    #爱好3(checkbox多选)
    hobby3 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好3",
        initial=[1, 3],    #默认选择1和3
        widget=forms.widgets.CheckboxSelectMultiple(attrs={"class":"c3"})         #attrs={"class":"c3"}:生成时指定css样式
    )

choice字段注意事项:
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
choice重新构造使用请看上面的city字段。

RegexValidator验证器
RegexValidator是django中自带的校验器,支持正则校验等。
RegexValidator校验器使用请看上面的mobile字段。

2.4、Django Form所有内置字段

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    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类型

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: