form组件

form组件

一、form组件简介

  1、form在组件的两大功能:

  • 数据重置
  • 校验规则

  2、form组件于传统form表单对比

  • 当我们用传统的form表单提交时会刷新页面,如果这个我们表单中的某项填错了,刷新后我们正确的选项也没有了.
  • 传统的form表单需要我们自己亲自校验每一项,其工作量太大
  • form组件前端自动生成表单元素。

  • form组件可自动验证表单内容信息。

  • form组件可保留用户上次输入的信息。

但是form表单的输出不包含submit 按钮,和表单的<form> 标签。 你必须自己提供。

二、form组件生成方法(三种)

1、方法一

<form action="/login/" method="post" novalidate>
    {% csrf_token %}
    {{ form_obj.as_p }}                  #{{ form.as_p }}将它们渲染在<p> 标签中        
    {{ form_obj.as_table }}                #{{ form.as_table }} 以表格的形式将它们渲染在<tr> 标签中
    {{ form_obj.as_ul }}                  #{{ form.as_ul }将它们渲染在<li> 标签中
    <p><input type="submit" value="提交"></p>
</form>

But:你必须自己提供<ul> 或 <table> 元素。

 方式一示例

#HTML

<form action="/login/" method="post" novalidate>     #novalidate不对输入进行验证的表单
    {% csrf_token %}
    {{ form_obj.as_p }}
    <p><input type="submit" value="提交"></p>
</form>


#views

from . import models
from django import forms
from django.forms import widgets
from django.core.exceptions import NON_FIELD_ERRORS,ValidationError


class LoginForm(forms.Form):
    user = forms.CharField(max_length=12,min_length=5,
                        label="用户名",
                        help_text="6~16个字符,区分大小写",
                        error_messages={"required":"不能为空",
                                        "min_length":"最小长度为5",})

    pwd = forms.CharField(
        help_text= "6~16个字符,区分大小写",
        error_messages={
            "invalid":"格式错误"

        },
        widget = widgets.PasswordInput(attrs={"class":"active"})

    )

def login(request):

if request.method == "POST":

form_obj = LoginForm(request.POST)
if form_obj.is_valid():
# 数据全部合格
#取出合格数据
return HttpResponse("success")
else:
# 最少存在一个字段的错误
#取出错误信息
return render(request, "login.html", {"form_obj": form_obj}) #存放错误信息
form_obj = LoginForm()
return render(request,"login.html",{"form_obj":form_obj})

  

2、方法二(在任意标签中渲染)

<form action="/login/" method="post" novalidate>    {# novalidate不对输入进行验证的表单 
   {% csrf_token %}
    <div>
    <label for="user">用户名:</label>
    {{ form_obj.user }}
        <span>{{ form_obj.errors.user.0 }}</span>
    </div>
    <div>
    <label for="pwd">密码:</label>
    {{ form_obj.pwd }}<span>{{ form_obj.errors.pwd.0 }}</span>
    </div>
    <p><input type="submit" value="提交"><span>{{ ret.0 }}</span></p>
</form>

  

 

3、方法三(for循环生成)

<form action="/login/" method="post" novalidate>
   {% csrf_token %}
    {% for field in form_obj %}
        <div>
        <lable>{{ field.label }}</lable>
        {{ field }}
        </div>
    {% endfor %}

  

 三、表单验证的model加form方法

调取数据库进行验证:在models.py文件中创建一个类,在数据库中生成

创建的一个类:

 

生成的数据库:

 

 

views及forms代码:

form组件代码可建立一个form.py文件,然后在views中引入

#form代码

class RegForm(forms.Form):
    user = forms.CharField(min_length=5,max_length=10,
                           error_messages={"required": "不能为空",
                                           "min_length": "最小长度为5",
                                           })
    pwd = forms.CharField(
        help_text="6~16个字符,区分大小写",
        widget=widgets.PasswordInput(attrs={"class": "active"}),
        error_messages={"required": "不能为空",
                        })

    repwd = forms.CharField(
        help_text="6~16个字符,区分大小写",
        widget=widgets.PasswordInput(attrs={"class": "active"}),
        error_messages={"required": "不能为空",
                        }
    )
    email = forms.EmailField(
                error_messages={"required": "不能为空",
                                "invalid": "格式错误"
                                },
    )
    tel = forms.CharField(
        error_messages={"required": "不能为空",
                        }
    )
    def clean_user(self):
        val = self.cleaned_data.get("user")
        if val.isdigit():
            raise ValidationError("不能为数字")
        else:
            ret = UserInfo.objects.filter(name=val)
            if not ret:
                return val
            else:
                raise ValidationError("该用户已注册")

    def clean_tel(self):
        val = self.cleaned_data.get("tel")
        import re               #引入正则

        ret = re.search("^1[3578]\d{9}$",val)
        if ret:
            return val
        else:
            raise ValidationError("手机号码格式")

    def clean(self):
        repwd = self.cleaned_data.get("repwd")
        pwd = self.cleaned_data.get("pwd")

        if repwd == pwd:
            return self.cleaned_data
        else:
            raise ValidationError("两次密码不一致")


views代码

def reg(request):
    if request.method == "POST":
        reg_form = RegForm(request.POST)
        if reg_form.is_valid():
            name = reg_form.cleaned_data.get("user")
            pwd = reg_form.cleaned_data.get("pwd")
            UserInfo.objects.create(name=name,pwd=pwd)
            return redirect("/login/")
        else:
            all_error = reg_form.errors.get("__all__")
            return render(request, "register.html", {"reg_form": reg_form,"all_error":all_error})

    reg_form = RegForm()
    return render(request,"register.html",{"reg_form":reg_form})





#html代码

<h1>注册页面</h1>
    <form action="/reg/" method="post" novalidate>
        {% csrf_token %}
        {% for field in reg_form %}
            <div>
                <lable>{{ field.label }}</lable>
                {{ field }}
                <span>{{ field.errors.0 }}</span>
                {% if field.label == "Repwd" %}        {#field.label == "Repwd" Repwd是页面渲染后的名称#}
                    <span>{{ all_error.0 }}</span>
                {% endif %}
            </div>
        {% endfor %}
    <p><input style="margin-left: 100px" type="submit" value="注册">

    </form>

四、form组件中常用字段及插件 

    Django中form的创建涉及到一些字段及插件

    字段用于对用户请求数据的验证,插件用于自动生成HTML

1.Django内置字段

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类型
        ...

    # 注:UUID是根据MAC以及当前时间等创建的不重复的随机字符串
       >>> import uuid

        # make a UUID based on the host ID and current time
        >>> uuid.uuid1()    # doctest: +SKIP
        UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')

        # make a UUID using an MD5 hash of a namespace UUID and a name
        >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
        UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')

        # make a random UUID
        >>> uuid.uuid4()    # doctest: +SKIP
        UUID('16fd2706-8baf-433b-82eb-8c7fada847da')

        # make a UUID using a SHA-1 hash of a namespace UUID and a name
        >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
        UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')

        # make a UUID from a string of hex digits (braces and hyphens ignored)
        >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')

        # convert a UUID to a string of hex digits in standard form
        >>> str(x)
        '00010203-0405-0607-0809-0a0b0c0d0e0f'

        # get the raw 16 bytes of the UUID
        >>> x.bytes
        b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'

        # make a UUID from a 16-byte string
        >>> uuid.UUID(bytes=x.bytes)
        UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')

2.Django内置插件

    ------------------
    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
    ------------------
 常用选择插件
        # 单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
        # )

    # 在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于静态字段×××获取的值
    # 无法实时更新×××,那么需要自定义构造方法从而达到此目的。
    方法一.
        from django.forms import Form
        from django.forms import widgets
        from django.forms import fields
        from django.core.validators import RegexValidator

        class MyForm(Form):

            user = fields.ChoiceField(
                # choices=((1, '上海'), (2, '北京'),),
                initial=2,
                widget=widgets.Select
            )

            def __init__(self, *args, **kwargs):
                super(MyForm,self).__init__(*args, **kwargs)
                # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),)
                # 或
                self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
    方法二.
        # 使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现
        from django import forms
        from django.forms import fields
        from django.forms import widgets
        from django.forms import models as form_model
        from django.core.exceptions import ValidationError
        from django.core.validators import RegexValidator

        class FInfo(forms.Form):
            authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
            # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())

五、form组件的校验

  form组件自定义校验规则:

自定义校验规则中也会使用到局部钩子 跟 全局钩子  前边小例子中有涉及

方式一:
    from django.forms import Form
    from django.forms import widgets
    from django.forms import fields
    from django.core.validators import RegexValidator
   form diango. class MyForm(Form): user = fields.CharField( validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], ) 方式二: import re from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.exceptions import ValidationError # 自定义验证规则 def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号码格式错误') class PublishForm(Form): title = fields.CharField(max_length=20, min_length=5, error_messages={'required': '标题不能为空', 'min_length': '标题最少为5个字符', 'max_length': '标题最多为20个字符'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': '标题5-20个字符'})) # 使用自定义验证规则 phone = fields.CharField(validators=[mobile_validate, ], error_messages={'required': '手机不能为空'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'})) email = fields.EmailField(required=False, error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'})) 方法三:自定义方法 from django import forms from django.forms import fields from django.forms import widgets from django.core.exceptions import ValidationError from django.core.validators import RegexValidator class FInfo(forms.Form): username = fields.CharField(max_length=5, validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], ) email = fields.EmailField() def clean_username(self): """ Form中字段中定义的格式匹配完之后,执行此方法进行验证 :return: """ value = self.cleaned_data['username'] if "666" in value: raise ValidationError('666已经被玩烂了...', 'invalid') return value 方法四:同时生成多个标签进行验证 from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.validators import RegexValidator ############## 自定义字段 ############## class PhoneField(fields.MultiValueField): def __init__(self, *args, **kwargs): # Define one message for all fields. error_messages = { 'incomplete': 'Enter a country calling code and a phone number.', } # Or define a different message for each field. f = ( fields.CharField( error_messages={'incomplete': 'Enter a country calling code.'}, validators=[ RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'), ], ), fields.CharField( error_messages={'incomplete': 'Enter a phone number.'}, validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')], ), fields.CharField( validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')], required=False, ), ) super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args, **kwargs) def compress(self, data_list): """ 当用户验证都通过后,该值返回给用户 :param data_list: :return: """ return data_list ############## 自定义插件 ############## class SplitPhoneWidget(widgets.MultiWidget): def __init__(self): ws = ( widgets.TextInput(), widgets.TextInput(), widgets.TextInput(), ) super(SplitPhoneWidget, self).__init__(ws) def decompress(self, value): """ 处理初始值,当初始值initial不是列表时,调用该方法 :param value: :return: """ if value: return value.split(',') return [None, None, None]

  

 局部钩子跟全局钩子案例:

from django import forms                #调用forms模块
    from django.forms import widgets        #调用widgets模块,用来对form组件的参数配置。
    from django.core.exceptions import ValidationError  #调用 ValidationError 模块。用来手动触发raise错误。
    from fileupdate.models import *      #载入必要的数据库列表。


    class FormReg(forms.Form):

        name = forms.CharField(min_length=4, widget=widgets.TextInput(attrs={'class': 'form-control '}), label='姓名',
                               error_messages={'required': '*不能为空', })
        pwd = forms.CharField(min_length=4, widget=widgets.PasswordInput(attrs={'class': 'form-control'}), label='密码')
        r_pwd = forms.CharField(min_length=4, widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
                                label='确认密码')
        email = forms.EmailField(widget=widgets.EmailInput(attrs={'class': 'form-control'}), label='邮箱')
        tel = forms.CharField(max_length=13, label='电话', widget=widgets.TextInput(attrs={'class': 'form-control'}))
        ##字段的校验,通过对widget的属性设置,可定义INPUT标签的type类型,以及标签的其他属性。通过对label设置,可以自定义form渲染时的标签名,
        ##另外,通过对error_messages属性设置,可对验证信息进行自定义。注意:字典中错误信息的key值是固定的
    
        def clean_name(self):    #局部钩子   注意:名字必须为clean_%s ,这是根据源码来设置的。
                                #其原理是,当字段校验完毕后,再进行查找是否有以clean_开头的函数名,如果有,就调用该函数,
                                #运行我们自定义的函数,如果满足条件就返回当前被校验字段的内容。否则手动触发ValidationError错误,源码中会捕获并将值返回。
            val = self.cleaned_data.get('name')     #通过cleaned_data获得对应字段的'干净数据'
            user_obj = User.objects.filter(name=val).first()  #与对应的数据库中字段相比较,并获得一个字段对象
            if not user_obj:   #对字段进行判断,如果为空(数据库中没有对应的名字),那么返回这个校验值。
                return val
            else:
                raise ValidationError('名字存在')   #如果存在,那么手动触发异常(异常名为ValidationError),并设置自定义内容。
    
        def clean(self):   #全局钩子  注意:名字必须为clean,这是根据源码来设置的。
                         #其原理是对校验完毕的字段,再进行字段间的校验。当字段校验完毕,查找是否有clean的函数,如果有就运行该
                         #函数,其功能是对所有校验的字段进行校验比对。如果满足条件,就将cleaned_data返回(这与源码相匹配)
                         #如果不满足就手动触发ValidationError错误。
            pwd = self.cleaned_data.get('pwd') 
            r_pwd = self.cleaned_data.get('r_pwd')
            if pwd and r_pwd:   #如果两个字段中一个为空值那么就不用再进行校验。直接返回cleaned_data,通过校验功能返回错误信息。
                if pwd == r_pwd:
                    return self.cleaned_data
                else:
                    raise ValidationError('两次密码不一致!')
            else:
                return self.cleaned_data

    def reg(request):
    
        if request.method == 'POST':  #如果是一次POST提交,那么进行校验。
            formreg = FormReg(request.POST)  #对提交的信息实例化。
    
            if formreg.is_valid():  #通过is_valid()方法进行判断,(注意:当执行这个函数时,将对所有字段进行校验,运行局部钩子和全局钩子)
                return HttpResponse('OK')
            else:
                print('cleaneddata', formreg.cleaned_data)
                print('errordata', formreg.errors)
                error = formreg.errors.get('__all__') #当设置了全局钩子时,要设置一个变量来获得全局钩子返回的错误信息。
                            #这是由于,全局钩子的错误在form对象的errors中,当clean()方法抛出异常时,源码会自动捕获,并将错误
                            #存储在errors字典中,其中键名'__all__'就是全局钩子的变量。
                return render(request, 'reg.html', locals())
    
        formreg = FormReg()   #当为get请求时,实例化一个空的对象,通过这个空的实例化对象可以渲染前段,自动生成form表单。
        return render(request,'reg.html', locals())

  

 

 

 

 

 

posted @ 2019-06-27 22:48  HelloBaby!  阅读(1462)  评论(0编辑  收藏  举报