Django之Form组件

  • 一、Django的Form组件主要功能:

  • 生成HTML标签

  • HTML Form提交时保留上次提交数据

  • 初始化页面显示内容

  • 验证用户数据(显示错误信息)

  • 二、Form组件的基本使用方法和流程:

a、创建一个类

b、类中创建字段(包含正则表达式)

c、GET

  obj = Form()

  obj.user -->自动生成HTML标签

d、POST

  obj = Form(request.POST)

  if obj.is_valid():

    obj.cleaned_data

  else:

    obj.errors

    return ...obj

注:

(1)当以GET请求时,返回实例化的Form对象obj,对应前端展示HTML标签

(2)当以POST请求时,实例化对象可以获取POST请求中传入的值,例如:request.POST,request.FILES 等

(3)obj.is_valid() 表示判断是否全部验证成功

  如果成功,POST请求传入的值保存在 obj.cleaned_data 里

  如果不成功,则可以通过 obj.errors 查看验证失败原因

(4)可以通过在 Form 类每个字段的设置中使用 error_messages 参数进行自定制验证失败信息的展示文字

  其中 invalid 表示与字段格式有关的错误展示

  • 1、创建Form类(a、b)

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

class F1Form(forms.Form):
    user = fields.CharField(
        max_length=18,
        min_length=6,
        required=True,
        error_messages={
            'required':'用户名不能为空',
            'max_length':'输入内容太长',
            'min_length':'输入内容太短',
        }
    )

    pwd = fields.CharField(required=True, min_length=32)

    age = fields.IntegerField(
        required=True,
        error_messages={
            'required': '年龄不能为空',
            'invalid':'必须为数字',
        }
    )

    email = fields.EmailField(
        required=True,
        min_length=8,
        error_messages={
            'required': '邮箱不能为空',
            'invalid': '邮箱格式错误',
        }
    )

此类通常创建在相关应用的文件夹中的某个自建py文件下,也可以写在views.py文件中:

  • 2、Views函数处理(c、d)

from django.shortcuts import render, HttpResponse, redirect
from app01.form import F1Form    # 从自建的py文件中导入

def f1(request):
    if request.method == 'GET':
        obj = F1Form()
        return render(request, 'f1.html', {'obj':obj})
    else:
        # u = request.POST.get('user')
        # p = request.POST.get('pwd')
        # a = request.POST.get('age')
        # e = request.POST.get('email')
        # print(u,p,a,e)

        obj = F1Form(request.POST)

        if obj.is_valid():
            print('验证成功', obj.cleaned_data)
            return redirect('http://www.xcar.com.cn')
        else:
            print('验证失败', obj.errors)

        return render(request, 'f1.html', {'obj':obj})
  • 3、生成HTML(c、d)

通过 obj 对象调用 Form 类中的字段名来在前端生成标签和展示标签里的内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    <form id='fm' action="/f1.html" method="post" novalidate>
        {% csrf_token %}
        <p>{{ obj.user.label }}{{ obj.user }}{{ obj.errors.user.0 }}</p>
        <p>{{ obj.pwd.label }}{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
        <p>{{ obj.age.label }}{{ obj.age }}{{ obj.errors.age.0 }}</p>
        <p>{{ obj.email.label }}{{ obj.email }}{{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交">
        <input type="button" value="Ajax提交" onclick="submitAjaxForm();">
    </form>

    <script src="/static/jquery-3.3.1.js"></script>
    <script>
        function submitAjaxForm(){
            $.ajax({
                url:'/f1.html',
                type:'POST',
                data:$('#fm').serialize(),
                success:function (arg) {
                    console.log(arg);
                }
            })
        }
    </script>

</body>
</html>

还可以通过 as 方法自动生成标签,但是样式不好调整

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   <!--生成p标签-->
    {{ obj.as_p }}

    <!--生成li标签-->
    <ul>
        {{ obj.as_ul }}
    </ul>

    <!--生成tr标签-->
    <table>
        {{ obj.as_table }}
    </table>

</body>
</html>
  • 三、Form类

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

  • 1、Django 的 Form 类内置字段设置内容:

Field 的通用属性:

    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示),在as用法生成标签时生效
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀,在as用法生成标签时生效

例如:

不同字段各自特有属性:

CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
  
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度

FloatField(IntegerField)
    ...
 
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')
View Code
  • 2、Django 的 Form 字段内置插件:

TextInput(Input)
Select
SelectMultiple
RadioSelect
CheckboxInput
CheckboxSelectMultiple
——————————————————————
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
NullBooleanSelect
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
View Code
  • 3、常用单选多选插件

  • 单选下拉框 Select

方式一:使用 ChoiceField、TypeChoiceField

# 1
city = fields.ChoiceField(
    choices=[(1,'上海'), (2, '北京'), (3, '沙河'),],
    initial=2,    # select标签设置默认值方式1
)

# 2
city2 = fields.TypedChoiceField(
    coerce = lambda x: int(x),    # 对选中的值进行一次转换
    choices=[(1, '上海'), (2, '北京'), (3, '沙河'),],
    initial=2,  # select标签设置默认值方式1
)

方式二:使用 CharField、IntegerField 配合 widget=widgets.Select

# 1
sel = fields.CharField(
    widget=widgets.Select(choices=[(1, '足球'), (2, '篮球'), (3, '网球'),])
)

# 2
sel = fields.IntegerField(
    widget=widgets.Select(choices=[(1, '足球'), (2, '篮球'), (3, '网球'),])
)
  • 多选下拉框 Select:值为列表

使用 MultipleChoiceField,可以通过 widget=widgets.SelectMultiple(attrs={'class':'c1'}) 方式给标签添加属性

# 1    
hobby = fields.MultipleChoiceField(
    choices=[(1, '足球'), (2, '篮球'), (3, '网球'),],
    initial=[1,2],    # 多选默认值可以传入一个列表
)

# 2    
mul_sel = fields.MultipleChoiceField(
    choices=[(1, '足球'), (2, '篮球'), (3, '网球'),],
    widget=widgets.SelectMultiple(attrs={'class':'c1'})
)
  • 单选 Checkbox 框:

使用 CharField、ChoiceField 配合 widget=widgets.CheckboxInput

che_box = fields.CharField(
    widget=widgets.CheckboxInput
)
  • 多选 Checkbox 框:值为列表

使用 MultipleChoiceField 配合 widget=widgets.CheckboxSelectMultiple

mul_chebox = fields.MultipleChoiceField(
    initial=[2, ],
    choices=[(1, '上海'), (2, '北京'),],
    widget=widgets.CheckboxSelectMultiple
)
  • 单选 Radio 框:

方式一:使用 ChoiceField 配合 widget=widgets.RadioSelect

rad = fields.ChoiceField(
    choices=[(1, '上海'), (2, '北京'), (3, '沙河'),],
    initial=2,
    widget=widgets.RadioSelect
)

方式二:使用 CharField 配合 widget=widgets.RadioSelect(choices=[])

rad = fields.CharField(
     initial=2,
     widget=widgets.RadioSelect(choices=[(1, '上海'), (2, '北京'), (3, '沙河'),])
)
  • 为 Select、Radio、Checkbox 框设置默认值

方式一:在字段中通过 initial 属性传入默认值

— — — — — — — — — — — — — — — — —  — — — — — — — — — — —  —— — — — — — — —  — — — — — — — —

方式二:在实例化 Form 对象的时候传入 “字段名和默认值” 的字典

  • 4、Form 组件动态绑定数据,根据数据库操作实时更新前端数据

以下写法会导致数据无法实时更新:

class LoveForm(forms.Form):
    price = fields.IntegerField()
    user_id = fields.IntegerField(
        widget=widgets.Select(choices=models.UserInfo.objects.values_list('id', 'username'))
    )

在使用选择标签时,需要注意 choices 的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到实时更新的目的

方式一:通过自定义构造方法实现,推荐使用

class LoveForm(forms.Form):
    price = fields.IntegerField()
    user_id = fields.IntegerField(
        widget=widgets.Select
    )

    # 实时更新方式1:推荐使用
    def __init__(self, *args, **kwargs):
        # super的操作是拷贝所有的静态字段,复制给self.fields,所以super一定要在self.fields上面
        super(LoveForm, self).__init__(*args, **kwargs)
        # self.fields代表所有的字段
        self.fields['user_id'].widget.choices = models.UserInfo.objects.values_list('id', 'username')

方式二:使用 ModelChoiceField、ModelMultipleChoiceField 配合 修改models中相关表的__str__方法 设置展示的内容来实现

class LoveForm(forms.Form):
    price = fields.IntegerField()

    # 实时更新方式2:需要依赖修改models中表的__str__方法设置展示的内容,否则实时更新展示的内容是object对象
    user_id = ModelChoiceField(
        queryset=models.UserInfo.objects.all(),
        to_field_name='id',    # HTML中标签中value的值对应的字段
    )
  • 四、Form组件扩展

  • 1、自定义验证规则—利用Form组件自带的正则进行验证

方式一:使用 validators 属性结合 RegexValidator 类,可以自定义多个验证规则

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开头')],
    )

# 错误信息依旧保存在 error_messages 的 invalid 里

也可以自定制不同正则所对应的错误提示:利用 RegexValidator 类中的 code 属性设置

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]+$', '请输入数字', code='invalidator1'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头', code='xx')],
    )
    
    error_messages = {'invalidator1':'请输入数字', 
                      'xx':'数字必须以159开头'}

方式二:使用 RegexField 字段,只能传入一个验证规则

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

 
class MyForm(Form):
    user = fields.RegexField(
            regex = r'^[0-9]+$',
            error_messages = {'invalid':'请输入数字'}    
    )    
  • 2、基于源码的自定制验证规则

  • 单字段验证

例如判断数据库中是否有该用户名,采用如下方式自定制方法:

1、Form里自定义方法:clean_字段名

2、必须有返回值:self.cleaned_data['username']

3、如果出错:raise ValidationError('用户名已存在')

  • 整体错误验证

对所有字段进行联合唯一验证时,采用自定制 clean 方法对整体进行验证,错误信息保存在 "__all__" 键中

  • 其他自定制验证

可以通过预留的钩子函数自定制其他的验证方法:_post_clean

from django.core.exceptions import NON_FIELD_ERRORS, ValidationError

class AjaxForm(forms.Form):
    username = fields.CharField()
    user_id = fields.IntegerField(
        widget=widgets.Select(choices=[(0,'alex'), (1,'liuhc'), (2,'yangj')])
    )

    # Form里自定义方法:clean_字段名
    # 必须有返回值:self.cleaned_data['username']
    # 如果出错:raise ValidationError('用户名已存在')
    def clean_username(self):
        v = self.cleaned_data['username']
        if models.UserInfo.objects.filter(username=v).count():
            # 整体错了
            # 自己详细错误信息
            raise ValidationError('用户名已存在')
        return v

    def clean_user_id(self):
        return self.cleaned_data['user_id']

    # 整体错误信息验证
    def clean(self):
        value_dict = self.cleaned_data
        v1 = value_dict.get('username')
        v2 = value_dict.get('user_id')
        if v1 == 'root' and v2 == 1:    # 假设需要 username='root' 且 user_id 等于1时,整体验证失败
            raise ValidationError('整体错误信息')
        return self.cleaned_data

源码查找流程如下:

 

posted @ 2020-11-03 18:33  江畔何人初见月/  阅读(125)  评论(0)    收藏  举报