Django之Form组件

Form组件功能

  • 对用户请求进行验证
  • 生成HTML代码

Form使用

创建一个类

要使用Django的Form组件,首先应该创建一个类,继承forms.Form类,在里面定义用户输入的规则,字段名必须与前端input框中的name属性一致

class ConfirmForm(forms.Form):
    user = fields.CharField(max_length=18, min_length=6, required=True)  # required: 表示不能为空
    pwd = fields.CharField(required=True, min_length=32)
    age = fields.IntegerField(required=True)
    email = fields.EmailField(required=True)

在视图函数中使用

在视图函数中生成刚刚创建的类的对象,将request.POST作为参数传进该类中进行验证

def index(request):
    form = ConfirmForm(request.POST)
    # 检查是否验证成功
    v = form.is_valid()
    if v:         # 验证成功
    	print(form.cleaned_data)     # 输出已经验证过的用户提交的数据
    	models.User.objects(username=form.cleaned_data['user'], pwd=form.cleaned_data['pwd'])   # 将数据存入数据库
    else:         # 验证失败
        print(form.errors)      # 输出错误信息     
    return render(request, "index.html", {'obj': form})

在HTML页面展示错误信息

Form将验证的错误信息都存在 obj.errors 字典中,在HTML中取错误信息时,只需要在errors后面点出对应的字段名就可以获取相应字段输入的异常信息了,有的时候,用户输入的信息可能会违反多条规则,比如用户的输入同时违反了不能输入数字和数据长度大于6个字符的规则,那么此时取出的错误信息是一个列表,这时显然是不需要将两条信息都展示的,只需要展示一条就好,那这时可以使用 obj.errors.字段名.0 将第一条异常信息取出来就好

<form action="/index.html" method="POST">
	<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>
    <p><input type="submit" /></p>
</form>

添加错误提示

上面已经实现了利用Form组件进行纠错的功能,但是输出的错误信息并不是由我们自己指定的,那么,能不能自定制错误信息呢?自然可以,只需要在对应字段中添加上 error_messages 字段即可,error_messages字段是一个字典,字典的每一个键值是对该字段进行限制的字段名,比如: min_length max_length required 等,值就是违反了这些限制后提示的错误信息

下面对上面创建的几个字段添加上错误信息:

class ConfirmForm(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
    		error_messages={
    		    'required': '邮箱不能为空',
    		    'invalid': '邮箱格式错误',
    		}
    )

生成HTML代码

上面虽然解决了对用户的输入进行判断并返回错误信息的问题,但是form表单提交还有一个很让人蛋疼的问题,就是不管输入的信息正确与否,只要提交了之前输入的信息就都没有了,如果输入对了还好说,但要是输入错了那就麻烦了,有可能只是需要更改一个字母就能正确却偏偏要重新再输入一遍,这无疑会让用户的体验变得非常差,那么Form组件能不能解决这个问题呢?当然是可以的,这就涉及到了Form组件的第二个功能,生成HTML代码

写一个这样的视图函数

def index(request):
    if request.method == "GET":
        obj = ConfirmForm()
        return render(request, "index.html", {"obj":: obj})
    else:
        obj = ConfirmForm(request.POST)
        if obj.is_valid():
            ....      # 这里书写业务
            return HttpResponse("ok")
        else:
            return render(request, "index.html", {"obj": obj})

在index.html中

在HTML页面中使用Form组件生成相应的输入框只需要使用obj点上字段名就可以了

<form action="/index.html" method="POST">
	<p>{{ obj.user }}{{ obj.errors.user.0 }}</p> 
	<p>{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
	<p>{{ obj.email }}{{ obj.errors.email.0 }}</p>
       <p><input type="submit" /></p> 
</form>

只要使用Form生成输入框后,就不用担心提交后用户数据丢失的问题了,即使提交后即使没有通过验证,下一次返回的obj中已经携带了上一次输入的信息,这一次再渲染时就会把上一次输入的信息也渲染到页面。

其实在第一次的时候也能为表单填写一些默认的值,只需要以字典的形式在创建对象的时候将初始值放到对象中,就像这样:

def index(request):
    obj = ConfirmForm({"user": "hh", "pwd": '123', 'email': '123456@136.com'})
    return render(request, "index.html", {"obj": obj})

这样在第一次输入的时候也能有默认值

Form字段详解

在Form组件中除了常用的 CharField() 字段之外还有很多字段,下面进行列举

------------------------- 重点字段 -------------------------

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内容后缀

ChoiceField(Field)

ChoiceField字段是生成下拉选框的字段,选框里面的内容由choices字段决定,ChoiceField字段是继承Field字段的,因此具有Field字段的全部字段,同时还具有以下新的字段

choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True,             是否必填
widget=None,               插件,默认select插件
label=None,                Label内容
initial=None,              初始值,通过choices属性里面元组前面的值来选定初始值,如:initial=1就选定了'北京'
help_text='',              帮助提示

MultipleChoiceField(ChoiceField)

上面的ChoiceField字段是进行单选的SELECT选框,而MultipleChoiceField字段是进行多选的SELECT选框,字段名与ChoiceField一致,在赋初始值的时候,如果有多个值,可以使用列表赋值,如:initial=[0, 1, ]

CharField(Field)

CharField字段继承了Field字段,因此具有Field字段的全部字段,另外还具有如下字段

max_length=None,             最大长度
min_length=None,             最小长度
strip=True                   是否移除用户输入空白

IntegerField(Field)

IntegerField字段继承了Field字段,因此具有Field字段的全部字段,另外还具有如下字段

max_value=None,              最大值
min_value=None,              最小值

DecimalField(IntegerField)

DecimalField是十进制的小数,继承IntegerField,具有IntegerField的全部字段,除此之外还具有如下字段

max_value=None,              最大值
min_value=None,              最小值
max_digits=None,             总长度
decimal_places=None,         小数位长度

DateField(BaseTemporalField)&DateTimeField(BaseTemporalField)&TimeField(BaseTemporalField)

  • DateField(BaseTemporalField)    格式:2018-07-29
  • DateTimeField(BaseTemporalField)  格式:2018-07-29 19:11
  • TimeField(BaseTemporalField)    格式:11:12

EmailField(CharField)

EmailField继承了CharField,具有和CharField相同的字段

FileField(Field)

FileField继承了Field,具有Field全部的字段,除此以外还具有如下字段

allow_empty_file=False     是否允许空文件
使用时,需要注意两点:
  • form表单中 enctype="multipart/form-data"
  •  view函数中 obj = MyForm(request.POST, request.FILES)

GenericIPAddressField

这是一个匹配IP地址的字段

  • protocol='both',       both,ipv4,ipv6支持的IP格式
  • unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用

RegexField(CharField)

该字段可以存放自定制的正则表达式,具有如下属性

regex,                      自定制正则表达式
max_length=None,            最大长度
min_length=None,            最小长度
error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}

 

利用Form组件自带的正则扩展

方式一:

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

方式二:

class MyForm(forms.Form):
    user = fields.RegexField(r'^[0-9]+$', error_messages={"invalid": '....'})

------------------------- 非重点字段 -------------------------

FloatField(IntegerField)
    ...

BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...

ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)

URLField(Field)
    ...

BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
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: int(val)   对选中的值进行一次转换
    empty_value= ''            空值的默认值

TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: int(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类型
    ...

widget内置插件

在各个字段中都有一个这样的字段叫 widget ,这个字段是一个HTML插件,可以赋予一些字段原来没有的属性

Django内置插件

 1 TextInput(Input)
 2 NumberInput(TextInput)
 3 EmailInput(TextInput)
 4 URLInput(TextInput)
 5 PasswordInput(TextInput)
 6 HiddenInput(TextInput)
 7 Textarea(Widget)
 8 DateInput(DateTimeBaseInput)
 9 DateTimeInput(DateTimeBaseInput)
10 TimeInput(DateTimeBaseInput)
11 CheckboxInput
12 Select
13 NullBooleanSelect
14 SelectMultiple
15 RadioSelect
16 CheckboxSelectMultiple
17 FileInput
18 ClearableFileInput
19 MultipleHiddenInput
20 SplitDateTimeWidget
21 SplitHiddenDateTimeWidget
22 SelectDateWidget
内置插件

常用的选择插件

radio按钮

方式一:

hobby = fields.ChoiceField(
    choices=((1, '篮球'), (2, '网球'), ),
    widget=widgets.RadioSelect,
)

方式二:

place = fields.CharField(
    initial=2,
    widget=widgets.RadioSelect(choices=((1, '北京'), (2, '天津'), )),
)

单选框

在做SELECT框的时候,除了由上面的ChoiceField字段跟MultipleChoiceField字段来做以外,还可以使用widget插件来做

方式一:

sel = fields.ChoiceField(
    choices=((1, '北京'), (2, '上海'), ),
    widget=widgets.Select,
)

方式二:

place = fields.CharField(
    initial=2,
    widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
)

多选框

place = fields.MultipleChoiceField(
    choices=((1,'上海'),(2,'北京'),),
    initial=[1,],
    widget=widgets.SelectMultiple
)

单checkbox

place = fields.CharField(
    widget=widgets.CheckboxInput()
)

多checkbox

place = fields.MultipleChoiceField(
    initial=[2, ],
    choices=((1, '上海'), (2, '北京'),),
    widget=widgets.CheckboxSelectMultiple
)

在widget中为input框指定参数

使用Form生成input框确实非常方便,但是如果有时候想要给这个input框加一点参数应该怎么办呢?比如加上id或者给上placeholder,这时需要用到widget中的attrs参数,如下

widget=widgets.InputText(arrts={
    "id": nid,
    "placeholder": "请输入",
})

Form组件之动态绑定数据

第一种实现方式

上面提到的所有字段都是类字段,随着类的创建而创建,在做多选SELECT框的时候,内容有的时候来自数据库的数据,而如果数据库的数据在某一时刻增加了一条,在没有重启服务器的情况下这条数据不会刷新到页面,这时弊端就显现出来了

那么如何去解决呢?只需要在每次刷新页面的时候访问数据库就可以了,而每次刷新都会创建对象,因此只需要将choices字段的赋值放到__init__()方法中即可

class LoginForm(forms.Form):
        peice = fields.IntegerField()
        user_id = fields.IntegerField(
        	widget=widgets.Select()
        )

        def __init__(self, *args, **kwargs):
            # 拷贝所有的静态字段,赋值给self.fields
            super(LoginForm, self).__init__(*args, **kwargs)

            self.fields['user_id'].widget.choices = models.UserInfo.objects.values_list('id', 'username')

像上面那样在__init__方法里面对choices字段进行赋值,那样每次创建对象的时候都会去数据库拿数据,这样之后,只要数据库多了数据或者少了数据,只需要刷新一下页面就可以了

第二种实现方式

除了向上面那样将choices字段放到__init__()方法中赋值以外,还可以使用ModelChoiceField字段解决该问题,但是这对Models里面的类有要求,必须要写上__str__()方法才能显示,而且有的时候如果在两个地方需要显示该类中不同的字段就没办法了,因为只有一个__str__()方法

class LoginForm(forms.Form):
    user_id = fields.ModelChoiceField(
        queryset=models.UserInfo.objects.all()
        to_field_name='id'     # 指定在value里面显示的内容,默认是id
    )

对Form进行近一步的验证

Form是通过 is_valid() 方法验证用户输入的数据的,那么这个方法在验证的时候做了些什么呢?追进去看看

由上图可以看到,is_valid()到最后本质上是通过这三个方法进行验证的,可以分别进这三个方法看看

_clean_fields()方法

在上面的方法中,主要对用户输入的信息进行了检测,首先便是对用户输入的信息进行正则检测,然后便是判断我们自己有没有定义clean_字段名()的方法,如果定义了,就执行该方法,并且使用该方法的返回值作为最终放到cleaned_data中相应字段的值,那么如果有的时候用户输入的信息必须是唯一的,即没有与数据库中同名的,那么是否可以在这里面判断呢?如果用户输入的与数据库中数据没有同名的,那就依然将这个值原封不动的返回出去,那么最终cleaned_data中与该字段对应的还是用户原来输入的值,如果用户输入的值在数据库中已经存在,那么是否可以抛出ValiddationError的异常让上面的方法捕捉,然后将异常信息封装到errors中显示到浏览器页面提示用户输入的数据已经存在呢?当然是可以的。因此我们需要自定义一个clean_字段名()的方法,在这个方法里面进行判断,如果满足了条件就将用户的值原封不动返回,否则抛出异常,将异常信息返回给用户

上面的方法对username进行了判断,如果在数据库中已经存在了该用户名,那么将异常信息返回给用户

_clean_form()方法

上面的方法是对单个字段进行处理,那么如果要对多个字段进行处理应该怎么办呢?比如判断两个字段联合唯一,那样再使用_clean_fields()方法就不妥了,这时就要用到_clean_form()方法。首先进_clean_form()方法看看

很显然,在_clean_form()方法里面就干了一件事,执行clean()方法,并把该方法的返回值赋值给 self.cleaned_data ,那么,这个clean()方法究竟是一个什么样的方法呢?追进去看看

很明显,在clean()方法里面就干了一件事,把 self.cleaned_data 返回,结合上面的代码,我们可以知道,整个_clean_form()方法就干了一件事情,就是把self.cleaned_data赋值给self.cleaned_data,这么看好像有点闲的蛋疼,当然,如果我们什么都不做,这段代码确实是闲的蛋疼,但是如果在我们自己定义的Form类里面重写这个clean()方法,那作用就大不一样了,首先明确目的,我们想要对两个字段进行联合唯一的判断,那么是否可以在clean()里面进行判断,如果不满足条件将抛出ValidationError异常在_clean_form()方法里面捕捉,最后将错误信息返回展示给用户

在重写了上面的clean()后就可以进行两个字段的联合唯一的判断了,当然,也可以进行其他的判断,总之在clean()方法中可以打破只能对单个字段进行判断的限制,实现对多个字段的联合判断。

最后值得一提的是,在上面的clean()方法中有一段话,里面有一个重要的信息,是在这个方法中产生的任何 ValidationError 异常都会被捕捉到并且将异常信息放到errors中,取的时候可以通过字段名 __all__ 取出,但是在HTML页面中如果想要展示错误的信息,不能使用__all__,因为__all__中含有特殊字符,因此应该使用 {{ obj.non_field_errors }} 取出错误信息

_post_clean()方法

其实通过上面的两个方法已经能够完成我们所有的需求了,这个_post_clean()方法跟clean()方法的作用有点重叠,只是在_post_clean()方法中什么都没有做,需要我们自己捕捉异常以及给errors赋值,除此之外重写clean()方法跟重写_post_clean()方法没有什么区别

至此,Form组件全部介绍完毕

参考文章:Django之Form组件 

posted @ 2018-07-29 22:49  Jin同学  阅读(165)  评论(0)    收藏  举报