Django之Form组件

一 Form的功能

  • 用户请求数据验证   ***
  • 自动生成错误信息
  • 打包用户提交的正确信息
  • 自动生成HTML标签(可以通过插件设置样式)***
  • HTML From提交保留上次提交的数据

二 Form组件的使用

2.1  创建Form类

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

class xxx(Form):	#必须继承Form
	xx = fields.CharField(required=True, max_length=None, min_length=None, error_message=None)
	#required:是否为空
	#max_length:最大长度
	#min_length:最小长度
	#error_message:错误信息

2.2  使用

#创建对象,将数据和规则(类)进行匹配
obj = xxx(request.POST)

# 是否校验成功
obj.is_valid()

# 所有错误信息
obj.errors

# 正确信息
obj.cleaned_data    #字典格式

注意:html标签name属性必须等于Form类字段名

2.3 实例

from django.shortcuts import render,redirect
from django.forms import Form
from django.forms import fields

class loginRegisterFrom(Form):
    '''用户登录注册验证'''
    # 用户名验证: 不能为空,长度在4-16位
    username = fields.CharField(
        max_length=16,
        min_length=4,
        required=True,
        error_messages={
            'required': '用户名不能为空!',
            'min_length': '用户名长度低于4位!',
            'max_length': '用户名长度高于16位!',
        }
    )
    # 邮箱验证: 不能为空
    email = fields.EmailField(
        required=True,
        error_messages={"required": "邮箱不能为空!", "invalid": "无效的邮箱!"},
    )
    # 密码验证: 不能为空,长度大于8位
    password = fields.CharField(
        required=True,
        min_length=8,
        error_messages={
            'required': '密码不能为空!',
            'min_length':'密码长度低于8位'
        }
    )

def login(request):
    '''登录'''
    if request.method == 'GET':
        return render(request, 'login_register.html', {'page_name': '登录'})
    else:
        obj = loginRegisterFrom(request.POST)
        if obj.is_valid():
            # 用户输入格式正确
            print(obj.cleaned_data)  # 字典类型 {'email': '123@qq.com', 'username': 'abcd', 'password': '12345678'}
            return redirect('http://www.baidu.com')
        else:
            # 用户输入格式错误
            print(obj.errors)
            return render(request, 'login_register.html', {'page_name': '登录','obj': obj})

def register(request):
    '''注册'''
    if request.method == 'GET':
        return render(request, 'login_register.html', {'page_name': '注册'})
    else:
        pass
view.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" action="/login/">
        {% csrf_token %}
        <h3>{{ page_name }}</h3>
        <p><input type="text" name="username" placeholder="用户名">{{ obj.errors.username.0 }}</p>
        <p><input type="text" name="email" placeholder="邮箱">{{ obj.errors.email.0 }}</p>
        <p><input type="text" name="password" placeholder="密码">{{ obj.errors.password.0 }}</p>
        <p><input type="submit" value="提交"></p>
    </form>
</body>
</html>
login_register.html
<ul class="errorlist">
<li>email
	<ul class="errorlist">
	    <li>无效的邮箱!</li>
	</ul>
</li>
<li>username
	<ul class="errorlist">
	    <li>用户名长度低于4位!</li>
</ul>
</li>
<li>password
<ul class="errorlist">
	    <li>密码长度低于8位</li>
	</ul>
</li>
</ul>
输入格式错误信息样式

三 Form组件内部原理(简单分析)

借助上面的实例,分析如下:

def login(request):
    if request.method == 'GET':
        return render(request, 'login_register.html', {'page_name': '登录'})
    else:
        obj = loginRegisterFrom(request.POST)
		'''
		1.loginRegisterFrom实例化时,
			self.fields={
			'username':正则表达式
			'email':正则表达式
			'password':正则表达式
			}
		2.循环self.fields
			flag = True  			#校验成功与否初始值
			errors       			#用于收集错误信息
			cleaned_data 			#用于收集正确信息
			for k,v in self.fields.items():
				k:usernam...
				v:正则表达式
				input_value = request.POST.get(k)
				正则表达式和input_value进行匹配
				flag = False
			return flag
		'''
        if obj.is_valid():
            # 用户输入格式正确
            print(obj.cleaned_data)  # 字典类型 {'email': '123@qq.com', 'username': 'abcd', 'password': '12345678'}
            return redirect('http://www.baidu.com')
        else:
            # 用户输入格式错误
            print(obj.errors)
            return render(request, 'login_register.html', {'page_name': '登录','obj': obj})

四 Form类中的字段、插件

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

4.1 字段

所谓字段,可以理解为不同的字段对应不同的正则表达式,我们可以使用Form中内置的字段以使用其正则表达式,我们也可以通过RegexField()内置字段自定义正则表达式。

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类型
Django内置字段清单

对于以上清单中的参数做如下分类:

  • 验证类:required,error_messages,max_length,min_length等
  • 生成HTML标签类:widget,label,disable,label_suffix,initial,help_text等

注: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')
View Code

4.2 插件

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
Django内置插件

注意:若只给定字段,未指定元素或插件,比如:xxx=fields.CharField()里面包含验证规则+HTML标签插件

五 如何保留上次输入的信息

根据目前学习到的知识,实现了数据form表单数据的验证,但是有一个问题,当我们某一项输入错误点击提交后,所有数据被刷新,失去上次内容,那我们怎么保留上次输入内容呢?

5.1 通过Form实现

这里我们需要借助Form组件自动生成HTML标签功能,具体实现如下:

views.py

from django.shortcuts import render,redirect,HttpResponse
from django.forms import Form
from django.forms import fields
class registerFrom(Form):
    '''注册验证'''
    # 用户名验证: 不能为空,长度在4-16位
    username = fields.CharField(
        max_length=16,
        min_length=4,
        required=True,
        label='用户名',
        #label_suffix=':',
        error_messages={
            'required': '用户名不能为空!',
            'min_length': '用户名长度低于4位!',
            'max_length': '用户名长度高于16位!',
        }
    )
    # 邮箱验证: 不能为空
    email = fields.EmailField(
        required=True,
        label='邮箱',
        #label_suffix=':',
        error_messages={"required": "邮箱不能为空!", "invalid": "无效的邮箱!"},
    )
    # 密码验证: 不能为空,长度大于8位
    password = fields.CharField(
        required=True,
        min_length=8,
        label='密码',
        #label_suffix=':',
        error_messages={
            'required': '密码不能为空!',
            'min_length':'密码长度低于8位'
        }
    )

def register(request):
    if request.method == "GET":
        obj = registerFrom()   # 生成HTML标签<input .... >
        return render(request, 'register.html', {'obj':obj})
    else:
        obj = registerFrom(request.POST) # 生成HTML标签<input .... value=‘xxx'>
        if obj.is_valid():
            print(obj.cleaned_data)
        else:
            print(obj.errors)
        return render(request, 'register.html', {'obj': obj})

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" action="/register/" novalidate>
        {%  csrf_token %}
        {# 方式一:#}
        <p>{{ obj.username.label }}{{ obj.username }}{{ obj.errors.username.0 }}</p>
        <p>{{ obj.email.label }}{{ obj.email }}{{ obj.errors.email.0 }}</p>
        <p>{{ obj.password.label }}{{ obj.password }}{{ obj.errors.password.0 }}</p>
        {# 方式二:该方式错误信息在前端以列表的方式显示在input标签的上放#}
        {# {{ obj.as_p }}#}
        <input type="submit" value="提交" />
    </form>
</body>
</html>
<form method="POST" action="/register/">
	{% csrf_token %}
	{{ form.xxoo.label }}
	{{ form.xxoo.id_for_label }}
	{{ form.xxoo.label_tag }}
	{{ form.xxoo.errors }}
</form>
其他标签(参数)使用

注:from表单内加上novalidate参数,取消浏览器的验证功能

5.2 通过Ajax实现

views.py

#registerFrom类同上
def ajax_register(request):
    '''aja注册'''
    if request.method == 'GET':
        return render(request, 'ajax_register.html')
    else:
        obj = registerFrom(request.POST)
        ret = {'status':True, 'msg':None}
        if obj.is_valid():
            # 用户输入格式正确
            print(obj.cleaned_data)
        else:
            # 用户输入格式错误
            #print(obj.errors)
            ret = {'status': False, 'msg': obj.errors}
        import json
        return HttpResponse(json.dumps(ret))

ajax_register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form id='f1' method="post" action="/ajax_register/">
        {% csrf_token %}
        <h3>{{ page_name }}</h3>
        <p><input type="text" name="username" placeholder="用户名"></p>
        <p><input type="text" name="email" placeholder="邮箱"></p>
        <p><input type="text" name="password" placeholder="密码"></p>
        <p><a onclick="submitFrom()">提交</a></p>
    </form>
    <script src="/static/jquery-3.2.1.js"></script>
    <script>
        function submitFrom() {
            $('.c1').remove();
            $.ajax({
                url:'/ajax_register/',
                type: 'POST',
                data: $('#f1').serialize(), //user=alex&pwd=456&csrftoen=dfdf ****
                dataType: 'JSON',
                success:function (arg) {
                    if(arg.status){
                        console.log('ok')
                    }else {
                        $.each(arg.msg,function (index,value) {
                            var tag = document.createElement('span');
                            tag.innerHTML = value[0];
                            tag.className = 'c1';
                            $('#f1').find('input[name="'+ index +'"]').after(tag);
                        })
                    }
                    }
                }
            )
        }
    </script>
</body>
</html>

5.3 两种方式类比

From方式:

  • 刷新,失去上次内容--借助Form组件自动生成HTML标签功能实现上次数据保留
  • 显示错误信息
  • 使用验证功能,生成HTML标签功能

Ajax方式:

  • 不刷新,上次内容自动保留
  • 不显示错误信息--借助js添加错误信息标签
  • 仅使用验证功能

六 实例分析

实例地址:待补充

6.1 Form对象添加数据

#Form对象中添加数据时,如果为:
obj = xxxForm(data={‘k1’:’v1’})      #里面包括HTML标签及错误信息(校验)
#如果不需要进行校验,可以写成:
obj = xxxForm(initial={‘k1’:’v1’}),
#obj = xxxForm(request.POST)的本质为:obj = xxxForm(data=request.POST),

6.2 选择框的使用

#单选	
#方式一(CharField/IntegerField...+widgets.Select()):
xx = fields.IntegerField(
	# widget = widgets.Select(choices = (1,'北京'),(2,'上海'),(3,'深圳'),])    #自定义
	widget = widgets.Select(choices = models.Classes.objects.values_list('id','tname'),attrs={'属性名': '属性值'})   #通过数据库获取
)
#注意:widget = widgets.Select(choices=models.Classes.objects.values_list('id','cname'),attrs={‘multiple’: ‘multiple’}) 无用,内部会把他当成单选		
#Select内部取值方式为request.POST.get(),只能取一个值

#方式二(ChoiceField+widgets.Select):
xx = fields.ChoiceField(
	choices = models.Classes.objects.values_list('id','tname'),   #单独一行,不能放在Select中
	widget = widgets.Select(attrs = {'属性名': '属性值'})     #通过指定attrs = {'属性名': '属性值'}可控制前端样式,无属性时可不要()
)
#对象初始化(指定默认值):obj = xxxForm({'cls_id':1})


#多选(MulipleChoiceFiled+widgets.SelectMultiple)
xx = fields.MultipleChoiceField(
	choices = models.Classes.objects.values_list('id','title'),    #单独一行,不能放在Select中
	widget = widgets.SelectMultiple		 #通过指定attrs = {'属性名': '属性值'}可控制前端样式,无属性时可不要()
)
#对象初始化(指定默认值):obj = xxxForm({'cls_id':[1,2,3]})	

多选方式的错误示范:

image

6.3 无法实时获取更新的值bug处理

使用select标签时,需要注意choices的选项可以从数据库中获取,但因为是静态字段无法实时获取更新的值,造成代码bug,有以下两种处理方式。

方式一:自定义构造方法(建议使用)
class studentForm(Form):
    sname = fields.CharField(min_length=2,max_length=32)
    email = fields.EmailField()
    age = fields.IntegerField(min_value=18,max_value=45)
    cls_id = fields.IntegerField(
        #从数据库中获取选择框数据
        #choices = models.Classes.objects.values_list('id', 'cname'),   #当加入自定义构造方法后,可省略
        widget = widgets.Select
    )
    def __init__(self, *args, **kwargs):
        '''自定义构造方法'''
        super(studentForm,self).__init__(*args,**kwargs)
        self.fields['cls_id'].widget.choices=models.Classes.objects.values_list('id','cname')

补充说明:

# 当我们创建对象时obj = teacherForm(),程序执行下面2步:
# 1. 找到所有字段
# 2. 将所有字段加入到self.fields中
#    self.fields = {
#        字段1: ...
#        字段2: ...
#	 ... ...
#    }	

方式二:使用Django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

from django.forms import models as form_model
class studentForm(Form):
    sname = fields.CharField(min_length=2,max_length=32)
    email = fields.EmailField()
    age = fields.IntegerField(min_value=18,max_value=45)
    cls_id = form_model.ModelChoiceField(queryset=models.Classes.objects.all())          #单选使用
    #xxxx = form_model.ModelMultipleChoiceField(queryset=models.Classes.objects.all())   #多选使用

但此时存在一个问题,访问时是一个一个对象,需要在models中对Classes做如下修改:

class Classes(models.Model):
    cname = models.CharField(max_length=32)
    def __str__(self):
        return self.cname

image

6.4 其他知识点补充

1. 创建Form类时,保持字段名与数据库字段名一致

2. zip函数(拉链函数)

>>> a = [(1,2,3),(11,22,33)]
>>> list(zip(*a))
[(1, 11), (2, 22), (3, 33)]
>>> list(zip(*a))[0]
(1, 11)

3. 三元运算

a if a>b else b  #如果a>b成立,则为a,否则为b

4. 格式化代码格式:

pycharm中:菜单栏Code—>Reformat Code

七 常用选择插件

class testForm(Form):
    # 单radio,值为字符串
    x1 = fields.CharField(
        initial = 1,     #初始选择对象
        widget = widgets.RadioSelect(choices=((1, '男'), (2, '女')))
    )

    # 单radio,值为字符串
    x2 = fields.ChoiceField(
        choices =((1, '男'), (2, '女')),
        initial = 2,
        widget = widgets.RadioSelect
    )

    # 单select,值为字符串
    x3 = fields.CharField(
        initial = 2,
        widget = widgets.Select(choices = ((1, '北京'), (2, '上海'), (3, '深圳')))
    )

    # 单select,值为字符串
    x4 = fields.ChoiceField(
        choices = ((1, '北京'), (2, '上海'), (3, '深圳')),
        initial = 2,
        widget = widgets.Select
    )

    # 多选select,值为列表
    x5 = fields.MultipleChoiceField(
        choices = ((1, '北京'), (2, '上海'), (3, '深圳')),
        initial = [1,2,],
        widget = widgets.SelectMultiple
    )

    # 单checkbox
    x6 = fields.CharField(
        widget = widgets.CheckboxInput
    )

    # 多选checkbox,值为列表
    x7 = fields.MultipleChoiceField(
        choices = ((1, '北京'), (2, '上海'), (3, '深圳')),
        # initial = [2,3,],
        widget = widgets.CheckboxSelectMultiple
    )

    # 文件
    x8 = fields.FileField(
        widget = widgets.FileInput
    )

    # 文本框
    x9 = fields.CharField(
        widget = widgets.Textarea
    )

八、扩展方法

8.1 自定义验证规则

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    xx = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )

更多方法可参考:http://www.cnblogs.com/wupeiqi/articles/6144178.html

8.2 方法扩展

寻找路径:

image

用法示例:

from django.core.exceptions import ValidationError
class registerForm(Form):
    '''用户注册'''
    user = fields.CharField(min_length=2,max_length=8)
    emial = fields.EmailField()
    pwd = fields.CharField(min_length=8,max_length=32)

    def clean_user(self):
        '''第一个钩子函数,针对每一个字段无法用正则验证的其他验证,比如:用户注册判断是否存在于数据库中等'''
        v = self.cleaned_data['user']
        if models.userInfo.objects.filter(name=v).count():
            raise ValidationError('用户名已存在')
        return v       #必须有返回值,不然返回为None,导致cleaned_data数据清空

    def clean_email(self):
        '''邮箱处理'''
        pass

    def clean_pwd(self):
        '''密码处理'''
        pass

    def clean(self):
        '''第二个钩子函数,对整体进行额外的验证,比如对用户和邮箱做联合唯一索引'''
        user = self.cleaned_data.get('user')
        email = self.cleaned_data.get('email')
        if models.userInfo.objects.filter(name=user,email=email).count():
            raise ValidationError('用户名邮箱已注册')  # 视图函数中通过clean_error = form.errors.get("__all__")获得,前端使用clean_error.0
        return self.cleaned_data

    def _post_clean(self):
        '''第三个钩子函数,自定义(不常用)'''
        pass

两次密码是否相同的几种验证方式:

# 视图
from django.shortcuts import render, HttpResponse, redirect
from django.forms import Form, fields, widgets
from django.core.exceptions import ValidationError


wid_01 = widgets.TextInput(attrs={"class":"form_control"})
wid_02 = widgets.PasswordInput(attrs={"class":"form_control"})

class UserForm(Form):
    name = fields.CharField(max_length=32,widget=wid_01)
    pwd = fields.CharField(max_length=32,widget=wid_02)
    r_pwd = fields.CharField(max_length=32,widget=wid_02)
    email = fields.EmailField(widget=wid_01)
    tel = fields.CharField(max_length=32,widget=wid_01)

    # 方式一:通过clean_字段名()方式验证
    def clean_r_pwd(self):
        """
        两次密码确认,该方式需要严格按照顺利执行
        :return:
        """
        pwd = self.cleaned_data['pwd']
        r_pwd = self.cleaned_data['r_pwd']
        if pwd == r_pwd:
            return r_pwd
        raise ValidationError('方式一:密码不一致')
    
    # 方式二:利用clean()+add_error()方式验证
    def clean(self):
        """
        执行完后再执行密码验证
        :return:
        """
        pwd = self.cleaned_data['pwd']
        r_pwd = self.cleaned_data['r_pwd']
        if pwd == r_pwd:
            # return self.cleaned_data
            return None
        self.add_error("r_pwd",ValidationError('方式二:密码不一致'))
        
    # 方式三:利用clean(),前端利用clean_error = form.errors.get("__all__")方式验证
    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('方式三:密码不一致')

def form_register(request):
    if request.method == "POST":
        form = UserForm(request.POST)
        if form.is_valid():
            print(form.cleaned_data) # 所有干净的字段以及对应的值
            return HttpResponse("OK")
        else:
            clean_error = form.errors.get("__all__")
            return render(request, "form_register4.html", locals())
    form = UserForm()
    return render(request,"form_register.html",locals())


# 模板 form_register.html
<!DOCTYPE>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件-->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-
    BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-lg-offset-3">
            <form method="post" action="" novalidate>
                <h3>注册</h3>
                {% csrf_token %}
                {% for field in form %}
                <div>
                    <label for="user">{{ field.label }}</label>
                    {{ field }}
                    <span style="color: red">
                    <!--方式三时加入该段代码-->
                        <!--{% if field.label == "R pwd" %}-->
                            <!--<span>{{ clean_error.0 }}</span>-->
                        <!--{% endif %}-->
                        {{ field.errors.0 }}
                    </span>
                </div>
                {% endfor %}
                <input type="submit" value="提交" class="btn btn-default" />
            </form>
        </div>
    </div>
</div>
View Code

说明:locals()

Python 的内建函数 locals() ,它返回的字典对所有局部变量的名称与值进行映射,所以我们在上面示例中可以用locals()替代复杂的{},但是我们需要注意的是它包括所有的局部变量,它们可能比你想让模板访问的要多。 比如:上面的locals()具体如下:

{'request': <WSGIRequest: POST '/form_register2/'>, 'clean_error': ['方式三:密码不一致'], 'form': <UserForm bound=True, valid=False, fields=(name;pwd;r_pwd;email;tel)>}

九 其他

在页面展示方面,有以下两种方式:

方式一:灵活,可以在前端任意布局(推荐使用)

<form>
	{{obj.xx}}
	{{obj.xx}}
	{{obj.xx}}
	{{obj.xx}}
</form>

方式二:生成简单,但Form类中需要进行前缀、后缀等设置

//生成p标签
{{obj.as_p}}
//生成ul标签
<ul>   
	{{obj.as_ul}}
</ul>
//生成table标签
<table>
	{{obj.as_table}}
</table>
...
posted @ 2018-10-31 09:50  Joe1991  阅读(156)  评论(0)    收藏  举报