3期 58-2 Django Form组件

jango的Form主要具有以下几大功能:

  • 生成HTML标签(可在生成html标签的同时,初始化所填数据)
  • 验证用户数据(显示错误信息,并能保留上次提交的数据)

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

一、基本用法

(一)创建Form类

from django.forms import Form
from django.forms import fields
from django.forms import widgets


class MyForm(Form):

    name = fields.CharField(                          # name的值为CharField类实例化为的字段对象。字段名字要与前端提交过来的一致。
        max_length=10,
        error_messages={                              # 自定义错误提示,默认情况下是内置的英文提示
            'max_length': '最大长度不能超过30',
        }
    )

    age = fields.IntegerField(
        min_value=10,
        max_value=30,
        error_messages={
            'invalid': '年龄只能为数字',                 # 数据类型错误,都用'invalid'
        }
    )

    gender = fields.ChoiceField(
       choices=((0, '女'), (1, '男')),
       widget=widgets.RadioSelect,
    )

    cs_id = fields.CharField(
        widget=widgets.Select(choices=((1, '一班'), (2, '二班'))),
        initial=2,
    )

给自动生成的html元素添加属性:

    name = fields.CharField(
        max_length=10,
        widget=widgets.TextInput(attrs={'placeholder': '姓名','class':'form-control'}),     # 根据要生成的html不同,可以是TextInput()、select()等
    )

(二)两种提交方式下的验证

1、form表单提交

  • 验证
  • 页面上自动保存上次的内容

(1)View函数处理

from django.shortcuts import render
from app01.MyForm import MyForm

def add_student_by_form(request):
    if request.method == 'GET':
        obj = MyForm()                                                       # 创建一个MyForm对象,发给前端自动生成html代码
        # 如果需要同时初始化数据写法如下:
        # obj = MyForm({'name': 'alex','age':15,'gender':1,'cs_id':'49'})    # 用字典传入初始值
        return render(request, 'add_student_by_form.html', {'obj': obj})
    else:
        obj = MyForm(request.POST)                                           # 创建一个被赋值的MyForm对象
        if obj.is_valid():                                                   # 验证所有提交的数据是否符合要求
            ret = obj.cleaned_data                                           # 只有执行is_valid()方法后,cleaned_data中才有值
            return render(request, 'add_student_by_form.html', {"ret": ret})
        else:                                                                # 验证失败,返回错误信息
            return render(request,'add_student_by_form.html', {'obj': obj})

(2)生成HTML

<form action="/add_student_by_form.html" method="POST" novalidate>
    {{ ret }}
    <p>
        {{ obj.name.label }}{{ obj.name }}{{ obj.errors.name.0 }}            <!--errors是错误的列表,取第一个错误就行,故写0-->
    </p>
    <p>
        {{ obj.age.label }}{{ obj.age }}{{ obj.errors.age.0 }}
    </p>
    <p>
        {{ obj.gender.label }}{{ obj.gender }}{{ obj.errors.gender.0 }}
    </p>
    <p>
        {{ obj.cs_id.label }}{{ obj.cs_id }}{{ obj.errors.cs_id.0 }}
    </p>
    <p><input type="submit"></p>
    {% csrf_token %}
</form>

  2、Ajax方式提交

  • 验证;
  • 要手动在页面上跳转或展示错误信息

(用HttpResponse返回信息。由于错误信息obj.errors继承自基本的数据类型dict,所以可以被 json.dumps())

(1)Views函数处理

def f2(request):
    if request.method == 'GET':
        obj = F1Form()
        return render(request, "f2.html", {"obj": obj})
    else:
        obj = F1Form(request.POST)
        dic = {"status": False, "msg": obj.errors, }   # 错误信息需要手动发送到前端。
        if obj.is_valid():
            dic["status"] = True
        else:
            pass
        import json
        ret = json.dumps(dic)    # dic中的obj.errors,继承自dict基本数据类型,所以可以被dumps处理
        return HttpResponse(ret)

(2)前端处理

<form id="fm" action="f2.html" method="POST" novalidate>
    <p>用户:{{ obj.username }} <span id="username"></span></p>
    <p>年龄:{{ obj.age }} <span id="age"></span></p>
    <p>邮箱:{{ obj.email }} <span id="email"></span></p>
</form>
<input type="button" value="Ajax提交" onclick="submitForm()">

<script src="/static/jquery-3.2.1.js"></script>
<script>
    function submitForm(){
        $("#username").html("");                                   // 清空原错误信息
        $("#age").html("");
        $("#email").html("");

        $.ajax({
            url: '/f2.html',
            type: 'POST',
            data: $("#fm").serialize(),
            success: function(arg){
                ret = JSON.parse(arg);   // ret格式为 {'status':True,'msg':{'username':[...],'age':[...],...}
                console.log(ret);
                if (ret.status){
                    window.location.href = "http://www.baidu.com";   // 用js在前端完成跳转
                }else{
                    for (var i in ret.msg){
                        //console.log(i);                // username
                        $("#"+i).html(ret.msg[i][0]);    // 将错误信息填充到页面
                    }
                }
            }
        })
    }
</script>

补充 : 错误信息的数据结构

django后端print(obj.errors)结果为:

<ul class="errorlist">
  
  <li>username
    <ul class="errorlist">
      <li>最小长度为3位!</li>
	</ul>  
  </li>
  
  <li>email
    <ul class="errorlist">
      <li>Enter a valid email address.</li>
      <li>Ensure this value has at least 10 characters (it has 3).</li>
	</ul>
  </li>
  
</ul>


其结构相当于:
{
  username:["最小长度为3位!"],
  email: ["Enter a valid email address.", "Ensure this value has at least 10 characters (it has 3)"]
}


故obj.errors 数据结构:
{ 
  username:["username验证错误信息1","username验证错误信息2", ...],
  email: ["email验证错误信息1","email验证错误信息2", ...],
  __all__: ["整体验证错误信息1","整体验证错误信息2", ...]
}


故可以手写错误信息:
obj.errors['username'] = ['用户名已经存在!', ...]

 

二、使用详解

一、fields.xxx 内置字段

每种内置字段内部都封装了相应的正则表达式和用于生成HTML标签(如:input框、select标签等)的widget,用于完成其相应功能的验证和自动生成HTML代码。子类是在父类的验证规则的基础上,再加入自己特有的验证规则。

常用验证字段类、继承关系及其参数:

Field
    required=True,                是否必填。默认为True,即必填。
    widget=None,                  自定义生成哪种类型的input框。每种Field都有默认的input框类型
    initial=None,                 设定本字段的初始默认值   
    error_messages=None,          错误信息 如:{'required': '不能为空', 'invalid': '格式错误'}。默认显示英文
    validators=[],                写正则表达式,自定义验证规则,可在列表中加入多个验证规则
    disabled=False,               是否可以编辑。True时,不可编辑
	localize=False,               是否支持本地时间
	label=None,                   用于生成Label标签或显示内容。默认None,即显示该字段的本名。需要在html模板中加入如:{{ obj.userName.label }}
	show_hidden_initial=False,    为True时,在当前input框后再生成一个隐藏的且具有原来值的input框(可用于检验新输入与原来的值是否一至) 
	help_text='',                 帮助信息。要在页面上显示需要在html模板中加入如:{{ obj.userName.help_text }}
	label_suffix=None             只有在完全自动生成html代码时(如用{{obj.as_p}}、{{obj.as_ul}、{{obj.as_table})才能显示出来,作为Label内容的后缀
  
  
CharField(Field)                  CharField继承自Field类,故Field类的参数CharField类同样有。下面的同理
    max_length=None,              最大长度
    min_length=None,              最小长度
    strip=True                    默认True,自动移除用户输入值首尾的空白。

EmailField(CharField)	

RegexField(CharField)
    regex,                        自定义正则表达式,如:'^150\d{8}$'  必须以150开头的11位数字,错误信息使用 error_messages={'invalid': '...'}
 
 
IntegerField(Field)     
    max_value=None,               最大值
    min_value=None,               最小值
	
DecimalField(IntegerField)
    max_digits=None,              最大总数字个数(总数字个数=小数点前的数字总个数+小数点后的数字总个数)
    decimal_places=None,          小数位的数字个数
 
 
ChoiceField(Field)               
    choices=(),                   选项,如:choices = ((0, '上海'), (1, '北京'), (2, '广州'),(3, '深圳'),)
	widget=None,                  默认生成单选select下拉框
    initial=None                  自定义initial写法:initial=2                    	

MultipleChoiceField(ChoiceField) 
	widget=None,                  默认生成多选select下拉框    
    initial=None,                 自定义initial写法:initial=[1, 2]

	
BaseTemporalField(Field)
    input_formats=None            时间格式化  
  
DateField(BaseTemporalField)      只能按以下格式提交:2015-09-01 (1位数月或日,前面加0不加0都可以)

TimeField(BaseTemporalField)      只能按以下格式提交:11:12  (1位数时或分,前面加0不加0都可)

DateTimeField(BaseTemporalField)  只能按以下格式提交:2015-09-01 11:12
 
 
FileField(Field)
    allow_empty_file=False        是否允许空文件
  
ImageField(FileField)             注:需要PIL模块,pip3 install Pillow                               
        
FileField与ImageField使用时,需要注意两点:
    - form表单中 enctype="multipart/form-data"
    - view函数中 obj = MyForm(request.POST, request.FILES)
	
	
GeneralIPAddressField
    protocol='both',              ipv4和ipv6格式都可以验证成功。若填'ipv4',只能验证ipv4的
	unpack_ipv4=False             解析ipv4地址,如果是::ffff:192.0.2.1的时候,解析为192.2.0.1,PS:protocol必须为both才能启用

 更多验证字段:

SlugField(CharField)              只能包含:数字,字母,下划线,减号(即连字符)    

UUIDField(CharField)              uuid格式


FloatField(IntegerField)


TypedChoiceField(ChoiceField)     与ChoiceField相比,增加对接收到的value值(如{city:'1'},'1'为文本),自动转换为所需的数据类型
    coerce = lambda x: int(x)     自定义一个函数,对选中的值进行一次转换
    empty_value= ''               空值的默认值
    
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda x: int(x)     对选中的每一个值进行一次转换
    empty_value= ''               空值的默认值    
                                  
FilePathField(ChoiceField)        在select下拉框里,列出指定路径下的所有文件或目录名
    path,                         指定的路径
    match=None,                   正则匹配
    recursive=False,              递归下面的文件夹
    allow_files=True,             允许文件
    allow_folders=False,          允许文件夹
    
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     

    
BooleanField(Field)                只能输入布尔值

NullBooleanField(BooleanField)     布尔值和null,三个选项


URLField(Field)


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

    
DurationField(Field)             时间间隔:%d %H:%M:%S.%f
View Code

补充:

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

 

二、内置字段的widget参数

每个field对象自动生成HTML标签是通过它里面封装的widget(插件)实现的,每种field都有自己的默认插件,一种类型的插件生成一种类型的input框。

生成html时,实际上是调用的field对象内部的__str__方法,返回一个html文本字符串。

字段的插件可通过widget字段修改。

  • 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

1、常用的选择插件

1、单选radio  
'''                
接收到的cleaned_data格式为:{'city': '2'}
'''
	
	# 写法1:
	city = fields.ChoiceField(
		choices=((0, '女'), (1, '男')),
		initial=2,
		widget=widgets.RadioSelect,
	)

	# 写法2:
	city = fields.CharField(
		initial=2,
		widget=widgets.RadioSelect(choices=((0, '女'), (1, '男'))),	
	)

2、单选select
'''                
接收到的cleaned_data格式为:{'city': '2'}
'''
	
	写法1:
	city = fields.ChoiceField(
		choices=((0, '上海'), (1, '北京'), (2, '广州')),
		initial=2,
		widget=widgets.Select,                   # 写不写都一样,默认就是这个插件
	)

	写法2:
	city = fields.CharField(
		initial=1,
		widget=widgets.Select(choices=((0, '上海'), (1, '北京'), (2, '广州'))),	
	)

3、多选select                 
'''
接收到的cleaned_data格式为: {'city': ['1', '2']}
'''
	
	写法1:
	city = fields.MultipleChoiceField(
		choices=((0, '上海'), (1, '北京'), (2, '广州')),
		initial=[0, 2],
		widget=widgets.SelectMultiple,            # 写不写都一样,默认就是这个插件
	)

	写法2:
	city = fields.MultipleChoiceField(             # 因为结果为多个值,不能用CharField
		initial=[1, 2],
		widget=widgets.SelectMultiple(choices=((0, '上海'), (1, '北京'), (2, '广州'))),
	)

4、单选checkbox               
'''
接收到的cleaned_data格式为:{'city': 'True'}
'''
   
   city = fields.CharField(              # 选中为True,没选中为False
        widget=widgets.CheckboxInput()
    )
	
5、多选checkbox               
'''
接收到的cleaned_data格式为: {'city': ['1', '2']}
'''
    
	写法1:
	city = fields.MultipleChoiceField(
        choices=((0, '上海'), (1, '北京'), (2, '广州')),
        initial=[2,],
        widget=widgets.CheckboxSelectMultiple,
    )
    写法2:
    city = fields.MultipleChoiceField(
        initial=[0, 2],
        widget=widgets.CheckboxSelectMultiple(choices=((0, '上海'), (1, '北京'), (2, '广州'))),
    )

  在生成HTML的同时,给生成的标签加属性的方法:

widget=widgets.ChoiceField(attrs={'placeholder': '姓名','class':'form-control'})    #用attrs参数输入一个字典

2、"选项"从数据库中动态获取

字段的choice的初始值可以从数据库中获取,但是由于是静态字段,获取的值无法实时更新,所以需要自定义构造方法从而达到此目的(每次创建对象时都更新值)。

(1)方式一:(推荐,适用性广)

from django.forms import Form
from django.forms import fields
from django.forms import widgets
from app01 import models


class MyForm(Form):

    cs_id = fields.CharField(
        widget=widgets.Select,
        initial=2,
    )

    def __init__(self,*args,**kwargs):
        super(MyForm, self).__init__(*args,**kwargs)   # 去内存中拷贝一遍所有静态字段,赋值给self.fields
        # self.fields['cs_id'].widget.choices = ((1, '一班'), (2, '二班'),)
        # 或 (注意:1、所需参数的数据结构 2、自定义值要写在super后面,否则值会被初始值覆盖)
        self.fields['cs_id'].widget.choices = models.Classes.objects.all().values_list('id', 'title')

 (2)方式二(不推荐):

from django.forms import Form
from django.forms import models as form_model
from app01 import models


class MyForm(Form):
     
    cs_id = form_model.ModelChoiceField(queryset=models.Classes.objects.all())

 使用方式二注意:在models中对应的类有__str__方法,否则页面上显示的为object,而不是名称

三、更高级的应用—自定义验证规则

上面已经介绍了自定义widget的方法。对于每个类的内置字段里都封装了正则表达式验证,当内置的验证不能满足需求时,也可以自定义。

1、基本方法

(1)用RegexField类,只能验证一条规则。

    tel = fields.RegexField(
        regex='^150\d{8}$',
        error_messages={
            'invalid': '必须是以150开头的11为数字',
        }
    )

(2)用validators字段,可验证多条规则。

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator

class MyForm(Form):
    tel = fields.CharField(
        validators=[                                              # 在CharField内置验证规则基础时,再加入新的验证规则
            RegexValidator(r'^[0-9]+$', '请输入数字'),            # 用RegexValidator这个类创建验证规则对象
            RegexValidator(r'^159[0-9]+$', '必须以159开头', code='invalid2'),   # 参数1为正则表达式,参数2为错误提示,参数3为自定义的错误信息key
        ],
        error_messages={
            'invalid': '...',                                     # validators引入的验证规则,错误信息默认放在invalid下
            'invalid2': '...'                                     # 存放相应code的自定义验证规则的错误信息,即上面的'必须以159开头'
        }
    )
from django.forms import Form
from django.forms import fields
from django.core.exceptions import ValidationError
import re


def mobile_validate1(value):
    mobile_re = re.compile(r'^[0-9]+$')
    if not mobile_re.match(value):
        raise ValidationError('请输入数')


def mobile_validate2(value):
    mobile_re = re.compile(r'^159[0-9]+$')
    if not mobile_re.match(value):
        raise ValidationError('数字必须以159开头')


class MyForm(Form):
    tel = fields.CharField(
        validators=[mobile_validate1, mobile_validate2],
    )
RegexValidator的理解

2、基于源码的扩展

源码:

 

(1)基于源码的扩展---单字段后续验证

场景:比如在创建员工资料时工号通过基本方法验证了,说明符合相关的正则表达式。但符合正则表达式就能插入吗?还得看看数据库中有没有重复的,即执行完基本验证后,还需要根据数据库中的数据进一步验证。

单字段验证的源码:

from django.forms import Form
from django.forms import fields
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
 
 
class MyForm(Form):
     
    tel = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')]
    )
 
    def clean_tel(self):
        """
        要点:
        1、哪个字段执行本验证,名字必须是 clean_该字段名。只对当前字段自己进行验证。
        2、必须返回值self.cleaned_data['tel']        
        3、如果出错:必须用ValidationError抛出错误
        """
        value = self.cleaned_data['tel']
        if len(value) != 11:
            raise ValidationError('长度必须是11位', 'invalid')
        return value

 (2)基于源码的扩展---整体验证

场景:单字段验证,只能保证每个字段能单独通过验证。当数据表的主键是组合码时,比如checkvipamout表中由checkid和vip两个字段组成主键,输入时需要两个字段单独完成验证后再组合起来验证看看有没有重复的。

form处理验证的源码:

 

整体扩展方法 clean()的写法与单字段后续验证类似,不再赘述。

字段错误,键名是字段名;整体的错误,键名是 __all__。

 

 

参考:http://www.cnblogs.com/wupeiqi/articles/6144178.html

 

posted @ 2018-01-25 12:49  seaidler  阅读(178)  评论(0)    收藏  举报