1.1 ModelForm作用及基本使用
1、form 作用
1、功能1: 验证
2、功能2: 生成html标签(默认功能:保留上次提交的值)
3、功能3: 数据操作
4、功能4: HTML Form提交保留上次提交数据
5、功能5: 初始化页面显示内容
2、form使用原则
1、 新url方式操作(一定要用form方式生成html,避免提交刷新页面,丢失当前页面中填的值)
2、 发Ajax请求时可以不用form生成html标签,仅用form做验证,因为ajax请求本身不刷新页面,不必担心填
的值会丢失,当然使用form生成html也是可以的
1.2 Meta中定义字段验证规则
注意: 导入模块名(fields、widgets)和字段名重复,所以导入时要起个别名。
from django import forms from django.forms import fields as Ffields from django.forms import widgets as Fwidgets class UserInfoModelForm(forms.ModelForm): is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput()) class Meta: model = models.UserInfo fields = '__all__' # fields = ['username','email'] # exclude = ['username'] labels = { 'username': '用户名', 'email': '邮箱', } help_texts = { 'username': '...' } widgets = { 'username': Fwidgets.Textarea(attrs={'class': 'c1'}) } error_messages = { '__all__':{ # 整体错误信息 }, 'email': { 'required': '邮箱不能为空', 'invalid': '邮箱格式错误..', } } field_classes = { # 定义字段的类是什么 # 'email': Ffields.URLField # 这里只能填类,加上括号就是对象了。 } # localized_fields=('ctime',) # 哪些字段做本地化
1.3 ModelForm应用:编辑默认选中 及 提交自动保存
1、所有文件
from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^index/', views.index), url(r'^user_list/', views.user_list), url(r'^user_edit/(?P<nid>d+)/', views.user_edit), ]
from django.db import models class UserType(models.Model): caption = models.CharField(max_length=32) def __str__(self): return self.caption class UserInfo(models.Model): username = models.CharField(max_length=32) email = models.EmailField() user_type = models.ForeignKey(to='UserType', to_field='id')
from django.shortcuts import render,HttpResponse from app01 import models from app01.forms import UserInfoModelForm #1、创建UserInfo表中数据 def index(request): if request.method == 'GET': obj = UserInfoModelForm() return render(request,'index.html',{'obj':obj}) elif request.method == 'POST': obj = UserInfoModelForm(request.POST) if obj.is_valid(): obj.save() return render(request,'index.html',{'obj':obj}) #2、展示UserInfo表中数据 def user_list(request): li = models.UserInfo.objects.all().select_related('user_type') return render(request,'user_list.html',{'li':li}) #3、编辑默认选中 & 提交自动保存 def user_edit(request,nid): if request.method == 'GET': user_obj = models.UserInfo.objects.filter(id=nid).first() mf = UserInfoModelForm(instance=user_obj) #只用传入instance点击编辑时就会默认选中 return render(request,'user_edit.html',{'mf':mf,'nid':nid}) elif request.method == 'POST': #ModelForm修改 user_obj = models.UserInfo.objects.filter(id=nid).first() # 如果这里没有传入 instance=user_obj就会变成ModelForm添加 mf = UserInfoModelForm(request.POST,instance=user_obj) #修改后数据要传递进去 if mf.is_valid(): mf.save() else: print(mf.errors.as_json()) return render(request,'user_edit.html',{'mf':mf,'nid':nid}) #################### 自动生成UserType 和 UserInfo 表中的数据 ############## usertype_list = [ {'caption':'python_group'}, {'caption':'linux_group'}, ] userinfo_list = [ {'username':'zhangsan','email':'zhangsan@qq.com','user_type_id':1,}, {'username':'lisi','email':'lisi@qq.com','user_type_id':1,}, {'username':'wangwu','email':'wangwu@qq.com','user_type_id':1,}, ] ''' for u_type in usertype_list: models.UserType.objects.create(**u_type) for userinfo in userinfo_list: models.UserInfo.objects.create(**userinfo) '''
from django.shortcuts import render,HttpResponse from django import forms # 在ModelForm中有插件名widgets,所以这里引入插件时要取别名,否则报错 from django.forms import fields as Ffields from django.forms import widgets as Fwidgets from app01 import models class UserInfoModelForm(forms.ModelForm): extraField = Ffields.CharField( widget=Fwidgets.CheckboxInput() ) #ModelForm中可以自定制额外字段 class Meta: model = models.UserInfo #model指定关联那个类 fields = '__all__' labels = { 'username':'用户名', 'email':'邮箱', } help_texts = { 'username':'username字段提示信息' } widgets = { 'username':Fwidgets.Textarea(attrs={'class':'c1',}) } error_messages = { '__all__':{}, #定义整体的错误信息 'email':{ 'required':'邮箱不能为空', 'invalid':'邮箱格式错误', } } field_classes = { # 'email':Ffields.URLField #改变字段格式为url } localized_fields=('birth_date',)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/index/" method="POST"> {% csrf_token %} <p>{{ obj.username.label }}: {{ obj.username }} {{ obj.errors.username.0 }}</p> <p>{{ obj.email.label }}: {{ obj.email }} {{ obj.errors.email.0 }}</p> <p>{{ obj.user_type.label }}: {{ obj.user_type }} {{ obj.errors.user_type.0 }}</p> <p>{{ obj.extraField.label }}: {{ obj.extraField }} {{ obj.errors.extraField.0 }}</p> <input type="submit" value="提交"> </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for row in li %} <li>{{ row.username }}-{{ row.user_type.caption }} <a href="/user_edit/{{ row.id }}/">编辑</a></li> {% endfor %} </ul> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="POST" action="/user_edit/{{ nid }}/"> {% csrf_token %} {{ mf.as_p }} <input type="submit" value="提交"> </form> </body> </html>
2、说明
1. http://127.0.0.1:8000/index/ 页面用来新建用户(在views.py中也有批量创建的for循环)
2. http://127.0.0.1:8000/user_list/ 页面用来展示已创建用户,当点击编辑是会携带对应用户的id,已gett请求提交给user_edit
3. http://127.0.0.1:8000/user_edit/1/ 这个路径携带有需要修改的用户id,实现编辑默认选中 及 提交自动保存
1.4 MordelForm中生成html & 数据验证 常用方法
1、生成HTML常用语法
1、 obj.user.label 标签显示内容(如:用户名)
obj.user.label_tag
2、 obj.user 自动生成一个input标签,这种表自动保留上一次提交的数据功能
3、 obj.errors.user.0 获取字段错误信息(提取到user字段)
obj.errors 所有字段错误信息的html字符串
obj.user.errors 错误信息(返回html标签)<ul class="errorlist"><li>用户名不能为空</li></ul>
4、 obj.email.help_text 获取提示帮助信息(必须输入邮箱格式)
4、ModelForm对象数据验证
1. 用于验证
model_form_obj = XXOOModelForm(request.POST) # 将POST中提交的所有数据传给处理类(类中做校验)
model_form_obj.is_valid() # 类中对输入信息校验结果,符合返回True,否则返回False
model_form_obj.clean() # 用户POST中所有正确信息,格式就是字典
model_form_obj.cleaned_data
model_form_obj.errors.as_json() # 错误信息转换成json格式
model_form_obj.errors # 所有错误信息的html字符串(ul li格式)
2. 用于创建保存
# 默认保存多对多
obj = form.save(commit=True)
# 不做任何操作,内部定义 save_m2m(用于保存多对多)
obj = form.save(commit=False)
obj.save() # 保存单表信息
obj.save_m2m() # 保存关联多对多信息
3、新建数据 、修改数据、默认选中
UserInfoModelForm(request.POST,instance=user_obj) # 修改新旧数据都需要提交
UserInfoModelForm(request.POST) # 新建只需提交request.POST数据
UserInfoModelForm(instance=user_obj) # 只用传入instance点击编辑时就会默认选中
4、验证执行过程
is_valid -> full_clean -> 钩子 -> 整体错误
def clean_字段名(self): # 可以抛出异常 # from django.core.exceptions import ValidationError return "新值"
1.5 ModelForm验证规则中的内置钩子
1. from验证经历的顺序(搜索:Form and field validation)
验证执行过程: is_valid -> full_clean -> 钩子 -> 整体错误
1、拿到字段:用户发送一堆数据,根据form循环,拿到第一个字段
2、正则匹配:先进行fields默认正则表达式判断,然后进行自定的正则表达式判断(如果有)
3、字段钩子函数:然后执行字段的钩子函数,接着进行第二个字段,然后是第二个字段钩子函数...
4、clean钩子函数:字段钩子函数执行完了再执行clean钩子函数进行整体验证
5、_post_clean: 最后执行_post_clean钩子做其他验证
2、 form验证的错误信息存放位置
1、字段钩子错误信息放到对应的字段中 (obj.error中对应的字段字典)
2、整体错误信息会放到 {"__all__":[],}中等价于{'NON_FIELD_ERRORS':[],} (如:执行clean)
3、forms.py文件中使用这三种钩子
from django import forms from django.forms import fields as Ffields from django.forms import widgets as Fwidgets from app01 import models from django.core.exceptions import ValidationError class UserInfoModelForm(forms.ModelForm): extraField = Ffields.CharField( widget=Fwidgets.CheckboxInput() ) #ModelForm中可以自定制额外字段 class Meta: model = models.User #model指定关联那个类 fields = '__all__' labels = { 'name':'用户名', 'pwd':'密码', } #1 clean_字段名 是字段钩子(每个字段都有对应的这个钩子):如判断:“用户名已存在” def clean_user(self): # self.cleand_data['user']是用户提交的数据' c = models.User.objects.filter(name=self.cleand_data['user']).count() if not c: return self.cleand_data['user'] #必须要有返回值 else: raise ValidationError('用户名已存在',code='xxx') #2 clean钩子对整体验证:如判断“用户名或密码错误” def clean(self): c = models.User.objects.filter( name=self.cleand_data['user'], pwd=self.cleand_data['pwd']).count() if c: return self.cleand_data #正确的值必须return回去 else: raise ValidationError('用户名或密码错误') #3 在这里可以做 其他验证 def _post_clean(self): pass
1.6 使用type动态生成ModelForm类进行数据验证、生成html
from django.db import models class User(models.Model): username = models.CharField(max_length=32) def __str__(self): return self.username def default_form_validation(self): ''' 每个class_admin都可以重写这个方法来对整体验证'''
from django.shortcuts import render from app01 import models from app01.forms import create_model_form def login(request): model_form_class = create_model_form(request,models.User) form_obj = model_form_class() if request.method == 'POST': obj = models.User.objects.get(id=1) form_obj = model_form_class(instance=obj) form_obj = model_form_class(request.POST,instance=obj) if form_obj.is_valid(): form_obj.save() else: print('errors',form_obj.errors) return render(request, 'loin.html',{'form_obj':form_obj})
from django.forms import ModelForm,ValidationError from app01 import models def create_model_form(request,admin_class): def __new__(cls,*args,**kwargs): # 重写ModelForm的__new__方法 ''' 作用1--> 添加字段样式: class="form-control" 作用2--> 添加字段钩子: clean_字段名 ''' for field_name, field_obj in cls.base_fields.items(): # field_name : 字段名称,比如 "username" # field_obj : 定义字段样式的类 field_obj.widget.attrs['class'] = "form-control" # 给所有字段添加样式:class="form-control" if hasattr(admin_class, "clean_%s" % field_name): # clean_字段名 是字段钩子(每个字段都有对应的这个钩子) field_clean_func = getattr(admin_class, "clean_%s" % field_name) setattr(cls, "clean_%s" % field_name, field_clean_func) return ModelForm.__new__(cls) # 调用一下ModelForm的__new__方法否则不往下走 def default_clean(self): # 重写ModelForm的 default_clean 方法 '''添加默认钩子''' error_list = [] # 在这个cleaned方法中定义一个允许用户自己定义的方法做验证 response = admin_class.default_form_validation(self) if response: error_list.append(response) if error_list: raise ValidationError(error_list) class Meta: # ModelForm中使用Meta类进行条件过滤 model = models.User # model指定关联那个类 fields = "__all__" # 对那些字段过滤 labels = { 'username': '用户名', } attrs = {'Meta':Meta} _model_form_class = type("DynamicModelForm",(ModelForm,),attrs) #创建类并设置Meta属性 setattr(_model_form_class,"__new__",__new__) #动态将__new__函数添加到类中 setattr(_model_form_class,'clean',default_clean) #动态将_default_clean__函数添加到类中 return _model_form_class
1.7 ModelForm实例:不使用ModelForm生成html、并用ajax提交显示错误信息
相关知识点:
1、不使用ModelForm生成html
2、使用ajax提交数据,所以无法使用obj.errors.xxx 在前端显示错误信息
3、使用json序列化ModelForm验证的错误信息
4、使用ajax将错误信息放到对应位置
from django.shortcuts import render,HttpResponse from django.utils import timezone from django.core.exceptions import ValidationError import hashlib import json from app01.models import User from app01.forms import RegisterFrm 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 register(request): if request.method == 'POST': if request.method == 'POST': ret = {'status': False, 'error': None, 'data': None} obj = RegisterFrm(request.POST) if obj.is_valid(): cd = obj.cleaned_data new_user = obj.save(commit=False) # 将密码md5加密后再存到数据库中 password2 = cd.get("password2") m = hashlib.md5() m.update(password2.encode()) new_user.password = m.hexdigest() new_user.save() ret['status']=True return HttpResponse(json.dumps(ret)) else: ret['error'] = obj.errors.as_data() # as_json() 返回的是字符串 request = json.dumps(ret, cls=JsonCustomEncoder) return HttpResponse(request) obj = RegisterFrm() return render(request,'register.html',{'obj':obj}) # 登陆 : 这里没有做登录界面只有验证密码的函数 def login(request): if request.method == 'POST': login_name = request.POST.get("login_name") password = request.POST.get("password") if login_name and password: # 将密码转md5 m = hashlib.md5() m.update(password.encode()) password_md5 = m.hexdigest() # 获取用户对象 user = User.objects.filter(login_name=login_name, password=password_md5).first() if user: print('用户密码正确')
from django.db import models class User(models.Model): login_name = models.CharField(max_length=32,unique=True,verbose_name="用户名",error_messages={'unique':"用户名已占用"}) password = models.CharField(max_length=32) email = models.EmailField(unique=True,error_messages={'unique':'邮箱已注册'}) def __str__(self): return self.login_name
from django import forms from django.forms import fields from app01.models import User class RegisterFrm(forms.ModelForm): login_name = forms.CharField( required=True, error_messages={ 'required': '用户名不能为空', }) email = forms.EmailField( error_messages={ 'required': '邮箱不能为空', 'invalid':'邮箱格式错误' }) password = forms.CharField( widget=forms.PasswordInput, label="密码", error_messages={ 'required': '密码不能为空', }) password2 = forms.CharField( widget=forms.PasswordInput, label="确认密码", error_messages={ 'required': '确认密码不能为空', }) class Meta: model = User fields = ("login_name", "password", "email") def clean_login_name(self): """login_name里不允许有空格""" cd = self.cleaned_data login_name = cd.get("login_name").strip() if " " in login_name: raise forms.ValidationError("用户名不能有空格") return login_name def clean_password2(self): """两次密码是否一致""" cd = self.cleaned_data if cd['password'] != cd['password2']: raise forms.ValidationError('确认密码不一致') return cd['password2']
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div class="reg_frm fl"> <h1>注册{{ obj.errors }}</h1> <form id="register_frm"> <table> <tr> <th>用户名</th> <td><input type="text" id="login_name" name="login_name"></td> <td class="tips">{{ obj.login_name.errors.0 }}</td> </tr> <tr> <th>邮箱</th> <td><input type="email" id="email" name="email"></td> <td class="tips">{{ obj.email.errors.0 }}</td> </tr> <tr> <th>密码</th> <td><input id="password" type="password" name="password" autocomplete="off" minlength="3"></td> <td class="tips">{{ obj.password.errors.0 }}</td> </tr> <tr> <th>确认密码</th> <td><input id="password2" type="password" name="password2" autocomplete="off" minlength="3"></td> <td class="tips"></td> </tr> </table> <p><input type="button" value="ajax提交" onclick="ajaxSubmit();"></p> </form> </div> <script src="/static/jquery-1.12.4.min.js"></script> <script> function ajaxSubmit() { $.ajax({ url:'/register/', data:$('#register_frm').serialize(), type:'POST', dataType:'json', success:function(arg){ //服务端返回的是字符串格式 if(arg.status==true){ console.log('成功创建用户后的操作') }else { var check_list = { 'login_name':'用户名', 'email':'邮箱', 'password':'密码', 'password2':'确认密码' }; for(var key in check_list){ $('#'+key).parent().parent().find('.tips').text(''); var err_val = "arg.error." + key +"[0].messages"; try{ var err = eval(err_val); $('#'+key).parent().parent().find('.tips').text(err) }catch (err){ console.log(err.message); } } } } }) } </script> </body> </html>