Django forms组件

Django forms

   Django中的forms组件能够为前端提交的数据进行验证,同时也能生成HTML代码,在全栈开发中非常的方便。

   但是如果你的项目是前后端分离的,那么就没必要使用forms的渲染HTML代码功能了。

   学习forms组件将分为三步:

   1.如何对数据进行验证

   2.如何渲染HTML代码

   3.如何将错误信息反馈给页面

数据验证

   forms提供的数据验证非常强大,你可以自定义你的数据验证格式,也可以使用内置的很多校验方法。

测试环境

   还是先准备好测试环境,在APP下的tests中进行如下设置:

from django.test import TestCase

# Create your tests here.
import os
import sys
sys.path.append(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Djangoforms.settings")
    import django
    django.setup()

验证类

   forms组件应该放在一个单独的文件中,我在APP下命建立了一个dataforms.py文件,并在里面书写了forms验证类。

from django import forms

class F1(forms.Form):
    username = forms.CharField(max_length=16, min_length=6, label="用户名")
    password = forms.CharField(max_length=16, min_length=6, label="密码")
    email = forms.EmailField(label="邮箱") # label是渲染HTML时使用的名字

  

   大概意思非常明了:

   1.username为字符类型,最长为16(字符),最短为6(字符)。

   2.password为字符类型,最长为16(字符),最短为6(字符)。

   3.email必须符合email规范

校验数据

   校验数据时,使用dict进行传入并且实例化F1类。

   使用is_valid()查看是否全部验证通过。

   使用cleand_data获取验证成功的字段。

   使用errors获取验证失败的字段与错误信息。(错误信息是HTML形式,这个是往页面上渲染用的)

   1.实例化F1类时,传入参数必须与字段一一对应。可以多传但是绝对不能少传,除非字段限制设置为可以为空。

from dataforms import F1

data = {
    "username": "云崖先生",
    "password": "12345678",
    "email": "[email protected]",
}
verify_data = F1(data)  # 注意!这里是实例化出了F1的对象

if verify_data.is_valid():
    print("全部验证通过...")
    print(verify_data.cleaned_data) # 获取验证通过的字段与数据

else:
    print("某个字段数据没能通过验证...")
    print(verify_data.errors)

# 
  • username
    • Ensure this value has at least 6 characters (it has 4).

   那么校验数据就说到这里。

渲染页面

   forms组件可以自动生成HTML代码,并且做了make_safe()处理,因此可以直接在页面上进行显示。

视图函数

   还是使用F1进行验证,书写一个视图函数。这里面有1个细节点。

from django.shortcuts import render
from django.shortcuts import redirect
from app01 import dataforms

def test(request):
    verify_obj = dataforms.F1()  # 注意!这里是传了一个空对象

    if request.method == "POST":
        verify_obj = dataforms.F1(request.POST) # request.POST 本身就是一个字典  注意!这里又实例化出了一个对象
        if verify_obj.is_valid():
            pass # 写入数据库的操作,然后执行redirect跳转
            return redirect("http://www.google.com")
        else:
            print(verify_obj.errors)

    return render(request,"test.html",locals()) # 无论如何都会返回这个页面

自动生成

   自动生成的方式有好几种,推荐使用手动的方式。

   其实每一个forms里面的字段都是一个实例化对象,而填入的参数则是其中的属性。我们可以通过.给他拿出来。

   以下是自动生成的方式。


    {{verify_obj.as_ul}} 
    {{verify_obj.as_p}}  
    {{verify_obj.as_table}} 

  

   推荐使用手动生成的方式。


    
    
    
    
{% for field in verify_obj %}

{{field.label}} : {{field}} {{field.errors.0}}

{% endfor %}

错误信息

   我们可以在页面上对错误信息进行渲染。

   经过上面视图函数的两次对F1的实例化,使我们可以做到在刷新页面的情况下保留原本输入的数据。

渲染信息

   由于某个字段的验证条件可能有多种,所以错误信息默认是以

    套着
  • 出现的。

       所以我们使用{{field.errors.0}}拿出第一条信息即可。

    
        
        
        
    {% for field in verify_obj %}

    {{field.label}} : {{field}} {{field.errors.0}}

    {% endfor %}

       Django forms组件_第1张图片

    自定错误

       可以看到,默认的它的错误信息是英文的。

       这个时候可以自定制错误,针对不同的规则定制不同的错误。

    class F1(forms.Form):
        username = forms.CharField(max_length=16, min_length=6, label="用户名",
            error_messages={
                "max_length":"用户名不能超过16位",
                "min_length":"用户名不能小于6位",
                "required":"不能为空",
                "invalid":"格式出错", # 代表全部错误
            }
        )
        password = forms.CharField(max_length=16, min_length=6, label="密码",
            error_messages={
                "max_length":"密码不能超过16位",
                "min_length":"密码不能小于6位",
                "required":"不能为空",
            }
        )
        email = forms.EmailField(label="邮箱",
            error_messages={
                "required":"不能为空",
                "invalid":"必须是邮箱格式",
            }
        )
    
    

    样式插件

       默认的字段样式都不太好看,我们可以利用widget来使用一些自带的插件。

    内置插件

       使用内置插件前要先进行导入:

    from django.forms import widgets
    
    插件名称 对应input
    TextInput input type="text"
    PasswordInput input type="password"
    HiddenInput input type="hidden"
    NumberInput input type="number"
    EmailInput input type="email"
    URLInput input type="url"
    Textarea textarea
    DateInput input type="data"
    DateTimeInput input type="datetime"
    TimeInput 只获取时分秒
    CheckboxInput input type="checkbox"
    CheckboxSelectMultiple 多选按钮
    Select select框
    NullBooleanSelect select框 三个选项 0 1 null
    SelectMultiple 可多选的select框
    RadioSelect input type="radion"
    FileInput 可以查看当前目录下的所有文件。作用不大
    ClearableFileInput 同上
    MultipleHiddenInput 作用不大
    SplitDateTimeWidget 作用不大
    SplitHiddenDateTimeWidget 作用不大
    SelectDateWidget 作用不大

    常用插件

       以下例举一些常用的插件。

    # 单radio,值为字符串
    # user = forms.CharField(
    #     initial=2,  # initial 默认选择的值
    #     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
    # )
     
    # 单radio,值为字符串
    # user = forms.ChoiceField(
    #     choices=((1, '上海'), (2, '北京'),),
    #     initial=2,
    #     widget=widgets.RadioSelect
    # )
     
    # 单select,值为字符串
    # user = forms.CharField(
    #     initial=2,
    #     widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
    # )
     
    # 单select,值为字符串
    # user = forms.ChoiceField(
    #     choices=((1, '上海'), (2, '北京'),),
    #     initial=2,
    #     widget=widgets.Select
    # )
     
    # 多选select,值为列表
    # user = forms.MultipleChoiceField(
    #     choices=((1,'上海'),(2,'北京'),),
    #     initial=[1,],
    #     widget=widgets.SelectMultiple
    # )
     
     
    # 单checkbox
    # user = forms.CharField(
    #     widget=widgets.CheckboxInput()
    # )
     
     
    # 多选checkbox,值为列表
    # user = forms.MultipleChoiceField(
    #     initial=[2, ],
    #     choices=((1, '上海'), (2, '北京'),),
    #     widget=widgets.CheckboxSelectMultiple
    # )
    

    插件样式

       如果使用了widget插件,我们可以利用attrs跟上一个字典,来定制插件的样式。

       注意:不同的类之间,用空格进行分割。

    from django import forms
    from django.forms import widgets
    
    class F1(forms.Form):
        username = forms.CharField(max_length=16, min_length=6, label="用户名",
            error_messages={
                "max_length":"用户名不能超过16位",
                "min_length":"用户名不能小于6位",
                "required":"不能为空",
                "invalid":"格式出错", # 代表全部错误
            },
            widget=widgets.TextInput(attrs={"class":"form-control","style":"color:red;font-size:16px;"}),
        )
        password = forms.CharField(max_length=16, min_length=6, label="密码",
            error_messages={
                "max_length":"密码不能超过16位",
                "min_length":"密码不能小于6位",
                "required":"不能为空",
            },
            widget=widgets.PasswordInput(attrs={"class":"form-control"}),
        )
        email = forms.EmailField(label="邮箱",
            error_messages={
                "required":"不能为空",
                "invalid":"必须是邮箱格式",
            },
            widget=widgets.EmailInput(attrs={"class":"form-control"})
        )
    

    内置字段

       介绍完插件后,还需要介绍一下内置字段。

       内置字段的作用是定义验证规则,而插件则是对生成的标签样式做美化功能的。

       注意不要被相同的字段名搞混淆了。

    通用参数

    参数名 描述
    required 是否允许为空,默认False
    label 生成Label标签或显示内容
    initial 初始值,常用于SELECT或RADION以及CHECKBOX中
    help_text 帮助信息(在标签旁边显示),没啥用
    error_messages 自定义错误信息
    validators 这是一个列表,可以存放多个正则用于验证
    localize 是否支持本地化,对于时间类型的可以选择为True
    disabled 是否可以编辑,默认是True
    label_suffix Lable的后缀,没什么用,主要是自动生成HTML代码是才能用得上
    show_hidden_initial 这个比较有用,一个隐藏的input框,可用于存放用户的id信息
    widget HTML插件

    字段大全

       注意区分,这里的字段是forms.字段名,不是插件。

    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) 			  必须是URL类型
        ...
     
     
    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类型
    

    钩子函数

    局部钩子

       在forms验证类中,可以定义局部钩子。

       可以作为二次验证。

       局部钩子的名字为clean_字段名

       注意,当局部钩子函数处理完数据后,应该将数据return回去。

    # 针对username字段的提交数据进行二次验证
    def clean_username(self):
    	username = self.cleaned_data.get("username")
    	prohibit = {"蛤蟆","维尼熊","跳跳虎"}
    	for name in prohibit:
    		if name in username:
    			self.add_error("username","含有违禁词汇!")  # 新增一个页面错误信息
    	return username
    

       Django forms组件_第2张图片

    全局钩子

       全局钩子可以拿到所有字段提交的数据信息,因此非常适合用来做数据库验证,验证提交的数据信息是否存在于数据库中。

       当然你如果是一个注册页面,可以用来验证两次密码是否输入一致。

       全局钩子的名称为clean

       数据库结构:

    from django.db import models
    
    class User(models.Model):
        username = models.CharField(max_length=16)
        password = models.CharField(max_length=16)
        email = models.EmailField(max_length=32)
    
        def __str__(self):
            return self.username
    

       全局钩子函数:

    def clean(self):
    	res = models.User.objects.filter(**self.cleaned_data).exists()
    	if not res:
    		self.ALLERROR = "数据库中不存在该数据"  # 自定义一个实例属性
    		raise ValidationError(self.ALLERROR)  # 先导入该异常,再添加上自定义的异常信息
    	return self.cleaned_data # 无论如何都进行返回
    

    抛出异常

       抛出异常分为字段异常和全局异常。

       如我们再局部钩子中发现某个字段数据有一些问题,则使用字段异常即可。

    self.add_error("username","含有违禁词汇!")  # 新增一个页面错误信息
    

       同时前端获取的时候可以直接获取到,因为0就是这个异常。

     

    {{verify_obj.username.label}} : {{verify_obj.username}} {{verify_obj.username.errors.0}}

       如果抛出的是全局异常,那么页面上就需要另外的显示手段了,我这里直接把它做成一个实例属性就可以进行取出。

    from django.forms import ValidationError  # 先导入全局异常
    
    self.ALLERROR = "数据库中不存在该数据"  # 自定义一个实例属性
    raise ValidationError(self.ALLERROR)  # 触发异常
    

       前端获取的话就可以直接用这个对象常量进行获取。

    {{verify_obj.ALLERROR}}
    

    全部代码

       urls.py

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^test/', views.test,name="test"),
    ]
    

       app01/views.py

    from django.shortcuts import render
    from django.shortcuts import redirect
    from app01 import dataforms
    
    def test(request):
        verify_obj = dataforms.F1()  # 注意!这里是传了一个空对象
    
        if request.method == "POST":
            verify_obj = dataforms.F1(request.POST) # request.POST 本身就是一个字典  注意!这里又实例化出了一个对象
            if verify_obj.is_valid():
                pass # 写入数据库的操作,然后执行redirect跳转
                return redirect("http://www.google.com")
            else:
                print(verify_obj.errors)
    
        return render(request,"test.html",locals())
    

       app01/dataforms.py

    from django import forms
    from django.forms import widgets
    from django.forms import ValidationError
    from app01 import models
    
    class F1(forms.Form):
        username = forms.CharField(max_length=16, min_length=6, label="用户名",
            error_messages={
                "max_length":"用户名不能超过16位",
                "min_length":"用户名不能小于6位",
                "required":"不能为空",
                "invalid":"格式出错", # 代表全部错误
            },
            widget=widgets.TextInput(attrs={"class":"form-control","style":"color:red;font-size:16px;"}),
        )
        password = forms.CharField(max_length=16, min_length=6, label="密码",
            error_messages={
                "max_length":"密码不能超过16位",
                "min_length":"密码不能小于6位",
                "required":"不能为空",
            },
            widget=widgets.PasswordInput(attrs={"class":"form-control"}),
        )
        email = forms.EmailField(label="邮箱",
            error_messages={
                "required":"不能为空",
                "invalid":"必须是邮箱格式",
            },
            widget=widgets.EmailInput(attrs={"class":"form-control"})
        )
    
        def clean_username(self):
            username = self.cleaned_data.get("username")
            prohibit = {"蛤蟆","维尼熊","跳跳虎"}
            for name in prohibit:
                if name in username:
                    self.add_error("username","含有违禁词汇!")  # 新增一个页面错误信息
            return username
        
        def clean(self):
            res = models.User.objects.filter(**self.cleaned_data).exists()
            if not res:
                self.ALLERROR = "数据库中不存在该数据"  # 自定义一个实例属性
                raise ValidationError(self.ALLERROR)  # 先导入该异常
            return self.cleaned_data # 无论如何都进行返回
    

       app01/models.py

    from django.db import models
    
    # Create your models here.
    class User(models.Model):
        username = models.CharField(max_length=16)
        password = models.CharField(max_length=16)
        email = models.EmailField(max_length=32)
    
        def __str__(self):
            return self.username
    

       templates/test.html

    
    
    
        
        
        Document
        
        
        
    
    
        
        
        
    {% for field in verify_obj %}

    {{field.label}} : {{field}} {{field.errors.0}}

    {% endfor %} {{verify_obj.ALLERROR}}

    正则验证

       forms组件允许自定义正则验证。

       如果你的业务需求很刁钻,则可尝试使用正则验证。

       方式一

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

    实时更新

       如果我们页面上展示的一个select框中的数据,是从数据库实时获取的,那么是没办法做到数据新插入一条消息页面也会立即显示出来这样的效果的。

       定义字段为类属性,只会加载一次。

       每次实例化对象用到的字段数据,都是用的类属性,故不能实时与数据库更新。

       我们把它做成实例属性即可。

    from django import forms
    from django.forms import widgets
    from app01 import models
    
    class F1(forms.Form):
    
        user_id = forms.CharField(
            label="选择用户",
        )
    
        def __init__(self,*args,**kwargs):
            super(F1,self).__init__(*args,**kwargs)
            self.fields["user_id"].widget = widgets.Select(
                choices=(models.User.objects.values_list("id","username"))
                )
            # 做成实例,每次进入视图函数时都会触发实例对象,故此能动态获取数据库中的数据
            # values_list [(1,"user1"),(2,"user2")]
    

你可能感兴趣的:(Django forms组件)