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 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‘,)  # 哪些字段做本地化

Meta中可以定义的字段类型

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),
]

urls.py 路由系统

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‘)

models.py 定义表

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‘:‘‘,‘user_type_id‘:1,},
    {‘username‘:‘lisi‘,‘email‘:‘‘,‘user_type_id‘:1,},
    {‘username‘:‘wangwu‘,‘email‘:‘‘,‘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,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‘,)

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 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

forms.py文件中使用这三种钩子

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都可以重写这个方法来对整体验证‘‘‘

models.py创建表

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})

views.py视图函数

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

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,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(‘用户密码正确‘)

views.py

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

models.py

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‘]

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

相关推荐