form组件
form组件
一、form组件简介
1、form在组件的两大功能:
- 数据重置
- 校验规则
2、form组件于传统form表单对比
- 当我们用传统的form表单提交时会刷新页面,如果这个我们表单中的某项填错了,刷新后我们正确的选项也没有了.
- 传统的form表单需要我们自己亲自校验每一项,其工作量太大
-
form组件前端自动生成表单元素。
-
form组件可自动验证表单内容信息。
- form组件可保留用户上次输入的信息。
但是form表单的输出不包含submit
按钮,和表单的<form>
标签。 你必须自己提供。
二、form组件生成方法(三种)
1、方法一
<form action="/login/" method="post" novalidate> {% csrf_token %} {{ form_obj.as_p }} #{{ form.as_p }}将它们渲染在<p>
标签中 {{ form_obj.as_table }} #{{ form.as_table }}
以表格的形式将它们渲染在<tr>
标签中 {{ form_obj.as_ul }} #{{ form.as_ul }将它们渲染在<li>
标签中 <p><input type="submit" value="提交"></p> </form>
But:你必须自己提供<ul>
或<table>
元素。
方式一示例
#HTML <form action="/login/" method="post" novalidate> #novalidate不对输入进行验证的表单 {% csrf_token %} {{ form_obj.as_p }} <p><input type="submit" value="提交"></p> </form> #views from . import models from django import forms from django.forms import widgets from django.core.exceptions import NON_FIELD_ERRORS,ValidationError class LoginForm(forms.Form): user = forms.CharField(max_length=12,min_length=5, label="用户名", help_text="6~16个字符,区分大小写", error_messages={"required":"不能为空", "min_length":"最小长度为5",}) pwd = forms.CharField( help_text= "6~16个字符,区分大小写", error_messages={ "invalid":"格式错误" }, widget = widgets.PasswordInput(attrs={"class":"active"}) )
def login(request):
if request.method == "POST":
form_obj = LoginForm(request.POST)
if form_obj.is_valid():
# 数据全部合格
#取出合格数据
return HttpResponse("success")
else:
# 最少存在一个字段的错误
#取出错误信息
return render(request, "login.html", {"form_obj": form_obj}) #存放错误信息
form_obj = LoginForm()
return render(request,"login.html",{"form_obj":form_obj})
2、方法二(在任意标签中渲染)
<form action="/login/" method="post" novalidate> {# novalidate不对输入进行验证的表单 {% csrf_token %} <div> <label for="user">用户名:</label> {{ form_obj.user }} <span>{{ form_obj.errors.user.0 }}</span> </div> <div> <label for="pwd">密码:</label> {{ form_obj.pwd }}<span>{{ form_obj.errors.pwd.0 }}</span> </div> <p><input type="submit" value="提交"><span>{{ ret.0 }}</span></p> </form>
3、方法三(for循环生成)
<form action="/login/" method="post" novalidate> {% csrf_token %} {% for field in form_obj %} <div> <lable>{{ field.label }}</lable> {{ field }} </div> {% endfor %}
三、表单验证的model加form方法
调取数据库进行验证:在models.py文件中创建一个类,在数据库中生成
创建的一个类:
生成的数据库:
views及forms代码:
form组件代码可建立一个form.py文件,然后在views中引入
#form代码 class RegForm(forms.Form): user = forms.CharField(min_length=5,max_length=10, error_messages={"required": "不能为空", "min_length": "最小长度为5", }) pwd = forms.CharField( help_text="6~16个字符,区分大小写", widget=widgets.PasswordInput(attrs={"class": "active"}), error_messages={"required": "不能为空", }) repwd = forms.CharField( help_text="6~16个字符,区分大小写", widget=widgets.PasswordInput(attrs={"class": "active"}), error_messages={"required": "不能为空", } ) email = forms.EmailField( error_messages={"required": "不能为空", "invalid": "格式错误" }, ) tel = forms.CharField( error_messages={"required": "不能为空", } ) def clean_user(self): val = self.cleaned_data.get("user") if val.isdigit(): raise ValidationError("不能为数字") else: ret = UserInfo.objects.filter(name=val) if not ret: return val else: raise ValidationError("该用户已注册") def clean_tel(self): val = self.cleaned_data.get("tel") import re #引入正则 ret = re.search("^1[3578]\d{9}$",val) if ret: return val else: raise ValidationError("手机号码格式") def clean(self): repwd = self.cleaned_data.get("repwd") pwd = self.cleaned_data.get("pwd") if repwd == pwd: return self.cleaned_data else: raise ValidationError("两次密码不一致") views代码 def reg(request): if request.method == "POST": reg_form = RegForm(request.POST) if reg_form.is_valid(): name = reg_form.cleaned_data.get("user") pwd = reg_form.cleaned_data.get("pwd") UserInfo.objects.create(name=name,pwd=pwd) return redirect("/login/") else: all_error = reg_form.errors.get("__all__") return render(request, "register.html", {"reg_form": reg_form,"all_error":all_error}) reg_form = RegForm() return render(request,"register.html",{"reg_form":reg_form}) #html代码 <h1>注册页面</h1> <form action="/reg/" method="post" novalidate> {% csrf_token %} {% for field in reg_form %} <div> <lable>{{ field.label }}</lable> {{ field }} <span>{{ field.errors.0 }}</span> {% if field.label == "Repwd" %} {#field.label == "Repwd" Repwd是页面渲染后的名称#} <span>{{ all_error.0 }}</span> {% endif %} </div> {% endfor %} <p><input style="margin-left: 100px" type="submit" value="注册"> </form>
四、form组件中常用字段及插件
Django中form的创建涉及到一些字段及插件
字段用于对用户请求数据的验证,插件用于自动生成HTML
1.Django内置字段
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类型 ... # 注: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')
2.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的选项可以从数据库中获取,但是由于静态字段×××获取的值 # 无法实时更新×××,那么需要自定义构造方法从而达到此目的。 方法一. 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.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),) # 或 self.fields['user'].widget.choices = models.Classes.objects.all().value_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组件的校验
form组件自定义校验规则:
自定义校验规则中也会使用到局部钩子 跟 全局钩子 前边小例子中有涉及
方式一: from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.validators import RegexValidator
form diango. 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]
局部钩子跟全局钩子案例:
from django import forms #调用forms模块 from django.forms import widgets #调用widgets模块,用来对form组件的参数配置。 from django.core.exceptions import ValidationError #调用 ValidationError 模块。用来手动触发raise错误。 from fileupdate.models import * #载入必要的数据库列表。 class FormReg(forms.Form): name = forms.CharField(min_length=4, widget=widgets.TextInput(attrs={'class': 'form-control '}), label='姓名', error_messages={'required': '*不能为空', }) pwd = forms.CharField(min_length=4, widget=widgets.PasswordInput(attrs={'class': 'form-control'}), label='密码') r_pwd = forms.CharField(min_length=4, widget=widgets.PasswordInput(attrs={'class': 'form-control'}), label='确认密码') email = forms.EmailField(widget=widgets.EmailInput(attrs={'class': 'form-control'}), label='邮箱') tel = forms.CharField(max_length=13, label='电话', widget=widgets.TextInput(attrs={'class': 'form-control'})) ##字段的校验,通过对widget的属性设置,可定义INPUT标签的type类型,以及标签的其他属性。通过对label设置,可以自定义form渲染时的标签名, ##另外,通过对error_messages属性设置,可对验证信息进行自定义。注意:字典中错误信息的key值是固定的 def clean_name(self): #局部钩子 注意:名字必须为clean_%s ,这是根据源码来设置的。 #其原理是,当字段校验完毕后,再进行查找是否有以clean_开头的函数名,如果有,就调用该函数, #运行我们自定义的函数,如果满足条件就返回当前被校验字段的内容。否则手动触发ValidationError错误,源码中会捕获并将值返回。 val = self.cleaned_data.get('name') #通过cleaned_data获得对应字段的'干净数据' user_obj = User.objects.filter(name=val).first() #与对应的数据库中字段相比较,并获得一个字段对象 if not user_obj: #对字段进行判断,如果为空(数据库中没有对应的名字),那么返回这个校验值。 return val else: raise ValidationError('名字存在') #如果存在,那么手动触发异常(异常名为ValidationError),并设置自定义内容。 def clean(self): #全局钩子 注意:名字必须为clean,这是根据源码来设置的。 #其原理是对校验完毕的字段,再进行字段间的校验。当字段校验完毕,查找是否有clean的函数,如果有就运行该 #函数,其功能是对所有校验的字段进行校验比对。如果满足条件,就将cleaned_data返回(这与源码相匹配) #如果不满足就手动触发ValidationError错误。 pwd = self.cleaned_data.get('pwd') r_pwd = self.cleaned_data.get('r_pwd') if pwd and r_pwd: #如果两个字段中一个为空值那么就不用再进行校验。直接返回cleaned_data,通过校验功能返回错误信息。 if pwd == r_pwd: return self.cleaned_data else: raise ValidationError('两次密码不一致!') else: return self.cleaned_data def reg(request): if request.method == 'POST': #如果是一次POST提交,那么进行校验。 formreg = FormReg(request.POST) #对提交的信息实例化。 if formreg.is_valid(): #通过is_valid()方法进行判断,(注意:当执行这个函数时,将对所有字段进行校验,运行局部钩子和全局钩子) return HttpResponse('OK') else: print('cleaneddata', formreg.cleaned_data) print('errordata', formreg.errors) error = formreg.errors.get('__all__') #当设置了全局钩子时,要设置一个变量来获得全局钩子返回的错误信息。 #这是由于,全局钩子的错误在form对象的errors中,当clean()方法抛出异常时,源码会自动捕获,并将错误 #存储在errors字典中,其中键名'__all__'就是全局钩子的变量。 return render(request, 'reg.html', locals()) formreg = FormReg() #当为get请求时,实例化一个空的对象,通过这个空的实例化对象可以渲染前段,自动生成form表单。 return render(request,'reg.html', locals())