Form详解

Form的功能

  • 验证用户请求(form表单验证)
  • 生成HTML标签(自动生成HTML标签)
  • 保留上一次提交的数据(保留数据)

我们在设计form表单时,有许多字段需要填写,且需要判断用户填写的内容是否合法,此时,就需要对字段进行验证。试想,如果我们自己编程实现的话,需要针对用户填写的各种异常进行错误提示,此时我们需要花费大量精力去应付各种可能出现的错误,然而,Django的form为我们实现了快捷的方式,它相当于已经编写好了一个模板,只需要我们在模板的基础上进行定制。

Django中的Form表单模板是一个类,创建如下:

1、原始Form

views.py文件

from django import forms

# Create your views here.


class FM(forms.Form):
    # 只关心处理自己定义的form表单数据,恶意攻击定义的数据不处理
    user = forms.CharField()
    pwd = forms.CharField()  # 这里的变量名必须和html form里的name保持一致
    email = forms.EmailField()

def fm(request):
    if request.method == "GET":
        return render(request, "fm.html")
    elif request.method == "POST":
        # 获取用户所有数据,每条数据请求的验证
        # 成功 --> 获取所有的正确信息;失败 --> 显示错误信息
        obj = FM(request.POST)
        r1 = obj.is_valid()
        if r1:  # 返回的正确信息
            print(obj.cleaned_data)
        else:   # 返回的错误信息
            print(obj.errors)
            print(obj.errors.as_json())
        return HttpResponse('fm')

  

fm.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <form action="/fm/" method="POST">
        {% csrf_token %}
        <input type="text" name="user">
        <input type="text" name="pwd">
        <input type="text" name="email">
        <input type="submit" value="提交" />
    </form>
</body>
</html>

  

提交表单

 

错误输出

原始错误和json类型的错误

问题??

我们可以从上面看到错误信息,它是最简单的也是最基本的错误信息提示:为空的时候,提示字段需要填写!

此时有一个问题:我们是否可以自己定制错误信息呢?当然可以!Django的Form为我们量身定制了,而且非常简单。

 

2、自定义form错误信息

修改views.py

from django import forms
class FM(forms.Form):
    # 只关心处理自己定义的form表单数据,恶意攻击定义的数据不处理
    user = forms.CharField(error_messages={'required':'用户名不能为空'})
    pwd = forms.CharField(
        max_length=12,
        min_length=6,
        error_messages={'required':'密码不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'}
    )
    email = forms.EmailField(error_messages={'required':'用户名不能为空','invalid':'邮箱格式错误'})

def fm(request):
    if request.method == "GET":
        return render(request, "fm.html")
    elif request.method == "POST":
        obj = FM(request.POST)
        r1 = obj.is_valid()
        if r1:  # 返回的正确信息
            print(obj.cleaned_data)
        else:   # 返回的错误信息
            # ErrorDict
            # print(obj.errors['user'][0])
            # print(obj.errors.as_json())
            return render(request,'fm.html',{'obj':obj})
        return render(request,'fm.html')

  

修改前端html文件

 <form action="/fm/" method="POST">
        {% csrf_token %}
        <p><input type="text" name="user"> {{ obj.errors.user.0 }}</p>
        <p><input type="text" name="pwd"> {{ obj.errors.pwd.0 }}</p>
        <p><input type="text" name="email"> {{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交" />
    </form>

  

错误提示

 

问题??

此时,都有对应的错误提示了,但是有一个比较严重的问题,就是上一次提交的内容一旦提交后,就消失了!

怎么解决?

3、保留上次提交的数据

 

修改fm.html文件

 

 <form action="/fm/" method="POST">
        {% csrf_token %}
        <p>{{ obj.user }} {{ obj.errors.user.0 }}</p>
        <p>{{ obj.pwd }} {{ obj.errors.pwd.0 }}</p>
        <p>{{ obj.email }} {{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交" />
    </form>

 

views.py文件修改:

 

def fm(request):
    if request.method == "GET":
        obj = FM()
        return render(request, "fm.html",{'obj':obj})
    elif request.method == "POST":
        obj = FM(request.POST)
        r1 = obj.is_valid()
        if r1:  # 返回的正确信息
            print(obj.cleaned_data)
        else:   # 返回的错误信息
            # ErrorDict
            # print(obj.errors['user'][0])
            # print(obj.errors.as_json())
            return render(request,'fm.html',{'obj':obj})
        return HttpResponse('提交成功!')

 

 

提交成功后:

 

 

4、更简洁的html标签生成

  • obj.as_p
  • obj.as_ul
  • obj.as_table

注意,字段的lable可以定制:通过label参数

 user = forms.CharField(error_messages={'required':'用户名不能为空'},label='用户名')

  

fm.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
     <form action="/fm/" method="POST">
        {% csrf_token %}
        {{ obj.as_p }}
        <input type="submit" value="提交" />
    </form>
</body>
</html>

  

 效果如下:

 

5、插件widget

form类里面的字段,只有一个功能,就是验证客户端发过来的数据。生成html的功能做不了。

但是怎么生成的html标签呢,在charfield里面有个插件,插件生成的。在其源码里做了html字符串的拼接返回。

from django import forms
from django.forms import widgets  # 插件在这里面
class FM(forms.Form):
    # 字段本身只做验证
    user = forms.CharField(         # 修改html标签,并指定样式##############
        error_messages={'required':'用户名不能为空'},
        widget=widgets.Textarea(attrs={'class':'c1'}),  # 页面再看就是textarea了
        label="用户名"
    )
    pwd = forms.CharField(
        max_length=12,
        min_length=6,
        error_messages={'required':'密码不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'},
        widget=widgets.PasswordInput  # 密码密文显示,如果自定义样式也可加上(attrs……)
    )
    email = forms.EmailField(error_messages={'required':'用户名不能为空','invalid':'邮箱格式错误'})

  

字段都在from django.forms import fields里面,所以上面的forms可以改用fields

email = fields.EmailField()

前端<p>{{ obj.user.label }}{{ obj.user }} {{ obj.errors.user.0 }}</p>插件里面input、checkbox、select、redio等全部都有。

 

 

6、常用插件

# 单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
# )

  

7、内置插件

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

  

8、form 内置字段

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

  

9、初始化操作

在Web应用程序中开发编写功能时,时常用到获取数据库中的数据并将值初始化在HTML中的标签上。

def fm(request):
    if request.method == "GET":
        dic = {
            "user":'r1',
            "pwd":'123456',
            "email":'aera@11.com',
            "city":1,
            "city2":[1,2],
        }
        obj = FM(initial=dic)  # 初始化
        return render(request, "fm.html",{'obj':obj})
    

10、widget动态更新数据

models.py文件

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.

class UserType(models.Model):
    caption = models.CharField(max_length=32)

    def __str__(self):
        return self.caption

  

forms.py文件

#coding:utf-8
from django import forms
from django.forms import fields
from app01 import models
from django.core.exceptions import ValidationError

class UserInfoForm(forms.Form):
    username = fields.CharField(max_length=32)
    email = fields.EmailField(max_length=32)
    user_type = fields.ChoiceField(
        choices=models.UserType.objects.values_list('id','caption')
    )

  

views.py文件

def index(request):
    if request.method == 'GET':
        obj = UserInfoForm()
        new_obj = models.User.objects.all()
        return render(request,'index.html',{'obj':obj,'new_obj':new_obj})
    elif request.method == 'POST':
        obj = UserInfoForm(request.POST)
        if obj.is_valid():
            rt_dic = obj.cleaned_data
            username = rt_dic.get('username')
            email = rt_dic.get('email')
            user_type_id = rt_dic.get('user_type')
            ut = models.UserType.objects.filter(id=user_type_id).first()
            models.User.objects.create(username=username,email=email,user_type=ut)
        return render(request,'index.html',{'obj':obj})

  

问题??

上面有个问题,就是数据库添加数据后,django需要重启网页才能看到新加的数据。为什么会这样呢?

forms里面,定义的类,类里的username、email、user_type都是静态字段(类变量),这些都是属于类的。

在__init__里面写的属于对象。

而启动Django的时候,类变量一次性都加在到内存了。

上面views.py里的obj = UserInfoForm()生成了一个对象,会执行类里的__init__方法,会把类里的字段封装到obj.fields里面(里面有username、email、user_type三个字段),所以:

我们可以考虑在form加载的时候进行数据初始化,也就是在forms.py的__init__()里面做文章:

# 下面的操作是让数据在网页上实时更新:每次刷新时,必先执行父类的初始化函数,再设定下拉列表框选项。
    def __init__(self, *args, **kwargs):

        super(UserInfoForm,self).__init__(*args, **kwargs)
        self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')

  

通过widgets还有另外两种方法进行更新:

#coding:utf-8
from django import forms
from django.forms import fields,widgets,ModelChoiceField
from app01 import models
from django.core.exceptions import ValidationError

class UserInfoForm(forms.Form):
    username = fields.CharField(max_length=32)
    email = fields.EmailField(max_length=32)
    #和user_type2类似,只是参数改变了方式
    user_type = fields.ChoiceField(
        choices=[],
        widget=widgets.Select
    )
    user_type2 = fields.ChoiceField(
        widget=widgets.Select(choices=[])
    )
    #ModelChoiceField参数
    """

    ModelChoiceField(ChoiceField)
       django.forms.models.ModelChoiceField
      queryset, # 查询数据库中的数据
      empty_label="---------", # 默认空显示内容
      to_field_name=None, # HTML中value的值对应的字段(html源码中value不同)
      limit_choices_to=None # ModelForm中对queryset二次筛选

  """
    user_type3 = ModelChoiceField(
        queryset=models.UserType.objects.all()
    )

    # 下面的操作是让数据在网页上实时更新:每次刷新时,必先执行父类的初始化函数,再设定下拉列表框选项。
    def __init__(self, *args, **kwargs):

        super(UserInfoForm,self).__init__(*args, **kwargs)
        self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')
        self.fields['user_type2'].widget.choices = models.UserType.objects.values_list('id', 'caption')

  

  

实现效果如下:

当然,在models.py文件里面还需要新增__str__()方法:

class UserType(models.Model):
    caption = models.CharField(max_length=32)

    def __str__(self):
        return self.caption

  

11、内置钩子

初始化操作

Form里面加一个字典数据,生成显示默认值

def index(request):
    from cmdb.forms import UserInfoForm
    obj = UserInfoForm({'username':'xq','email':'xq@qq.com','user_type':'1'})  # 默认值
    return render(request, 'index.html', {'obj':obj})

  效果如下:

数据验证

obj.is_valid()#做简单的数据验证,查看源码可以知道,这个方法有2个功能:一个是判断是否有字段为空,一个是判断是否有错误信息。

    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors

  self.is_bound = data is not None or files is not None

  问题??虽然我们可以通过is_valid()方法进行简单的验证,但是错误提示和错误信息我们都无法定制。

怎么解决呢?form提供了接口:

    #清洗单个字段
def clean_username(self): # value = self.cleaned_data['username'] if value == 'root': return value else: raise ValidationError('你不是超级用户...')
#清洗钩子,可以自定义 def clean(self): username = self.cleaned_data.get('username') email = self.cleaned_data.get('email') if models.User.objects.filter(username=username, email=email).count() == 1: raise ValidationError('用户名和邮箱联合唯一索引重复!!!') return self.cleaned_data   #清洗钩子,可以自定义 def _post_clean(self): print 'aaa %s' %self.cleaned_data username = self.cleaned_data['username'] email = self.cleaned_data['email'] if models.User.objects.filter(username=username,email=email).count()==1: self.add_error("__all__", ValidationError('用户名和邮箱联合唯一索引重复!!!'))

  

在form验证里面的顺序:

is_valid()===》self.errors()和self.is_bound()===>self.full_clean()===》self._clean_fields()===>self._clean_form()===》self._post_clean()

form验证,

  • 先第一个字段正则表达式判断,执行字段钩子函数clean_fields();
  • 第二个字段正则,第二个的钩子;
  • 所有字段完成后,执行clean()钩子;
  • clean执行完后,执行_post_clean()钩子

12、序列化错误信息

不管提交数据是浏览器提交、还是curl方式,还是ajax提交,只要是request.POST都可以做验证。

res['error'] = obj.errors.as_json() # 转为json格式

<script src="/static/jquery-1.12.4.js"></script>
    <script>
        // 页面框架加载完自动执行
        $('#submit').click(function(){
            $.ajax({
                url:'/login.html',
                type:'POST',
                data:$('#fm').serialize(),
                success:function(arg){
                    console.log(arg);
                    arg = JSON.parse(arg);  // 转为字典
                    console.log(arg);
                },
                error: function(){

                }
            })
        })
    </script>

  

上面的方式可以实现,只是前端需要反解两次,不太好,下面优化一下。

  • as_json : 生成json格式
  • as_data : 生成dict数据。
from django import forms
from django.forms import widgets, fields
class LoginForm(forms.Form):
    username = fields.CharField()
    password = fields.CharField(
        max_length=64,
        min_length=12
    )

# 序列化,转换为指定数据类型
import json
from django.core.exceptions import ValidationError
class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, ValidationError):
            return {'code':field.code, 'messages': field.messages}
        else:
            return json.JSONEncoder.default(self, field)

def login(request):
    res = {'status':True, 'error':None, 'data': None}
    if request.method == "GET":
        return render(request,"login.html")
    elif request.method == "POST":
        obj = LoginForm(request.POST)
        if obj.is_valid():
            print(obj.cleand_data)
        else:
            # print(obj.errors, type(obj.errors))
            # res['error'] = obj.errors.as_json()  # 转为json格式
            print(type(obj.errors.as_data()))
            for k,v in obj.errors.as_data().items():
                print(k,v)  # 这里v是ValidationError类型,不能序列化
            res['error'] = obj.errors.as_data()
        result = json.dumps(res, cls=JsonCustomEncoder)
        return HttpResponse(json.dumps(result))

  

13、序列化操作

 

Json.dumps用来做序列化,但是只能序列化一些简单的基本数据类型。对于无法序列化的数据类型,只能自定制了。

ErrorDict自定义JSONEncoder

由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:

import json 
from datetime import date 
from datetime import datetime 

class JsonCustomEncoder(json.JSONEncoder): 

    def default(self, field): 

        if isinstance(field, datetime): 
            return o.strftime('%Y-%m-%d %H:%M:%S') 
        elif isinstance(field, date): 
            return o.strftime('%Y-%m-%d')   # 转成字符串类型
        else: 
            return json.JSONEncoder.default(self, field) 

v = {'k':'123', "k1":datetime.datetime.now()}   
ds = json.dumps(v, cls=JsonCustomEncoder) 

  

QuerySet 第一种序列化方式

上面自己写实现也可以,不过Django提供了方法做序列化

from django.core import serializers

v = models.tb.objects.all()
data = serializers.serialize("json", v)

  

QuerySet 第二种序列化方式

import json 
from datetime import date 
from datetime import datetime 

class JsonCustomEncoder(json.JSONEncoder): 

    def default(self, field): 

        if isinstance(field, datetime): 
            return field.strftime('%Y-%m-%d %H:%M:%S') 
        elif isinstance(field, date): 
            return field.strftime('%Y-%m-%d') 
        else: 
            return json.JSONEncoder.default(self, field) 

v = models.tb.objects.values('id','name','ctime')
v = list(v)  # 把(类似列表的queryset类型)转换为列表
v = json.dumps(v,cls=JsonCustomEncoder)  # 这里cls只对ctime操作。
# 如果没有ctime这个类型,只写上边的三句就可以实现

  

14、Form总结

 我们在程序中使用Form表单非常多,Django中的Form类除了具备form表单功能外,它最强大的是它的数据验证功能,让编程人员省去了很多事情,而且它的数据验证非常灵活。

a、Form数据字段验证过程:

首先,我们可以使用is_valid()方法,对表单字段进行简单数据验证(是否为空或有errors),其中errors会调用full_clean(),【空字段或简单错误

紧接着,full_clean()函数里面会有每个字段的正则,clean_field()然后会调用_clean_form(),它里面会有clean()钩子,【清洗字段、清洗整个表单,可灵活自定义

最后会调用_post_clean()钩子。【清洗提交数据,可灵活自定义】

b、Form数据返回值:

is_valid()进行数据验证,然后通过cleaned_data获取数据字典,errors获取错误字典。

c、Form数据实时更新:

在form类初始化时,把对象进行实时拉取最新数据,保证数据为最新。

        super(UserInfoForm,self).__init__(*args, **kwargs)
        self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')
        self.fields['user_type2'].widget.choices = models.UserType.objects.values_list('id', 'caption')

d、序列化数据

posted @ 2018-10-08 22:20  skyflask  阅读(880)  评论(0编辑  收藏  举报