• Django ModelForm操作及验证


    一、内容回顾

        Model
            - 数据库操作
            - 验证
            class A(MOdel):
                user = 
                email = 
                pwd = 
    
        Form
            - class LoginForm(Form): 
                email = fields.EmailField()
                user = 
                pwd = 
    
            - is_valid -> 每一个字段进行正则(字段内置正则)+clean_字段 -> clean(__all__)  -> _post_clean
            - cleand_data
            - error
        --------> 推荐Form <---------
    

    二、ModelForm操作及验证

    Model + Form ==> ModelForm。model和form的结合体,所以有以下功能:

    • 数据验证
    • 数据库操作

    model有操作数据库的字段,form验证也有那几个字段,虽然耦合度降低,但是代码是有重复的。如果利用model里的字段,那是不是form里的字段就不用写了。

    1、Model + Form (之前的操作)

    models.py

    class UserType(models.Model):
        caption = models.CharField(max_length=32)
    
    class UserInfo(models.Model):
        username = models.CharField(max_length=32)
        email = models.EmailField()
        user_type = models.ForeignKey(to='UserType',to_field='id')
    

    forms.py

    from django import forms
    from django.forms import fields
    
    class UserInfoForm(forms.Form):
        # username = models.CharField(max_length=32)    <-- models
        username = fields.CharField(max_length=32)
        # email = models.EmailField()    <-- models
        email = fields.EmailField()
        # user_type = models.ForeignKey(to='UserType',to_field='id')    <-- models
        user_type = fields.ChoiceField(
            choices=models.UserType.objects.values_list('id','caption')
        )
    
        # 下面的操作是让数据在网页上实时更新。
        def __init__(self, *args, **kwargs):
            super(UserInfoForm,self).__init__(*args, **kwargs)
            self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')
    

    index.html

    <body>
        <form action="/index/" method="POST" novalidate="novalidate">
            {% csrf_token %}
            {{ obj.as_p }}
            <input type="submit" value="提交">
        </form>
    </body>
    

    novalidate : HTML5输入类型和浏览器验证

    如果表单中包含URLField、EmailField和其他整数字段类似,Django将使用url、email和number这样的HTML5输入类型。默认情况下,浏览器可能会对这些字段进行他们自身的验证,这些验证可能比Django的验证更严格。如果你想禁用这个行为,请设置form标签的novalidate属性,或者制定一个不同的字段,如TextInput。

    2、ModelForm 基本操作

    forms.py

    class UserInfoModelForm(forms.ModelForm):
    
        class Meta:
            model = models.UserInfo    # 与models建立了依赖关系
            fields = "__all__"
    

    views.py

    def index(request):
        if request.method == "GET":
            obj = UserInfoModelForm()
            return render(request,"index.html",{'obj':obj})
        elif request.method == "POST":
            obj = UserInfoModelForm(request.POST)
            print(obj.is_valid())  # 这是方法,别忘记了加括号
            print(obj.cleaned_data)
            print(obj.errors)
            return render(request,"index.html",{'obj':obj})
    

    自定制字段名

    如何定义http上定义的字段呢,自定义写成中文的?之前的用法是在Form里写上label。Model Form定义要用verbose_name

    models.py

    class UserInfo(models.Model):
        username = models.CharField(max_length=32, verbose_name='用户')
        email = models.EmailField(verbose_name='邮箱')
        user_type = models.ForeignKey(to='UserType',to_field='id', verbose_name='类型')
    

    如果不在model里定义,在modelForm里实现,利用labels

    class UserInfoModelForm(forms.ModelForm):
    
        class Meta:
            model = models.UserInfo
            fields = "__all__"
            labels = {
                'username':'用户名',
                'email':'邮箱',
            }
    

    展示指定的列

    fields = "__all__"  #展示所有字段
    fields = ['username','email']   # 显示指定列
    exclude = ['username']          # 排除指定列
    

    为什么modelForm里也能做验证?
    form里面有is_validcleaned_dataerrors

    # Form验证:
        UserInfoForm -> Form -> BaseForm( 包含is_valid等方法)
    
    # ModelForm验证:
        UserInfoModelForm -> ModelForm -> BaseModelForm -> BaseForm
    

    3、ModelForm组件

    ModelForm
        a.  class Meta:
                model,                           # 对应Model的
                fields=None,                     # 字段
                exclude=None,                    # 排除字段
                labels=None,                     # 提示信息
                help_texts=None,                 # 帮助提示信息
                widgets=None,                    # 自定义插件
                error_messages=None,             # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS)
                field_classes=None               # 自定义字段类 (也可以自定义字段)
                localized_fields=('birth_date',) # 本地化,如:根据不同时区显示数据
                如:
                    数据库中
                        2016-12-27 04:10:57
                    setting中的配置
                        TIME_ZONE = 'Asia/Shanghai'
                        USE_TZ = True
                    则显示:
                        2016-12-27 12:10:57
        b. 验证执行过程
            is_valid -> full_clean -> 钩子 -> 整体错误
    
        c. 字典字段验证
            def clean_字段名(self):
                # 可以抛出异常
                # from django.core.exceptions import ValidationError
                return "新值"
        d. 用于验证
            model_form_obj = XXOOModelForm()
            model_form_obj.is_valid()
            model_form_obj.errors.as_json()
            model_form_obj.clean()
            model_form_obj.cleaned_data
        e. 用于创建
            model_form_obj = XXOOModelForm(request.POST)
            #### 页面显示,并提交 #####
            # 默认保存多对多
                obj = form.save(commit=True)
            # 不做任何操作,内部定义 save_m2m(用于保存多对多)
                obj = form.save(commit=False)
                obj.save()      # 保存单表信息
                obj.save_m2m()  # 保存关联多对多信息
    
        f. 用于更新和初始化
            obj = model.tb.objects.get(id=1)
            model_form_obj = XXOOModelForm(request.POST,instance=obj)
            ...
    
            PS: 单纯初始化
                model_form_obj = XXOOModelForm(initial={...})
    

    注意:导入模块名(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',)  # 哪些字段做本地化
    

    4、ModelForm 数据库操作

    4.1、创建数据save

    如果数据验证是ok的,那么save,就直接在数据库中创建完数据了

    if obj.is_valid():
        obj.save()      # 创建数据
    

    在如下一对多、多对多关系中:

    class UserType(models.Model):
        caption = models.CharField(max_length=32)
    
    class UserGroup(models.Model):
        name = models.CharField(max_length=32)
    
    class UserInfo(models.Model):
        username = models.CharField(max_length=32)
        email = models.EmailField()
        user_type = models.ForeignKey(to='UserType',to_field='id')
        u2g = models.ManyToManyField(UserGroup)
    

    这样的话,执行上面的obj.save()会在UserInfo表和多对多关系表里都增加数据。

    views.py

    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()  # 等价以下三句
                # instance = obj.save(False)
                # instance.save()
                # obj.save_m2m()
            return render(request,'index.html',{'obj': obj})
    

    4.2、save 做了哪些操作?

    save源码里:

    def save(self, commit=True):
        """"""
        if commit:
            self.instance.save()    # 指的当前model对象
            self._save_m2m()        # 指:保存m2m对象
        else:
            self.save_m2m = self._save_m2m
        return self.instance    # model 类的对象
        """"""
    

    所以instance = obj.save(False)时,什么都不会操作。

    if obj.is_valid():
        instance = obj.save(False)
        instance.save()     # 当前对象表数据创建
        obj.save_m2m()      # 多对多表数据创建
        # 上面这三句完成的是和上面 obj.save 一样的操作。拆开就可以自定制操作了
    

    4.3、修改数据

    修改表数据是,记得把instance信息也传进去,不然是新建数据,而不是对某行数据进行修改。
    编辑用户信息,新url方式保留默认数据
    urls.py

        url(r'^user_list/', views.user_list),
        url(r'^edit-(d+)/', views.user_edit),
    

    views.py

    def user_list(request):
        li = models.UserInfo.objects.all().select_related('user_type')  # 这里只能是外键,多对多字段也不可以
        return render(request,'user_list.html',{'li': li})
    
    def user_edit(request, nid):
        # 获取当前id对象的用户信息
        # 显示用户已经存在数据
        if request.method == "GET":
            user_obj = models.UserInfo.objects.filter(id=nid).first()
            mf = UserInfoModelForm(instance=user_obj)   # 把默认数据传递进去
            return render(request,'user_edit.html',{'mf': mf, 'nid': nid})
        elif request.method == 'POST':
            # 数据修改的信息,给数据库的哪一行做修改?
            user_obj = models.UserInfo.objects.filter(id=nid).first()
            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})
    

    user_list.html

    <body>
        <ul>
            {% for row in li %}
                <li>{{ row.username }} - {{ row.user_type.caption }} - <a href="/edit-{{ row.id }}/">编辑</a></li>
            {% endfor %}
        </ul>
    </body>
    

    user_edit.html

    <body>
        <form method="POST" action="/edit-{{ nid }}/">
            {% csrf_token %}
        {{ mf.as_p }}
            <input type="submit" value="提交" />
        </form>
    </body>
    

    5、ModelForm钩子、额外字段

    数据验证钩子
    从上面的FormModelForm中,他们都是继承了BaseForm,而is_valid是在BaseForm中定义的,所以ModelForm也能和Form一样使用各种钩子

    额外字段
    像网页上的checkbox,一个月内免登陆,用提交到数据库么?这个只需要设置session和cookie就可以了。

    views.py

    class UserInfoModelForm(forms.ModelForm):
    
        is_rmb = fields.CharField(widget=widgets.CheckboxInput())  # 额外字段
    
        class Meta:
            model = models.UserInfo
            fields = '__all__'
    

    6、总结

        1. 生成HTML标签:class Meta: ...
        2. mf = xxxModelForm(instance=ModelObj) 生成默认值
        3. 额外的标签, is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput())
        4. 各种验证 is_valid() -> 各种钩子...
        5.  mf.save()
            # 或
            instance = mf.save(False)
            instance.save()
            mf.save_m2m()
    

    ModelForm因为model和form耦合太密切,所以一般写小程序用它。

     


    作者:马小跳_
    链接:https://www.jianshu.com/p/dee5806b27a1
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    为系统添加语言包
    除掉任务栏上的隐藏小按钮
    叠加多个无线网络
    在win7中通过手机投放媒体
    开关WI-Fi显示列表
    hibernate(二)对象的三种状态、一级缓存、多对一、inverse、cascade
    hibernate(一)helloworld、api、事务、配置文件、映射文件
    SQL Server2019安装
    Windows server2012搭建FTP服务器
    Gradle的使用教程
  • 原文地址:https://www.cnblogs.com/floodwater/p/10019284.html
Copyright © 2020-2023  润新知