Python Web框架篇:Django Form组件
Form简介
在HTTP中,表单(form标签),是用来提交数据的,其action属性说明了其传输数据的方法:如何传、如何接收。
访问网站时,表单可以实现客户端与服务器之间的通信。例如查询,就用到了表单(其属性中,action=get)。
再比如说注册与登陆,也是要用到表单的。但这里由于涉及到隐私问题,需要保证数据传输的安全性,因此其传输方法就应当使用post而非get。
总之,对客户端来说,表单就是用来向服务器提交数据的;
而对服务器来说,表单就是你提供给客户端的发送信息的渠道,你需要对用户发送来的信息进行处理和响应,以达到页面的交互。
Django Form的功能
- 生成HTML标签
- 验证用户数据(显示错误信息)
- HTML Form提交保留上次提交数据
- 初始化页面显示内容
Django Form验证流程
用于验证用户请求数据合法性的一个组件
如果没有Form就得繁琐地用正则表达式了
a. 用户提交数据的验证
1、创建模版 class LoginForm(forms.Form):...
2、将请求交给模版,创建一个对象 obj = LoginForm(request.POST)
3、进行验证 obj.is_valid()
4、获取正确信息 obj.clean()
5、获取错误信息 obj.errors
b. 错误信息提示
Form提交,刷新页面的特性,模版对象内部值丰富,再显示时,值和错误信息都有
c. 保留上一次提交的数据
1、自动生成html标签
2、保留上一次提交的数据
Form的创建
1 from django.shortcuts import render, redirect 2 from django import forms 3 #这里为了简单,把form类写到视图函数 4 # 模版 5 class LoginForm(forms.Form): 6 # 模版中的元素 7 user = forms.CharField(min_length=6,error_messages={"required": '用户名不能为空','min_length': '用户名长度不能小6'}) 8 email = forms.EmailField(error_messages={"required": '邮箱不能为空','invalid': '邮箱格式错误'})
9 def login(request): 10 if request.method == "GET": 11 # 数据库中获取 12 obj = LoginForm() 13 return render(request,'login.html',{'oo': obj}) 14 elif request.method == "POST": 15 """ 16 obj = LoginForm(request.POST) 17 # 验证 18 status = obj.is_valid() 19 print(status) 20 21 value_dict = obj.clean() 验证成功的值,输入格式正确的拿到了 22 print(value_dict) 23 24 # error_obj = obj.errors 25 error_obj = obj.errors.as_json() 26 print(error_obj) code定义错误信息 27 """ 28 obj = LoginForm(request.POST) 29 if obj.is_valid(): 30 value_dict = obj.clean() 31 print(value_dict) 32 # create(**value_dict) 33 else: 34 # 封装了所有的错误信息 35 # error_obj=obj.errors 36 # print(obj.errors['email'][0],type(error_obj['email']) 37 # print(obj.errors['email'][0],type(error_obj['email'][0]) 38 # print(obj.errors["user"][0]) 39 # print(type(error_obj)) 40 from django.forms.utils import ErrorDict 41 pass 42 return render(request, 'login.html',{'oo': obj})
当然你也可以在app里创建form.py文件,在里面创建类:
下文将使用这个文件。
from django import forms as DForms from django.forms import fields from django.forms import widgets from django.core.validators import RegexValidator class DetailForm(DForms.Form): user1 = fields.CharField() #默认input框,可以改成select,radio等 user2 = fields.CharField(widget=widgets.TextInput) user4 = fields.IntegerField() # 字符串 user3 = fields.ChoiceField(choices=[(1, 'SH'), (2, 'BJ'), ]) user5 = fields.CharField( widget=widgets.Select(choices=[(1, 'SH'), (2, 'BJ'), ]) ) user6 = fields.IntegerField( widget=widgets.Select(choices=[(1, 'SH'), (2, 'BJ'), ]) ) user7 = fields.IntegerField( widget=widgets.RadioSelect(choices=[(1, 'SH'), (2, 'BJ'), ]) ) #{'user1': '11', 'user2': '22', 'user4': 33, 'user3': '1', 'user5': '2', 'user6': 1, 'user7': 2}
视图函数:
from django.shortcuts import render, redirect from app01 import forms def detail(request): obj = forms.DetailForm(request.POST) obj.is_valid() print(obj.clean()) return render(request,'detail.html', {'obj': obj})
生成HTML:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/detail.html" method="POST"> {{ obj.user1 }} {{ obj.user2 }} {{ obj.user3 }} {{ obj.user4 }} {{ obj.user5 }} {{ obj.user6 }} {{ obj.user7 }} <input type="submit" value="提交"/> </form> </body> </html>
常用HTML标签:
1 <form method="POST" enctype="multipart/form-data"> 2 {% csrf_token %} 3 4 {{ form.xxoo.label }} 5 {{ form.xxoo.id_for_label }} 6 {{ form.xxoo.label_tag }} 7 {{ form.xxoo.errors }} 8 <p>{{ form.user }} {{ form.user.errors }}</p> 9 <input type="submit" /> 10 </form>
运行后后台返回结果:
#{'user1': '11', 'user2': '22', 'user4': 33, 'user3': '1', 'user5': '2', 'user6': 1, 'user7': 2}
下面将讲解为什么是这个结果
Django的Form的实现步骤:
a. 创建一个验证用户请求的模板包含:
类:模版,到底验证几个
字段:用于验证用户某个字段
插件:user = forms.CharField(..,widget=Input框)
Django内置字段如下:
1,字段核心参数
每个字段类的构造函数至少接受这些参数。有些字段类接受额外的、字段特有的参数,但以下参数应该总是能接受:
Field.required
默认情况下,每个字段 类都假设必需有值,所以如果你传递一个空的值 —— 不管是None 还是空字符串(“”) —— clean() 将引发一个ValidationError 异常。
Field.label
正如在前面“输出表单为HTML”中解释的,字段默认label 是通过将字段名中所有的下划线转换成空格并大写第一个字母生成的。如果默认的标签不合适,可以指定label。
Field.label_suffix label_suffix
参数让你基于每个字段覆盖表单的label_suffix
。
Field.initial
initial 参数让你指定渲染未绑定的表单中的字段时使用的初始值。
Field.widget
widget 参数让你指定渲染表单时使用的Widget 类。更多信息参见Widgets。
Field.help_text help_text
参数让你指定字段的描述文本。如果提供help_text
,在通过表单的便捷方法(例如,as_ul())渲染字段时,它将紧接着字段显示。
Field.error_messages
error_messages 参数让你覆盖字段引发的异常中的默认信息。传递的是一个字典,其键为你想覆盖的错误信息。
Field.validators
validators 参数让你可以为字段提供一个验证函数的列表。
Field.localize
localize 参数启用表单数据的本地化,包括输入和输出。
Field.has_changed()
has_changed() 方法用于决定字段的值是否从初始值发生了改变。返回True 或False。
2,内建字段
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 是否移除用户输入空白
- 默认的Widget:TextInput
- 空值:’ ‘(一个空字符串)
- 规范化为:一个Unicode 对象。
- 如果提供,验证max_length 或min_length。 否则,所有的输入都是合法的。
- 错误信息的键:required, max_length, min_length
可选参数:max_length
, min_length
IntegerField(Field) max_value=None, 最大值 min_value=None, 最小值 FloatField(IntegerField)
- 默认的Widget:当 Field.localize 是False 时为NumberInput,否则为TextInput。
- 空值:None
- 规范化为:一个Float 对象。
- 验证给出的值是一个浮点数。和Python 的float() 函数一样,允许前导和尾随的空白符。
- 错误信息的键:required, invalid, max_value, min_value
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
- 默认的Widget:TextInput
- 空值:None
- 规范化为:一个Python timedelta。
- 验证给出的值是一个字符串,而可以给转换为timedelta。
- 错误信息的键:required, invalid.
RegexField(CharField) regex, 自定制正则表达式 max_length=None, 最大长度 min_length=None, 最小长度 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} EmailField(CharField)
- 默认的Widget:EmailInput
- 空值:”(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给出的值是一个合法的邮件地址,使用一个适度复杂的正则表达式。
- 错误信息的键:required, invalid
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)
- 默认的Widget:URLInput
- 空值:”(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定值是有效的URL。
- 错误信息的键:required, invalid
BooleanField(Field)
- 默认的Widget:CheckboxInput
- 空值:False
- 规范化为:Python 的True 或 False。
- 如果字段带有required=True,验证值是否为True(例如复选框被勾上)。
- 错误信息的键:required
NullBooleanField(BooleanField)
- 默认的Widget:NullBooleanSelect
- 空值:None
- 规范化为:一个Python True, False 或None 值。
- 不验证任何内容(即,它从不引发ValidationError)。
ChoiceField(Field) ... choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) required=True, 是否必填 widget=None, 插件,默认select插件 label=None, Label内容 initial=None, 初始值 help_text='', 帮助提示
- 默认的Widget:Select
- 空值:”(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定的值在选项列表中存在。
- 错误信息的键:required, invalid_choice
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
MultipleChoiceField(**kwargs)
- 默认的Widget:SelectMultiple
- 空值:[](一个空列表)
- 规范化为:一个Unicode 对象列表。
- 验证给定值列表中的每个值都存在于选择列表中。
- 错误信息的键:required, invalid_choice, invalid_list
TypedMultipleChoiceField(**kwargs)
就像MultipleChoiceField,除了TypedMultipleChoiceField需要两个额外的参数,coerce和empty_value。
- 默认的Widget:SelectMultiple
- 空值:empty_value
- 规范化为:coerce参数提供的类型值列表。
- 验证给定值存在于选项列表中并且可以强制。
- 错误信息的键:required, invalid_choice
TypedChoiceField(ChoiceField) coerce = lambda val: val 对选中的值进行一次转换 empty_value= '' 空值的默认值
- 默认的Widget:Select
- 空值:empty_value
- 规范化为:coerce 参数类型的值。
- 验证给定的值在选项列表中存在并且可以被强制转换。
- 错误信息的键:required, invalid_choice
ImageField(**kwargs)
- 默认的Widget:ClearableFileInput
- 空值:None
- 规范化为: An UploadedFile object that wraps the file content and file name into a single object.
- 验证文件数据已绑定到表单,并且该文件具有Pillow理解的图像格式。
- 错误信息的键:required, invalid, missing, empty, invalid_image
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=''
- path,你想要列出的目录的绝对路径。这个目录必须存在。
- recursive,如果为False(默认值),只用直接位于path 下的文件或目录作为选项。如果为True,将递归访问这个目录,其所有的子目录和文件都将作为选项。
- match,正则表达式表示的一个模式;只有匹配这个表达式的名称才允许作为选项。
- allow_files,可选。为True 或False。默认为True。表示是否应该包含指定位置的文件。它和allow_folders 必须有一个为True。
- allow_folders,可选。为True 或False。 默认为False。表示是否应该包含指定位置的目录。 它和allow_files 必须有一个为True。
FileField(**kwargs)
- 默认的Widget:ClearableFileInput
- 空值:None
- 规范化为:一个UploadedFile 对象,它封装文件内容和文件名为一个单独的对象。
- 可以验证非空的文件数据已经绑定到表单。
- 错误信息的键:required, invalid, missing, empty, max_length
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:TextInput
- 空值:”(一个空字符串)
- 规范化为:一个UUID 对象。
- 错误信息的键:required, invalid
3.处理关系字段
两个字段可用于表示模型之间的关系:ModelChoiceField和ModelMultipleChoiceField。这两个字段都需要单个queryset参数,用于创建字段的选择。在表单验证时,这些字段将把一个模型对象(在ModelChoiceField的情况下)或多个模型对象(在ModelMultipleChoiceField的情况下)放置到cleaned_data表单的字典。
对于更复杂的用法,可以在声明表单字段时指定queryset=None,然后在窗体的init()方法中填充queryset:
ModelChoiceField(**kwargs)
- 默认的Widget:Select
- 空值:None
- 规范化为:一个模型实例。
- 验证给定的id存在于查询集中。
- 错误信息的键:required, invalid_choice
ModelMultipleChoiceField(**kwargs)
- 默认的Widget:SelectMultiple
- 空值:QuerySet (self.queryset.none())
- 规范化为: 模型实例的一个QuerySet。
- 验证在给定的值列表中的每个id存在于查询集中。
- 错误信息的键:required, list, invalid_choice, invalid_pk_value
Django内置插件:
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
# 单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 # )
在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。
如果内建的字段不能满足你的需求,你可以很容易地创建自定义的字段。你需要创建django.forms.Field 的一个子类。它只要求实现一个clean() 方法和接收上面核心参数的init() 方法(required, label, initial, widget, help_text)。
#方法一:
from app01 import models class DBForm(DForms.Form): host = fields.CharField() host_type = fields.IntegerField( widget=widgets.Select(choices=[]) ) def __init__(self,*args, **kwargs): # 执行父类的构造方法 super(DBForm,self).__init__(*args, **kwargs) self.fields['host_type'].widget.choices = models.UserType.objects.all().values_list('id','caption')
#方法二:使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现from
django
import
forms
from
django.forms
import
fields
from
django.forms
import
widgets
from
django.forms
import
models as form_model
from
django.core.exceptions
import
ValidationError
from
django.core.validators
import
RegexValidator
class
FInfo(forms.Form):
authors
=
form_model.ModelMultipleChoiceField(queryset
=
models.NNewType.objects.
all
())
# authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
自定义Form验证规则:
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开头')], ) # 方法二 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('手机号码格式错误') phone = fields.CharField(validators=[mobile_validate, ], error_messages={'required': '手机不能为空'}, 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