ModelForm 数据验证 & 生成html & 数据库操作

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 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.3 ModelForm应用:编辑默认选中 及 提交自动保存

  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.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 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文件中使用这三种钩子

1.6 使用type动态生成ModelForm类进行数据验证、生成html


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.7 ModelForm实例:不使用ModelForm生成html、并用ajax提交显示错误信息

  相关知识点:

      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

 

相关文章