Django(八)下:Model操作和Form操作、序列化操作
二、Form操作
一般会创建forms.py文件,单独存放form模块。
Form 专门做数据验证,而且非常强大。有以下两个插件:
- fields :验证(肯定会用的)
- widgets:生成HTML(有时候用,有时候可以不用)
- 一般新url方式操作用widgets,因为生成url不是关键的,可以保留上一次提交的数据
- Ajax请求的时候,可以不用它生成html
1.1、Form操作动态Select数据
urls.py
url(r'^index/$', views.index),
views.py
def index(request): from cmdb.forms import UserInfoForm obj = UserInfoForm() return render(request, 'index.html', {'obj':obj})
forms.py
from django import forms from django.forms import widgets, fields from cmdb import models class UserInfoForm(forms.Form): user = fields.CharField( required=False, widget=widgets.Textarea(attrs={'class':'c1'}) ) pwd = fields.CharField( max_length=12, widget=widgets.PasswordInput(attrs={'class':'c1'}) ) user_type = fields.ChoiceField( # choices=[(1,'普通用户'),(2,'超级用户')], # 手动填写 choices=models.UserType.objects.values_list('id','name'), # 数据库中获取 widget=widgets.Select )
models.py
class UserType(models.Model): name = models.CharField(max_length=32)
index.html
<body> <p>{{ obj.user }}</p> <p>{{ obj.pwd }}</p> <p>{{ obj.user_type }}</p> <p>{{ obj.user_type2 }}</p> </body>
上面有个问题,就是数据库添加数据后,django需要重启网页才能看到新加的数据。why?
forms里面,定义的类,类里的user、pwd、user_type都是静态字段(类变量),这些都是属于类的。
在__init__
里面写的属于对象。
而启动Django的时候,类变量一次性都加在到内存了。
上面views.py里的obj = UserInfoForm()
生成了一个对象,会执行类里的__init__
方法,会把类里的字段封装到obj.fields里面(里面有user、pwd、user_type三个字段),所以:
views.py
def index(request): from cmdb.forms import UserInfoForm from cmdb import models obj = UserInfoForm() obj.fields['user_type'].choices = models.UserType.objects.values_list('id','name') return render(request, 'index.html', {'obj':obj})
这样,数据库添加数据,网页上就可以实时更新了。但是如果有很多choices的话,也会写很多,也不好。
所以不在views.py里做修改,可以在forms.py里类的__init__
构造方法里写
forms.py
from django import forms from django.forms import widgets, fields from cmdb import models class UserInfoForm(forms.Form): user = fields.CharField( required=False, widget=widgets.Textarea(attrs={'class':'c1'}) ) pwd = fields.CharField( max_length=12, widget=widgets.PasswordInput(attrs={'class':'c1'}) ) user_type = fields.ChoiceField( # choices=[(1,'普通用户'),(2,'超级用户')], # 手动填写 choices=[], # 构造方法里取,这里只定义就可以了,不需要在获取一次。 widget=widgets.Select ) #### 另外一种写法 方式 #### user_type2 = fields.ChoiceField( widget=widgets.Select(choices=[]) ) def __init__(self, *args, **kwargs): super(UserInfoForm,self).__init__(*args, **kwargs) self.fields['user_type'].choices = models.UserType.objects.values_list('id','name') #### 另外一种写法 方式 #### self.fields['user_type2'].widget.choices = models.UserType.objects.values_list('id','name')
1.2、Form操作动态Select数据
上面1.1里是自己获取实现的,Django也可以自动实现,只是实现的不太好。
django 里自动实现的,看user_type3
from django import forms from django.forms import widgets, fields from django.forms.models import ModelChoiceField from cmdb import models class UserInfoForm(forms.Form): user = fields.CharField( required=False, widget=widgets.Textarea(attrs={'class':'c1'}) ) pwd = fields.CharField( max_length=12, widget=widgets.PasswordInput(attrs={'class':'c1'}) ) user_type = fields.ChoiceField( choices=[], widget=widgets.Select ) user_type2 = fields.ChoiceField( widget=widgets.Select(choices=[]) ) user_type3 = ModelChoiceField( queryset=models.UserType.objects.all() ) def __init__(self, *args, **kwargs): super(UserInfoForm,self).__init__(*args, **kwargs) self.fields['user_type'].choices = models.UserType.objects.values_list('id','name') self.fields['user_type2'].widget.choices = models.UserType.objects.values_list('id','name')
使用Django自动提供的这个方法,有一点不好的就是:需要自己在models里加上__str__
方法
class UserType(models.Model): name = models.CharField(max_length=32) def __str__(self): return self.name
ModelChoiceField 参数
ModelChoiceField(ChoiceField) ... django.forms.models.ModelChoiceField queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=None, # HTML中value的值对应的字段(html源码中value不同) limit_choices_to=None # ModelForm中对queryset二次筛选
ModelChoiceField 是单选的,多选的是:ModelMultipleChoiceField
2、Form内置钩子(数据验证)
初始化操作
加一个字典,生成显示默认值
views.py
def index(request): from cmdb.forms import UserInfoForm obj = UserInfoForm({'user':'lgeng','user_type':'2'}) # 默认值 return render(request, 'index.html', {'obj':obj})
数据验证
views.py
def index(request): from cmdb.forms import UserInfoForm if request.method == 'GET': obj = UserInfoForm({'user':'lgeng','user_type':'2'}) return render(request, 'index.html', {'obj':obj}) elif request.method == 'POST': obj = UserInfoForm(request.POST, request.FILES) # request.FILES:接收文件 obj.is_valid() # 数据验证
正则表达式验证成功了,但是如果用户名重复了,也是不让执行的,如何自定制呢?
注册数据验证示例
forms.py
from django.core.exceptions import ValidationError class RegisterForm(forms.Form): user = fields.CharField email = fields.EmailField def clean_user(self): # 对user做一个单独的验证 c = models.UserType.objects.filter(name=self.cleaned_data['user']) if c : # 如果用户名已经存在,提示报错信息 raise ValidationError('用户名已经存在',code='xxx') else: # 不存在,验证通过 return self.cleaned_data['user'] def clean_email(self): # 对email做单独的验证 # 验证必须要有返回值 return self.cleaned_data['email'] # 验证顺序: # 先user字段,在clean_user方法 # 再email字段,在clean_email方法
登录数据验证示例
forms.py
class LoginForm(forms.Form): user = fields.CharField # 这里也可以validators=[],自定义正则数据验证 email = fields.EmailField(validators=[]) def clean_user(self): pass def clean_pwd(self): pass # 当用户名和密码验证整体错误,报错给下面的方法 def clean(self): c = models.User.objects.filter(name=self.cleaned_data.get('user'),pwd=self.cleaned_data.get('pwd')) if c: return self.cleaned_data else: raise ValidationError("用户名或密码错误") # 自定义一些其他验证操作 def _post_clean(self): pass
验证顺序
这么多钩子,这么强大的数据验证,验证顺序是:
form循环,
- 先第一个字段正则表达式判断,执行字段钩子函数;
- 第二个字段正则,第二个的钩子;
- 所有字段完成后,执行clean钩子;
- clean执行完后,执行
_post_clean
钩子
怎么去记:通过看源码去找:
先找is_valid
,找到errors
,找到full_clean
,所有的钩子基于full_clean
执行的
验证完成后
views.py(伪代码)
def register(request): from cmdb.forms import RegisterForm from django.core.exceptions import NON_FIELD_ERRORS obj = RegisterForm(request.POST) if obj.is_valid(): obj.cleand_data else: obj.errors { 'user':[{'code':'required','message':'xxxx'}], 'pwd':[{'code':'required','message':'xxxx'}], # 上面每个字段的错误信息放在里面,那clean总的错误信息放在哪里? '__all__':[], # 特殊的整体错误信息,放在这里 # NON_FIELD_ERRORS:[], 和 __all__ 一个意思。 }
3、Form内置序列化错误信息
不管提交数据是浏览器提交、还是curl方式,还是ajax提交,只要是request.POST
都可以做验证。
Ajax 提交数据,进行验证
urls.py
url(r'^login.html$', views.login),
views.py
from django import forms from django.forms import widgets, fields class LoginForm(forms.Form): username = fields.CharField() password = fields.CharField( max_length=64, min_length=12 ) def login(request): import json res = {'status':True, 'error':None, 'data': None} if request.method == "GET": return render(request,"login.html") elif request.method == "POST": obj = LoginForm(request.POST) if obj.is_valid(): print(obj.cleand_data) else: # print(obj.errors, type(obj.errors)) res['error'] = obj.errors.as_json() # 转为json格式 return HttpResponse(json.dumps(res))
login.html
<body> <form id="fm"> {% csrf_token %} <p><input type="text" name="username" /></p> <p><input type="password" name="password" /></p> <a id="submit">ajax提交</a> </form> <script src="/static/jquery-1.12.4.js"></script> <script> // 页面框架加载完自动执行 $('#submit').click(function(){ $.ajax({ url:'/login.html', type:'POST', data:$('#fm').serialize(), success:function(arg){ console.log(arg); arg = JSON.parse(arg); // 转为字典 console.log(arg); }, error: function(){ } }) }) </script> </body>
上面的方式可以实现,只是前端需要反解两次,不太好,下面优化一下。
- as_json : 生成json格式
- as_data : 生成dict数据。
views.py
from django import forms from django.forms import widgets, fields class LoginForm(forms.Form): username = fields.CharField() password = fields.CharField( max_length=64, min_length=12 ) # 序列化,转换为指定数据类型 import json from django.core.exceptions import ValidationError class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, ValidationError): return {'code':field.code, 'messages': field.messages} else: return json.JSONEncoder.default(self, field) def login(request): res = {'status':True, 'error':None, 'data': None} if request.method == "GET": return render(request,"login.html") elif request.method == "POST": obj = LoginForm(request.POST) if obj.is_valid(): print(obj.cleand_data) else: # print(obj.errors, type(obj.errors)) # res['error'] = obj.errors.as_json() # 转为json格式 print(type(obj.errors.as_data())) for k,v in obj.errors.as_data().items(): print(k,v) # 这里v是ValidationError类型,不能序列化 res['error'] = obj.errors.as_data() result = json.dumps(res, cls=JsonCustomEncoder) return HttpResponse(json.dumps(result))
4、Django 序列化操作
Json.dumps
用来做序列化,但是只能序列化一些简单的基本数据类型。对于无法序列化的数据类型,只能自定制了。
ErrorDict自定义JSONEncoder
由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:
import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return o.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return o.strftime('%Y-%m-%d') # 转成字符串类型 else: return json.JSONEncoder.default(self, field) v = {'k':'123', "k1":datetime.datetime.now()} ds = json.dumps(v, cls=JsonCustomEncoder)
QuerySet 第一种序列化方式
上面自己写实现也可以,不过Django提供了方法做序列化
from django.core import serializers v = models.tb.objects.all() data = serializers.serialize("json", v)
QuerySet 第二种序列化方式
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这个类型,只写上边的三句就可以实现
注:form里__all__
整体的错误信息,前端展示如下:{{ obj.non_field_errors }}
转载请务必保留此出处:http://www.cnblogs.com/lgeng/articles/7366370.html
<!-- END -->
《版本说明》: 本文转自 -- http://blog.csdn.net/fgf00/article/details/54629502