django-Form
参考博客:http://www.cnblogs.com/wupeiqi/articles/5246483.html
django的form主要有以下几个功能:
- 生成html标签
- 表单数据验证(显示错误信息)
- HTML-form保存上次提交的信息
- 初始化显示页面的内容
基本应用
1.创建form类
from django.forms import Form from django.forms import widgets from django.forms import fields class MyForm(Form): user = fields.CharField( widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'c1'}) ) gender = fields.ChoiceField( choices=((1, '男'), (2, '女'),), initial=2, widget=widgets.RadioSelect ) city = fields.CharField( initial=2, widget=widgets.Select(choices=((1,'上海'),(2,'北京'),)) ) pwd = fields.CharField( widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) )
2.View函数处理
from django.shortcuts import render, redirect from .forms import MyForm def index(request): if request.method == "GET": obj = MyForm() return render(request, 'index.html', {'form': obj}) elif request.method == "POST": obj = MyForm(request.POST, request.FILES) if obj.is_valid(): values = obj.clean() print(values) else: errors = obj.errors print(errors) return render(request, 'index.html', {'form': obj}) else: return redirect('http://www.google.com')
3.生成HTML
<form action="/" method="POST" enctype="multipart/form-data"> <p>{{ form.user }} {{ form.user.errors }}</p> <p>{{ form.gender }} {{ form.gender.errors }}</p> <p>{{ form.city }} {{ form.city.errors }}</p> <p>{{ form.pwd }} {{ form.pwd.errors }}</p> <input type="submit"/> </form>
From基本知识点
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类型 ...
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
常用选择插件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 单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
# )
Form之select操作
1. form操作动态select数据
from django import forms from django.forms import fields,widgets class LoginCheck(forms.Form): user = fields.CharField( widget = widgets.TextInput ) pwd = fields.CharField( min_length=6 max_length=18 widget= widgets.PasswordInput ) user_type = fields.ChoiceField( #choices =((1,'超级用户'),(2,"普通用户"),) choices = models.UserType.objects.values_list('id','name') widget= widgets.Select ) def index(request): dic = { "user":"张三", "pwd":123, "user_type":1 } obj = LoginCheck(initial=dic);#注意此处是个字典,数据的初始化操作 return render(request,"index.html",{'obj':obj}) HTML <p>{{obj.user}}</p> <p>{{obj.pwd}}</p> <P>{{obj.user_type}}</P>
以上方式可以动态的从数据库中获取数据,但是要注意的是,如果数据库中的数据更新时,需要重启系统才会进行更新,重新登入网页时不会更新的,主要因为变量,user,pwd,user_type是类变量,当解释器解释时,类变量会放到内存的一块空间存储,名声实例时,不会更新类变量,所以只能通过重启系统来进行数据库数据的更新读取。为了解决此问题。我们可以在实例声明时进行以下操作来手动进行更新数据库中的数据,操作如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class form_acion(forms.From): user_type = fields.ChoiceField( choices=models.Usertype.objects.values_list('id', 'name'), widget=widgets.Select, ) ....... def index(request): dic={ “user_type”:1 } obj = form_action(initial=dic) obj.fields['user_type'].choices=models.Usertype.objects.values_list('id','name') render(request,'index.html',{"obj":obj} 实例声明时更新数据
上述方式虽然可以解决动态加载问题,但是每次需要在函数中来添加,这样增加了工作量,我们可以将函数放置于__init__构造函数中,构造函数__init__函数会把类里的字段封装到obj.fields里面(里面有user、pwd、user_type三个字段)方式如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#字段,如果给不参数widget赋值,默认是Select,单列 user_type = fields.ChoiceField( choices=models.Usertype.objects.values_list('id', 'user_type'), widget=widgets.Select, ) #字段,给参数widget赋值SelectMultiple,多列 user_type = fields.ChoiceField( choices=models.Usertype.objects.values_list('id', 'user_type'), widget=widgets.SelectMultiple, ) #字段,给参数widget赋值Select,单列 user_type = fields.MultipleChoiceField( choices=models.Usertype.objects.values_list('id', 'user_type'), widget=widgets.Select ) #字段 如果不给参数widget赋值,默认是SelectMultiple,多列 user_type = fields.MultipleChoiceField( choices=models.Usertype.objects.values_list('id', 'user_type'), widget=widgets.SelectMultiple ) def __init__(self, *args, **kwargs): super(P, self).__init__(*args, **kwargs) #写法1 self.fields['user_type'].choices=models.Usertype.objects.values_list('id', 'user_type') #写法2 self.fields['user_type'].widget.choices=models.Usertype.objects.values_list('id', 'user_type')
django也为我们提供了动态加载的方式,但是django实现的并不完善,使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.forms import models as form_models #方式1,单选 user_type = form_models.ModelChoiceField(queryset=models.Usertype.objects.all(), empty_label='用户类型') #方式2,多选 user_type = form_models.ModelMultipleChoiceField(queryset=models.Usertype.objects.all())
ModelChoiceField参数格式
ModelChoiceField(ChoiceField) ... django.forms.models.ModelChoiceField queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=None, # HTML中value的值对应的字段(html源码中value不同) limit_choices_to=None # ModelForm中对queryset二次筛选
2.Form-内置钩子(数据验证)
普通数据验证index(request)
elif request.method =='POST': obj = F(request.POST)# 将post的数据放入验证 r = obj.is_valid()#判断校验结果 print(r) if r: print(obj.cleaned_data)#返回值是个字典{'pwd': '123', 'email': '123123@daid.com', 'usr': 'root'} else: print(obj.errors)#拿到所有错误信息,返回值<ul class="errorlist"><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li></ul> #可以将错误返回到前端 print(obj.errors.as_json())#json转换后的值
HTML <form action="/index" method="post"> {% csrf_token %} {#由于obj.errors.usr,pwd,user_type 是个列表,{"pwd": [{"code": "min_length", "message": "\u5bc6\u7801\u957f\u5ea6\u4e0d\u80fd\u5c0f\u4e8e6\u4e2a\u5b57\u7b26"}]},索引使用.0#} <p>{{obj.usr}}{{ obj.errors.usr.0 }}</p> <p>{{obj.pwd}}{{ obj.errors.pwd.0 }}</p> <p>{{obj.user_type}}{{ obj.errors.user_type.0 }}</p> <P><input type="submit" value="登入"></P> </form>
自定义验证方式:
1.可以使用参数:validators=[], 自定义验证规则
方式1
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#模块导入 from django.core.validators import RegexValidator class ActionCheck(forms.Form): phone = fields.CharField( validators=[RegexValidator(r'^[0-9]+$', '电话必须是数字'), RegexValidator(r'^181[0-9]+$', "电话必须以181开头")] widget=widgets.TextInput() )
方式2
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 自定义验证规则 def mobile_validate(value): 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 ActionCheck(forms.Form): phone = fields.CharField( validators=[mobile_validate, ], widget=widgets.TextInput()
2.当格式验证没有问题,是否可以将数据直接写入数据库?当然不能,以上方式只为验证数据格式,但是无法验证数据库数据一致性,如果是添加账户密码,数据库中如有相同数据,上述方式没有办法验证,这时我们可以使用以下方法,对数据进行进一步验证。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class FormLogin(forms.Form): username = fields.CharField( error_messages={'require': "账号不能为空哦"}, widget=widgets.TextInput() ) password = fields.CharField( error_messages={'require': "密码不能为空哦"}, widget=widgets.PasswordInput() ) def clean_username(self): value = self.cleaned_data['username'] #此处可以添加数据库读取数据操作 if 'root' == value: return value else: raise ValidationError("账号错误", code='user_error') def clean_password(self): value = self.cleaned_data['password'] #此处可以添加数据库读取操作 if '123456' == value: return value else: raise ValidationError('密码错误', code='pwd_error') #验证顺序: #1、username #2、clean_username #3、password #4、clean_password
上面的方法我们只能进行局部验证,要想进行全局验证,有以下方法:
- clean:全局验证
- _post_clean:全局验证,验证顺序在clean之后
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class FormLogin(forms.Form): def clean(self): if self.cleaned_data.get('usernmae') and self.cleaned_data.get('password'): pass # return self.cleaned_data else: print('error') raise ValidationError("账号或者密码错误哦",code='user_pwd_eorror') def _post_clean(self): pass
验证顺序:
- 第一个正则表达式,执行钩子函数clean_字段名
- 第二个正则,钩子函数clean_字段名
- 都执行完后执行clean方法
- clean执行完后执行_post_clean
追根朔源:is_vaild,找到errors,找到full_clean,所有钩子函数都是基于full_clean()执行的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
obj.errors { 'user':[{'code':'required','message':'xxxx'}], 'pwd':[{'code':'required','message':'xxxx'}], # 上面每个字段的错误信息放在里面,那clean总的错误信息放在哪里? '__all__':[], # 特殊的整体错误信息,放在这里 # NON_FIELD_ERRORS:[], 和 __all__ 一个意思。 }
3.Form-数据序列化
主要函数包括:
as_json()--序列化成json格式
as_data()--序列化成字典格式
html数据提交方式主要有:submit,ajax两种,主要应用于(新url,模态框。。)
以ajax提交为例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class FormHobby(forms.Form): hobby = fields.CharField( widget=widgets.TextInput( attrs={"placeholder": "爱好"} ) ) def clean_hobby(self): print('hobby', self.cleaned_data) value = self.cleaned_data.get('hobby') v = models.Hobby.objects.filter(hobby_name=value) if v: # 如果v存在,则报错 raise ValidationError('爱好已经存在') else: models.Hobby.objects.create(hobby_name=value) return value def hobby_add(request): import json res = {'status': True, 'error': None, 'data': None} if request.method == 'POST': obj = FormHobby(request.POST) if obj.is_valid():#如果数据验证正确 print(obj.cleaned_data) else: res['status'] = False res['error'] = obj.errors.as_json() print(obj.errors) return HttpResponse(json.dumps(res))
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
$(function(){ $('#i1').click(function(){ $('.modal,.cach').removeClass("hide") }); $('#add_cancel').click(function(){ $('.modal,.cach').addClass("hide") }); function csrfSafeMethod(method){ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ beforeSend:function(xhr,settings){ if(!csrfSafeMethod(settings.type)&&!this.crossDomain) { xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken')) } } } ); $("#add_action").click(function(){ $.ajax({ url: '/hobby_add', type:'POST', data:$("#form_add").serialize(), dataType:'JSON', traditional:true, success:function(obj){ if(obj.status == true) { location.reload() } else{ $("#shsh").text(JSON.parse(obj.error)['hobby'][0]['message']); } }, error:function(){} }); }); });
上述方式可以执行,使用的是as_json方式对错误信息进行格式化, 如果验证错误,前端需要进行两次json的反解才可以拿到错误数据,这样操作麻烦,工作量大。
通过源码json.dumps,我们可以搜索追朔到cls = JSONDecoder,JSONDecoder一个类型校正类,用于排除json不支持的类,JSONDecoder有一个default用于做json格式化数据监测,源码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
if cls is None: cls = JSONEncoder return cls( skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, indent=indent, separators=separators, default=default, sort_keys=sort_keys, **kw).encode(obj)
我们可以通过重写JSONDecoder的default方法来进行数据json格式化:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import json from django.core.exceptions import ValidationError class JsonCustomEncoder(json.JSONEncoder): def default(self, field): print(field) if isinstance(field, ValidationError): return {'code': field.code, 'messages': field.messages} else: return json.JSONEncoder.default(self, field) #isinstance(object, classinfo)如果参数object是classinfo的实例,或者object是classinfo类的子类的一个实例, 返回True。如果object不是一个给定类型的的对象, 则返回结果总是False。 #example:isinstance(a,dict) 判断对象a是否为字典,如果为真,会打印True,如为假,打印False。 def hobby_add(request): import json res = {'status': True, 'error': None, 'data': None} if request.method == 'POST': obj = FormHobby(request.POST) if obj.is_valid():#如果数据验证正确 print(obj.cleaned_data) else: res['status'] = False res['error'] = obj.errors.as_data() print('obj.errors.as_data()---->', obj.errors.as_data()) print('obj.errors.as_json()---->', obj.errors.as_json()) # for k,v in obj.errors.as_data().items(): # print(k,v) # print(obj.errors) #但是as_data是无法通过json来序列化的。 result = json.dumps(res, cls=JsonCustomEncoder) return HttpResponse(json.dumps(result))
以上方式,经过自己验证,貌似还有问题......
既然我们提到了json的格式化,那我们就在这里对json自定制格式做一个扩展:
日期格式自定制json化:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import json from datetime import date from datetime import datetime class JsonCustomEncoder1(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return field.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return field.strftime('%Y-%m-%d') # 转成字符串类型 else: return json.JSONEncoder.default(self, field) v = {'k':'123', "k1": datetime.now()} ds = json.dumps(v, cls=JsonCustomEncoder1) print(ds)
QuerySet自定制json化:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.core import serializers v = models.tb.objects.all() data = serializers.serialize("json", v)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return field.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return field.strftime('%Y-%m-%d') else: return json.JSONEncoder.default(self, field) v = models.tb.objects.values('id','name','ctime') v = list(v) # 把(类似列表的queryset类型)转换为列表 v = json.dumps(v,cls=JsonCustomEncoder) # 这里cls只对ctime操作。 # 如果没有ctime这个类型,只写上边的三句就可以实现
持续更新中......
参考:https://www.cnblogs.com/lgeng/p/7366370.html
参考:https://blog.csdn.net/fgf00/article/details/54614706
参考:http://www.cnblogs.com/wupeiqi/articles/6144178.html