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也是可以的
注意: 导入模块名(fields、widgets)和字段名重复,所以导入时要起个别名。
from django import formsfrom django.forms import fields as Ffieldsfrom django.forms import widgets as Fwidgetsclass 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‘,) # 哪些字段做本地化
Meta中可以定义的字段类型
1、所有文件
from app01 import viewsurlpatterns = [ 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),]
urls.py 路由系统
from django.db import modelsclass UserType(models.Model): caption = models.CharField(max_length=32) def __str__(self): return self.captionclass UserInfo(models.Model): username = models.CharField(max_length=32) email = models.EmailField() user_type = models.ForeignKey(to=‘UserType‘, to_field=‘id‘)
models.py 定义表
from django.shortcuts import render,HttpResponsefrom app01 import modelsfrom 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)‘‘‘
views.py 视图函数
from django.shortcuts import render,HttpResponsefrom django import forms# 在ModelForm中有插件名widgets,所以这里引入插件时要取别名,否则报错from django.forms import fields as Ffieldsfrom django.forms import widgets as Fwidgetsfrom app01 import modelsclass 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‘,)
forms.py 数据验证规则
<!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>
index.html生成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>
user_list.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>
user_edit.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、生成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. 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 formsfrom django.forms import fields as Ffieldsfrom django.forms import widgets as Fwidgetsfrom app01 import modelsfrom django.core.exceptions import ValidationErrorclass 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
forms.py文件中使用这三种钩子
from django.db import modelsclass User(models.Model): username = models.CharField(max_length=32) def __str__(self): return self.username def default_form_validation(self): ‘‘‘ 每个class_admin都可以重写这个方法来对整体验证‘‘‘
models.py创建表
from django.shortcuts import renderfrom app01 import modelsfrom app01.forms import create_model_formdef 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})
views.py视图函数
from django.forms import ModelForm,ValidationErrorfrom app01 import modelsdef 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
froms.py动态生成ModelForm类
相关知识点:
1、不使用ModelForm生成html
2、使用ajax提交数据,所以无法使用obj.errors.xxx 在前端显示错误信息
3、使用json序列化ModelForm验证的错误信息
4、使用ajax将错误信息放到对应位置
from django.shortcuts import render,HttpResponsefrom django.utils import timezonefrom django.core.exceptions import ValidationErrorimport hashlibimport jsonfrom app01.models import Userfrom app01.forms import RegisterFrmclass 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(‘用户密码正确‘)
views.py
from django.db import modelsclass 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
models.py
from django import formsfrom django.forms import fieldsfrom app01.models import Userclass 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‘]
forms.py
<!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>
register.html