1 注册功能,登录功能,前端需要校验(字段长度,邮箱是否合法。。。) 2 前端校验可以没有,后端校验是必须的,使用传统方式 if判断写的很多 3 借助于forms组件,可以快速实现字段的校验 from django.forms import Form 总结一下,其实form组件的主要功能如下: 生成页面可用的HTML标签 对用户提交的数据进行校验 保留上次输入内容
Field required=True, 是否允许为空 widget=None, HTML插件 label=None, 用于生成Label标签或显示内容 initial=None, 初始值 help_text='', 帮助信息(在标签旁边显示) error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} 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类型
### 1 写一个类,类里写要校验的字段 class MyForm(forms.Form): # 校验这个字段,最大长度是32,最小长度是3 # required=Ture表示不能为空 为False表示可以为空,但是如果有值则进行校验 name = forms.CharField(required=False, max_length=32, min_length=3,label='用户名') email = forms.EmailField(label='邮箱') age=forms.IntegerField(max_value=200,min_value=0,label='年龄') ### 2 视图函数中使用 def register(request): # 数据可以是从前端传过来的,也可以是自己后台的数据 # 我现在有以下数据 data={'name':'lqz','email':'33333@qq.com','age':900} # data={'email':'33333@qq.com','age':100} # data={'age':100} # 校验数据是否合法 # 实例化得到form对象,把要校验的数据传入 form=myforms.MyForm(data) # 校验数据:form.is_valid() 返回布尔类型 if form.is_valid(): print('校验通过') # 校验通过的数据 print(form.cleaned_data) #无论是否校验通过,都可以获取 cleaned_data,但是必须在is_valid之后 else: print(form.cleaned_data) # form.cleaned_data 不一定是上面传入的数据,只包含校验通的的字段 print('校验失败') # 哪个字段失败了?失败的原因是什么 print(form.errors) print(type(form.errors)) from django.forms.utils import ErrorDict #### 重写了__str__ print(form.errors.as_json()) #可以返回多种不同格式的数据 print(form.errors.as_data()) # form.errors.as_ul() # 是为了渲染模板 return HttpResponse('ok')
# 定制模板中的显示样式,及配置类 # widget=widgets.PasswordInput(attrs={'class': 'form-control'}) # 错误信息中文显示 error_messages={'min_length': '太短了小伙子'} from django import forms from django.forms import widgets from django.forms import ValidationError class Myform(forms.Form): name = forms.CharField(required=False, max_length=32, min_length=3, label='用户名', widget=widgets.TextInput(attrs={'class': 'form-control'}), error_messages={'min_length': '太短了小伙子'}) password = forms.CharField(required=False, max_length=32, min_length=3, label='密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'}), error_messages={'min_length': '太短了小伙子'}) re_password = forms.CharField(required=False, max_length=32, min_length=3, label='确认密码', widget=widgets.PasswordInput(attrs={'class': 'form-control'}), error_messages={'min_length': '太短了小伙子'}) email = forms.EmailField(label='邮箱', error_messages={'required': '小惠子,这个必填'}, widget=widgets.TextInput(attrs={'class': 'form-control'})) age = forms.IntegerField(max_value=200, min_value=0, label='年龄', widget=widgets.TextInput(attrs={'class': 'form-control'})) text = forms.CharField(label='个人简介', widget=widgets.Textarea(attrs={'class': 'form-control'})) date = forms.DateField(label='日期', widget=forms.DateInput(attrs={'type':'date','class': 'form-control'})) def clean_name(self): name = self.cleaned_data.get('name') if name.startswith('sb'): raise ValidationError('不能以sb开头') else: return name def clean(self): password = self.cleaned_data.get('password') re_password = self.cleaned_data.get('re_password') if password == re_password: return self.cleaned_data else: raise ValidationError('两次密码不一致') class LgoinForm(forms.Form): name = forms.CharField( required=True, strip=True, help_text='不能小于六位', max_length=16, initial='狗剩', min_length=6, label="用户名", error_messages={ "max_length":"太长了", "min_length":"太短了了", }, widget=widgets.TextInput(attrs={'class':'form-control'}) ) password = forms.CharField( max_length=16, min_length=6, label="密码", widget=widgets.PasswordInput(attrs={'class':'form-control'}) ) sex = forms.ChoiceField( label='性别', initial=3, choices=((1,'男'),(2,'女'),(3,'保密')), widget=widgets.RadioSelect() ) city = forms.ChoiceField( label='籍贯', initial=1, choices=((1, '上海'), (2, '北京'), (3, '芜湖')), widget=widgets.Select() ) hobby = forms.MultipleChoiceField( label='爱好', initial=[1,3], choices=((1, '抽烟'), (2, '喝酒'), (3, '烫头')), widget=widgets.CheckboxSelectMultiple, ) girls = forms.MultipleChoiceField( label='女朋友', choices=((1, '红旭妹妹'), (2, '相熙哥哥'), (3, '程根姐姐')), widget=widgets.SelectMultiple, ) status = forms.ChoiceField( label='remeber me', choices=(('True', '红旭妹妹'),('False', '程根姐姐')), widget=widgets.CheckboxInput ) birthday = forms.CharField( label='生日', widget=widgets.TextInput(attrs={'type':'date'}) )
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 # 写一个类,写字段 class MyForm(forms.Form): # 校验这个字段,最大长度是32,最小长度是3 # required=Trues时字段允许不传,如果传则必须符合校验 name = forms.CharField(required=False, max_length=32, min_length=3,label='用户名') email = forms.EmailField(label='邮箱') age=forms.IntegerField(max_value=200,min_value=0,label='年龄')
from app01 import myforms def register(request): if request.method=='GET': form=myforms.MyForm() #获取form return render(request,'register.html',{'form':form}) elif request.method=='POST': # 数据校验 form=myforms.MyForm(request.POST) if form.is_valid(): print('校验通过,存数据库') else: print(form.errors.as_data()) print('校验失败,返回错误') return HttpResponse('ok')
<h1>手动创建模板</h1> <form action="" method="post"> <p>用户名:<input type="text" name="name"></p> <p>邮箱:<input type="text" name="email"></p> <p>年龄:<input type="text" name="age"></p> <p><input type="submit" value="提交"></p> </form>
<h1>半自动渲染模板1</h1> <form action="" method="post"> <p>用户名:{{ form.name }}</p> <p>邮箱:{{ form.email }}</p> <p>年龄:{{ form.age }}</p> <p><input type="submit" value="提交"></p> </form>
<h1>半自动渲染模板2</h1> <form action="" method="post"> <p>{{ form.name.label }}--{{ form.name }}</p> <p>{{ form.email.label }}---{{ form.email }}</p> <p>{{ form.age.label }}---{{ form.age }}</p> <p><input type="submit" value="提交"></p> </form>
<h1>半自动渲染模板3(用的最多)</h1> <form action="" method="post"> {% for foo in form %} <p>{{ foo.label }} :{{ foo }}</p> {% endfor %} <p><input type="submit" value="提交"></p> </form>
<h1>全自动(了解)</h1> <form action="" method="post"> {# {{ form.as_ul }}#} {{ form.as_p }} {# <table>#} {# {{ form.as_table }}#} {# </table>#} <p><input type="submit" value="提交"></p> </form>
1 form对象.errors 字典 2 name对象.errors ## 视图函数 def register(request): if request.method=='GET': form=myforms.MyForm() return render(request, 'register.html',{'form':form}) else: form=myforms.MyForm(request.POST) if form.is_valid(): return redirect('http://www.baidu.com') else: return render(request, 'register.html',{'form':form}) ## 模板 <form action="" method="post" novalidate> {% for foo in form %} <div class="form-group"> <label for="">{{ foo.label }}</label> {{ foo }} # foo.errors 每一个form字段的错误 <span class="text-danger pull-right">{{ foo.errors }}</span> </div> {% endfor %} <div class="text-center"> <input type="submit" value="提交" class="btn btn-danger"> # form.errors 所有的报错信息 <span>{{ form.errors }}</span> </div> </form>
# 对特定字段进行校验 class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" }, widget=forms.widgets.TextInput(attrs={"class": "form-control"}) ) ... # 定义局部钩子clean_fieldName,用来校验username字段,之前的校验股则还在,给你提供了一个添加一些校验功能的钩子 def clean_username(self): value = self.cleaned_data.get("username") if "666" in value: raise ValidationError("光喊666是不行的") else: return value
class LoginForm(forms.Form): ... password = forms.CharField( min_length=6, label="密码", widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) ) re_password = forms.CharField( min_length=6, label="确认密码", widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) ) ... # 定义全局的钩子,用来校验密码和确认密码字段是否相同,执行全局钩子的时候,cleaned_data里面肯定是有了通过前面验证的所有数据 def clean(self): password_value = self.cleaned_data.get('password') re_password_value = self.cleaned_data.get('re_password') if password_value == re_password_value: return self.cleaned_data #全局钩子要返回所有的数据 else: self.add_error('re_password', '两次密码不一致') #在re_password这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个re_password的值,所以打印clean_data的时候会看不到它 raise ValidationError('两次密码不一致') # form.errors.get('__all__') python中获取全局错误
#首先导入ModelForm from django.forms import ModelForm #在视图函数中,定义一个类,比如就叫StudentList,这个类要继承ModelForm,在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的) #在这个原类中,有以下属性(部分): class StudentList(ModelForm): class Meta: model =Student #对应的Model中的类 fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段 exclude = None #排除的字段 #error_messages用法: error_messages = { 'name':{'required':"用户名不能为空",}, 'age':{'required':"年龄不能为空",}, } #widgets用法,比如把输入用户名的input框给为Textarea #首先得导入模块 from django.forms import widgets as wid #因为重名,所以起个别名 widgets = { "name":wid.Textarea(attrs={"class":"c1"}) #还可以自定义属性 } #labels,自定义在前端显示的名字 labels= { "name":"用户名" } 然后在url对应的视图函数中实例化这个类,把这个对象传给前端 def student(request): if request.method == 'GET': student_list = StudentList() return render(request,'student.html',{'student_list':student_list}) 然后前端只需要 {{ student_list.as_p }} 一下,所有的字段就都出来了,可以用as_p显示全部,也可以通过for循环 <body> <div class="container"> <h1>student</h1> <form method="POST" novalidate> {% csrf_token %} {# {{ student_list.as_p }}#} {% for student in student_list %} <div class="form-group col-md-6"> {# 拿到数据字段的verbose_name,没有就默认显示字段名 #} <label class="col-md-3 control-label">{{ student.label }}</label> <div class="col-md-9" style="position: relative;">{{ student }}</div> </div> {% endfor %} <div class="col-md-2 col-md-offset-10"> <input type="submit" value="提交" class="btn-primary"> </div> </form> </div> </body>
def student(request): if request.method == 'GET': student_list = StudentList() return render(request,'student.html',{'student_list':student_list}) else: student_list = StudentList(request.POST) if student_list.is_valid(): student_list.save() return redirect(request,'student_list.html',{'student_list':student_list}
from django.shortcuts import render,HttpResponse,redirect from django.forms import ModelForm # Create your views here. from app01 import models def test(request): # model_form = models.Student model_form = models.Student.objects.all() return render(request,'test.html',{'model_form':model_form}) class StudentList(ModelForm): class Meta: model = models.Student #对应的Model中的类 fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段 exclude = None #排除的字段 labels = None #提示信息 help_texts = None #帮助提示信息 widgets = None #自定义插件 error_messages = None #自定义错误信息 #error_messages用法: error_messages = { 'name':{'required':"用户名不能为空",}, 'age':{'required':"年龄不能为空",}, } #widgets用法,比如把输入用户名的input框给为Textarea #首先得导入模块 from django.forms import widgets as wid #因为重名,所以起个别名 widgets = { "name":wid.Textarea } #labels,自定义在前端显示的名字 labels= { "name":"用户名" } def student(request): if request.method == 'GET': student_list = StudentList() return render(request,'student.html',{'student_list':student_list}) else: student_list = StudentList(request.POST) if student_list.is_valid(): student_list.save() return render(request,'student.html',{'student_list':student_list}) def student_edit(request,pk): obj = models.Student.objects.filter(pk=pk).first() if not obj: return redirect('test') if request.method == "GET": student_list = StudentList(instance=obj) return render(request,'student_edit.html',{'student_list':student_list}) else: student_list = StudentList(request.POST,instance=obj) if student_list.is_valid(): student_list.save() return render(request,'student_edit.html',{'student_list':student_list})
def full_clean(self): """ Clean all of self.data and populate self._errors and self.cleaned_data. """ self._errors = ErrorDict() if not self.is_bound: # Stop further processing. return self.cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self.empty_permitted and not self.has_changed(): return # 为什么全局钩子是在以上所有校验通过才走 self._clean_fields() 局部钩子 self._clean_form() 全句钩子 self._post_clean() def _clean_fields(self): for name, field in self.fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. if field.disabled: value = self.get_initial_for_field(field, name) else: value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: # 字段自己的校验完成后才执行局部钩子 if isinstance(field, FileField): initial = self.get_initial_for_field(field, name) value = field.clean(value, initial) else: value = field.clean(value) self.cleaned_data[name] = value # 为什么名字一定要叫clean_字段名, if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() #执行局部钩子 # 为什么成功要把字段值返回, self.cleaned_data[name] = value # 把局部钩子的返回结果覆盖到字段执行结果中 # 为失败抛出ValidationError except ValidationError as e: self.add_error(name, e) #如果发生错误,给字段添加错误 def _clean_form(self): try: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: # 为什么在视图函数中拿到的就是返回的 self.cleaned_data = cleaned_data #如果全局钩子有返回则覆盖所有返回内容 为什么全局钩子返回一个自己写的字典 ?