Form组件牛逼之处
Django的Form主要具有一下几大功能:
- 自动生成HTML标签(Form组件中内置的Field字段中参数widgets等一些参数配合使用)
- 验证用户提交的数据(显示错误信息)
- HTML Form提交保留上次提交数据(两种方式ajax提交和form表单提交)
- 初始化页面显示内容(有时候要把数据库的数据值初始化放到HTML标签中)
- 生成双HTML标签,一个隐藏,一个显示。(主要对用户的操作进行对比查看)
基本操作(form表单、ajax)
1、创建form类
1 from django.shortcuts import render, redirect, HttpResponse 2 from django.forms import Form 3 from django.forms import fields 4 5 6 class LoginForm(Form): 7 username = fields.CharField(required=True, 8 max_length=18, 9 min_length=6, 10 error_messages={ 11 'required': '用户名不能为空', 12 'max_length': '太长了', 13 'min_length': '太短了', 14 }) 15 password = fields.CharField(required=True, max_length=18, min_length=6)
2、form表单提交方式的views函数处理(刷新页面)
1 def login(request): 2 if request.method == 'GET': 3 return render(request, 'login.html') 4 else: 5 obj = LoginForm(request.POST) 6 """ 7 在实例化时候: 8 内部原理:1.self.fields = { 9 'username':正则表达式 10 'password':正则表达式 11 } 12 2.循环self.fields 13 flag = True 14 for k, v in self.fields.items(): 15 (1)username,正则表达式match方法 16 input输入框的value=request.POST.get(k) 若没匹配成功 17 flag = False 18 return flag 19 20 """ 21 if obj.is_valid(): 22 print(obj.cleaned_data) # 字典类型 成功之后用户提交的数据 23 return redirect('http://www.baidu.com') 24 else: 25 print(type(obj.errors)) # ul中套li标签验证全部的错误信息<class 'django.forms.utils.ErrorDict'> 对象__str__ 26 print(obj.errors['username'][0]) 27 return render(request, 'login.html', {'obj': obj})
3.ajax提交方式的视图函数处理(默认不刷新)
1 def ajaxlogin(request): 2 import json 3 ret = {'status': True, 'msg': None} 4 obj = LoginForm(request.POST) 5 if obj.is_valid(): 6 print(obj.cleaned_data) 7 else: 8 # v = json.dumps(obj.errors) 9 # print(v) 10 ret['status'] = False 11 ret['msg'] = obj.errors 12 return HttpResponse(json.dumps(ret)) 13 # ajax最好直接返回HttpResponse当然render也行render内部也返回HttpResponse好和前端交互最好转成json格式
4.HTML模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form id='f1' action="/login/" method="post">
{% csrf_token %}
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p><input type="submit" value="提交"></p>
<a οnclick="submitForm();">提交</a>
</form>
<script src="/static/jquery-3.3.1.min.js"></script>
<script>
/* ajax这样提交验证 默认不刷新,若要刷新在ajax中刷新 */
function submitForm() {
$('.c1').remove();
$.ajax({
url: '/ajax_login/',
type: 'POST',
data: $('#f1').serialize(),/* username='SB'&password='123456'&csrftoken='jhjhjhj' 普通拿数据最终都要转化成这样放在请求体 */
dataType: 'JSON', /* 把后端拿到的字符串转成对象类型 */
success: function (arg) {
console.log(arg);
if (arg.status){
}else {
$.each(arg.msg, function (index, value) {
console.log(index, value);
var tag = document.createElement('span');
tag.innerHTML = value[0];
tag.className = 'c1';
$('#f1').find('input[name="'+ index +'"]').after(tag);
/* input[name="msg"] */
})
}
}
})
}
</script>
</body>
</html>
保存用户上次提交的内容(方式1)
1.form类与视图代码逻辑处理
1 class RegisterForm(Form): 2 user = fields.CharField(max_length=15, min_length=6) 3 pwd = fields.CharField(min_length=6) 4 email = fields.EmailField() 5 phone = fields.RegexField('139\d+') 6 7 8 def register(request): 9 if request.method == 'GET': 10 obj = RegisterForm() 11 return render(request, 'register.html', {'obj': obj}) 12 else: 13 obj = RegisterForm(request.POST) 14 if obj.is_valid(): 15 print(obj.cleaned_data) 16 else: 17 print(obj.errors) 18 return render(request, 'register.html', {'obj': obj})
2.HTML模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form action="/register/" method="post" novalidate>
{% csrf_token %}
<p>
用户:{{ obj.user }}{{ obj.errors.user.0 }}
</p>
<p>
{# 密码:{{ obj.pwd }}{{ obj.errors.pwd.0 }}#}
密码:<input type="password" name="pwd">{{ obj.errors.pwd.0 }}
</p>
<p>
邮箱:{{ obj.email }}{{ obj.errors.email.0 }}
</p>
<p>
电话:{{ obj.phone }}{{ obj.errors.phone.0 }}
</p>
<p>
<input type="submit" value="提交">
</p>
</form>
</body>
</html>
Form组件内置的字段和插件
创建Form类时,主要用到字段和插件widstes,字段用于对用户请求数据的校验,插件用作自动生成HTML
1、Djano内置字段
1 Field 2 required=True, 是否允许为空 3 widget=None, HTML插件 4 label=None, 用于生成Label标签或显示内容(使用用户名密码时候,使用) 5 initial=None, 初始值(默认值) 6 help_text='', 帮助信息(在标签旁边显示) 7 error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} 8 show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直) 9 validators=[], 自定义验证规则 10 localize=False, 是否支持本地化,示例场景:utc时间和本地时间转化 11 disabled=False, 是否可以编辑 12 label_suffix=None Label内容后缀 13 14 15 CharField(Field) 16 max_length=None, 最大长度 17 min_length=None, 最小长度 18 strip=True 是否移除用户输入空白 19 20 IntegerField(Field) 21 max_value=None, 最大值 22 min_value=None, 最小值 23 24 FloatField(IntegerField) 25 ... 26 27 DecimalField(IntegerField) 28 max_value=None, 最大值 29 min_value=None, 最小值 30 max_digits=None, 总长度 31 decimal_places=None, 小数位长度 32 33 BaseTemporalField(Field) 34 input_formats=None 时间格式化 35 36 DateField(BaseTemporalField) 格式:2015-09-01 37 TimeField(BaseTemporalField) 格式:11:12 38 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 39 40 DurationField(Field) 时间间隔:%d %H:%M:%S.%f 41 ... 42 43 RegexField(CharField) 44 regex, 自定制正则表达式 45 max_length=None, 最大长度 46 min_length=None, 最小长度 47 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} 48 49 EmailField(CharField) 50 ... 51 52 FileField(Field) 53 allow_empty_file=False 是否允许空文件,文件也在clean_data里 54 55 ImageField(FileField) 56 ... 57 注:需要PIL模块,pip3 install Pillow 58 以上两个字典使用时,需要注意两点: 59 - form表单中 enctype="multipart/form-data" 60 - view函数中 obj = MyForm(request.POST, request.FILES) 61 62 URLField(Field) 63 ... 64 65 66 BooleanField(Field) 67 ... 68 69 NullBooleanField(BooleanField) 70 ... 71 72 ChoiceField(Field) 73 ... 74 choices=(), 选项,如:choices = ((0,'成都'),(1,'北京'),) 75 required=True, 是否必填 76 widget=None, 插件,默认select插件 77 label=None, Label内容 78 initial=None, 初始值 79 help_text='', 帮助提示 80 81 82 ModelChoiceField(ChoiceField) 83 ... django.forms.models.ModelChoiceField 84 queryset, # 查询数据库中的数据 85 empty_label="---------", # 默认空显示内容 86 to_field_name=None, # HTML中value的值对应的字段 87 limit_choices_to=None # ModelForm中对queryset二次筛选 88 89 ModelMultipleChoiceField(ModelChoiceField) #多选 90 ... django.forms.models.ModelMultipleChoiceField 91 92 93 94 TypedChoiceField(ChoiceField) 95 coerce = lambda val: val 对选中的值进行一次转换 96 empty_value= '' 空值的默认值 97 98 MultipleChoiceField(ChoiceField) 99 ... 100 101 TypedMultipleChoiceField(MultipleChoiceField) 102 coerce = lambda val: val 对选中的每一个值进行一次转换 103 empty_value= '' 空值的默认值 104 105 ComboField(Field) 106 fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 107 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) 108 109 MultiValueField(Field) 110 PS: 抽象类,只能被继承,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 111 112 SplitDateTimeField(MultiValueField) 113 input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y'] 114 input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] 115 116 FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 117 path, 文件夹路径 118 match=None, 正则匹配 119 recursive=False, 递归下面的文件夹 120 allow_files=True, 允许文件 121 allow_folders=False, 允许文件夹 122 required=True, 123 widget=None, 124 label=None, 125 initial=None, 126 help_text='' 127 128 GenericIPAddressField 129 protocol='both', both,ipv4,ipv6支持的IP格式 130 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 131 132 SlugField(CharField) 数字,字母,下划线,减号(连字符) 133 ... 134 135 UUIDField(CharField) uuid类型 136 ... 137 注:UUID是根据MAC以及当前时间等创建的不重复的随机字符串
2.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
3.常用插件示例
# 单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 # )
初始化数据
把数据初始化放在HTML标签中
1.Form
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 from django.core.validators import RegexValidator 5 6 7 class MyForm(Form): 8 user = fields.CharField() 9 10 city = fields.ChoiceField( 11 choices=((1, '成都'), (2, '北京'),), 12 widget=widgets.Select 13 )
2.views
1 from django.shortcuts import render, redirect 2 from .forms import MyForm 3 4 5 def index(request): 6 if request.method == "GET": 7 values = {'user': 'root', 'city': 2} 8 obj = MyForm(values) 9 10 return render(request, 'index.html', {'form': obj}) 11 elif request.method == "POST": 12 return redirect('http://www.google.com') 13 else: 14 return redirect('http://www.google.com')
3.HTML
1 <form method="POST" enctype="multipart/form-data"> 2 {% csrf_token %} 3 <p>{{ form.user }} {{ form.user.errors }}</p> 4 <p>{{ form.city }} {{ form.city.errors }}</p> 5 6 <input type="submit"/> 7 </form>
注意:如果涉及到choice字段,需要注意choices参数要配置拿到数据从数据库中获取,这样数据实时更新,
法1:需要从写init方法
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 5 6 class MyForm(Form): 7 8 user = fields.ChoiceField( 9 # choices=((1, '成都'), (2, '北京'),), 10 initial=2, 11 widget=widgets.Select 12 ) 13 14 def __init__(self, *args, **kwargs): 15 super(MyForm,self).__init__(*args, **kwargs) 16 # self.fields['user'].choices = ((1, '成都'), (2, '北京'),) 17 # 或 18 self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
法2:使用提供的ModelChioceField和ModelMultipleChoiceField字段来实现
1 from django import forms 2 from django.forms import fields 3 from django.forms import models as form_model 4 5 6 class FInfo(forms.Form): 7 authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选 8 # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
Form中对 对象验证的hook函数
在form验证提供了is_vaild方法进行验证,在这个方法中其实提供了许多的钩子方法,让我们可以自己定义验证方法,这样使得规则多元化。
Django2.1版本源码验证流程:
obj.is_vaild: 入口中self.is_bound代表数据不能为None和字段也是不能为None
在self.errors中:self._errors默认是None,必执行self.full_clean()方法-->self._errors = ErrorDict(),self.cleaned_data = {},空字典赋值给错误信息,通过信息
接着执行self._clean_fields() -->循环self.fields.items() 拿到字段名字和field正则匹配的规则,初步验证开始拿到用户提交的数据跟正则匹配,重点环节(反射if hasattr(self, 'clean_%s' % name),这里就可以根据定义的字段名字,我们可以开始自己定义这个函数自定义验证)如果没成功验证必须抛出ValidationError这个错(局部钩子)
接着执行self._clean_form() ---->self.clean()(返回self.cleaned_data)什么事都没干,这里又是一个钩子,我们自己定义可以对整个字段进行验证 ,否则报错抛出必须是ValidationError(全局钩子)
接着执行self._post_clean()----->直接pass了又是一个钩子,这里还可以再一次的进行验证,可以和self.clean()一样
总结:验证大体流程:obj.is_vaild-->self.is_bound-->self.errors-->self.full_clean-->self._clean_fields-->self._clean_form-->slef._post_clean
自定义验证
1.RegexValidator验证器
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 from django.core.validators import RegexValidator 5 6 class MyForm(Form): 7 user = fields.CharField( 8 validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], 9 )
2.自定义验证函数
1 import re 2 from django.forms import Form 3 from django.forms import widgets 4 from django.forms import fields 5 from django.core.exceptions import ValidationError 6 7 8 # 自定义验证规则 9 def mobile_validate(value): 10 mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') 11 if not mobile_re.match(value): 12 raise ValidationError('手机号码格式错误') 13 14 15 class PublishForm(Form): 16 17 18 title = fields.CharField(max_length=20, 19 min_length=5, 20 error_messages={'required': '标题不能为空', 21 'min_length': '标题最少为5个字符', 22 'max_length': '标题最多为20个字符'}, 23 widget=widgets.TextInput(attrs={'class': "form-control", 24 'placeholder': '标题5-20个字符'})) 25 26 27 # 使用自定义验证规则 28 phone = fields.CharField(validators=[mobile_validate, ], 29 error_messages={'required': '手机不能为空'}, 30 widget=widgets.TextInput(attrs={'class': "form-control", 31 'placeholder': u'手机号码'})) 32 33 email = fields.EmailField(required=False, 34 error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, 35 widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
3.采用局部钩子自定义验证
1 class LoginForm(forms.Form): 2 username = forms.CharField( 3 min_length=8, 4 label="用户名", 5 initial="SB", 6 error_messages={ 7 "required": "不能为空", 8 "invalid": "格式错误", 9 "min_length": "用户名最短8位" 10 }, 11 widget=forms.widgets.TextInput(attrs={"class": "form-control"}) 12 ) 13 ... 14 # 定义局部钩子,用来校验username字段 15 def clean_username(self): 16 value = self.cleaned_data.get("username") 17 if "吊炸天" in value: 18 raise ValidationError("吊炸天") 19 else: 20 return value
4.采用全局钩子验证函数
1 class LoginForm(forms.Form): 2 ... 3 password = forms.CharField( 4 min_length=6, 5 label="密码", 6 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) 7 ) 8 re_password = forms.CharField( 9 min_length=6, 10 label="确认密码", 11 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True) 12 ) 13 ... 14 # 定义全局的钩子,用来校验密码和确认密码字段是否相同 15 def clean(self): 16 password_value = self.cleaned_data.get('password') 17 re_password_value = self.cleaned_data.get('re_password') 18 if password_value == re_password_value: 19 return self.cleaned_data 20 else: 21 self.add_error('re_password', '两次密码不一致') 22 raise ValidationError('两次密码不一致')
5.采用self._post_clean()定义验证
1 class LoginForm(Form): 2 username=fields.CharField() 3 pwd=fields.CharField() 4 5 def _post_clean(self): 6 pass # 同上定义self.clean()
ModelForm组件
django中的modelform组件同时具有model和form作用,但是耦合度比较高,当项目需要拆分时候就比较困难了,所以在使用modelform时候需要先考虑项目的扩展性。
基本使用:
models.py
1 from django.db import models 2 3 # Create your models here. 4 5 6 class UserInfo(models.Model): 7 username = models.CharField(max_length=16) 8 password = models.CharField(max_length=64) 9 10 def __str__(self): 11 return self.username
modelform定义我把视图函数放在一起,简单的玩了
1 from django.shortcuts import render 2 from django.forms import ModelForm 3 from .models import UserInfo 4 # Create your views here. 5 6 7 class UserModelForm(ModelForm): 8 class Meta: 9 model = UserInfo # 指明model来源,models中的类 10 fields = '__all__' # 指明验证的字段 11 12 13 def login(request): 14 if request.method == 'GET': 15 obj = UserModelForm() 16 return render(request, 'modelform.html', {'obj': obj})
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
{{ obj.as_p }}
</body>
</html>
Mate的基本参数:
1 class Mate: 2 model, # 对应Model的 3 fields=None, # Model中的字段,__all__代表所有字段,定制使用列表列出字段 4 exclude=None, # 排除Model中的某个字段 5 labels=None, # 生成html字段显示名称,同model中定义使用verbose_name参数一样一样,但是类型是字典,形如{"字段":"名称"},定义了该名称会覆盖verbose_name 6 help_texts=None, # 帮助提示信息,类型是字典 7 widgets=None, # 自定义插件 8 error_messages=None, # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS) 9 field_classes=None # 自定义form字段类 (也可以自定义字段),主要用于修改验证的form格式,类型为字典 10 localized_fields=('birth_date',) # 本地化,如:根据不同时区显示数据 11 如: 12 数据库中 13 2018-12-27 04:10:57 14 setting中的配置 15 TIME_ZONE = 'Asia/Shanghai' 16 USE_TZ = True 17 则显示: 18 2018-12-27 12:10:57
例子:
1 class UserModelForm(ModelForm): 2 class Meta: 3 model = UserInfo # 指明model来源,models中的类 4 fields = '__all__' # 指明验证的字段 5 # fields = ['username', 'password'] 6 exclude = ['username'] # 排除某个字段 7 labels = { # 标签 8 'username': '用户', 9 'password': '密码' 10 } 11 help_texts = { # 提示信息 12 'username': '*必填', 13 'password': '*必填', 14 } 15 widgets = { # 插件 16 "password": forms.widgets.PasswordInput(attrs={"class": "c1"}), 17 } 18 error_messages = { # 自定义错误信息 19 "username": { 20 'required': '用户名不能为空', 21 'max_length': '用户名超出最大长度16' 22 }, 23 "__all__": {}, # 总的错误信息 24 } 25 field_classes = { 26 "password": myfields.IntegerField # 修改form字段验证 27 }
modelform验证
继承关系:ModelForm------>BaseModelForm------->BaseForm里边有is_valid()方法所以跟form验证类似
验证顺序:is_valid-->full_clean-->clean_fields-->clean_form-->_post_clean
相关验证方法:
is_valid()#基本验证 errors.as_json()#错误信息 clean()#钩子验证 cleaned_data#验证通过的数据
modelform数据初始化以及增加和修改
1.创建
1 2 obj=UserModelForm(request.POST) 3 if obj.is_valid(): 4 obj.save() #自动添加,默认参数commit为True
2.修改和初始化
1 # 根据POST数据创建一个新的form对象 2 obj = UserModelForm(request.POST) 3 4 # 创建用户对象 5 new_ user = obj.save() 6 7 # 基于一个用户对象创建form对象 8 edit_obj = UserInfo.objects.get(id=1) 9 # 使用POST提交的数据更新用户对象 10 obj = UserModelForm(request.POST, instance=edit_obj) 11 obj.save()