zoukankan      html  css  js  c++  java
  • django--ModelForm

    ModelForm的功能

    •   强大的数据验证
    •   适中的数据库操作

    forms验证源码分析

    """
    Django validation and HTML form handling.
    """
    
    from django.core.exceptions import ValidationError  # NOQA
    from django.forms.boundfield import *  # NOQA
    from django.forms.fields import *  # NOQA
    from django.forms.forms import *  # NOQA
    from django.forms.formsets import *  # NOQA
    from django.forms.models import *  # NOQA
    from django.forms.widgets import *  # NOQA

      以上代码可以看出,forms整合了fields,widgets,等模块。

      当is_valid()执行时:

      

      点进去errors:

      

      再点进去self.full_clean()。这里的_errors是一个错误字典。 

      

      在没有其他错误的时候,程序又执行了三个方法。第一个的作用是将每一个提交过来的字段进行一个一个的验证。第二个是全局验证,第三个是hook。

      

      在第一个函数中,看到name和field分别循环拿到了每一个提交过来的字段和其Forms点后面的类进行一个一个验证。没有一个错误都会放到error中。

      注意红框中的内容:去找一个clean_name的方法。显然是用来尝试拿到用户自定义验证的函数。因此:

    def clean_username(self):
                """
                Form中字段中定义的格式匹配完之后,执行此方法进行验证
                :return:
                """
                value = self.cleaned_data['username']
                if "666" in value:
                    raise ValidationError('666已经被玩烂了...', 'invalid')
                return value

       上述的代买就是自定义了username的验证方式,运用此函数就可以为所欲为。可以直接raise一个错误因为外层中已经exception过了。但是返回为clean的值。

      在第二个函数在中:

      

      点进去clean函数发现是一个hook,这里是让用户自定义全局验证的hook。因此有:

    def clean(self):
            v1 = self.cleaned_data['name']
            v2 = self.cleaned_data['email']
            if v1 == 'root' and v2 == 'root@live.com':
                pass
            else:
                raise ValidationError('用户名或邮箱错误!')
            return self.cleaned_data

      上述代码中的cleaned_data中拿到了提交过来的字段。

      第三个函数不在截图了,就是空hook。与子不同的是返回的东西:

     def _post_clean(self):
            v1 = self.cleaned_data['name']
            v2 = self.cleaned_data['email']
            if v1 == 'root' and v2 == 'root@live.com':
                pass
            else:
                self.add_error("__all__", ValidationError('用户名或密码错误...'))

      发生错误时,返回的是一个双下划綫all,这里是有原因的。上图中也是一个None。其结果是一致的都在error函数中归为了一个。

      总结:就是forms函数验证的过程就是将字段在 full_clean函数中验证,通过_clean_fields一个一个验证,有错误就提交给error记录。

    Modelform继承了两者的特点  

      Form: UserForm -> Form -> BaseForm

      ModelForm: UserModelForm -> ModelForm -> BaseModelForm -> BaseForm

      上述一直在叙述form其实说的就是ModelForm两者继承的一样的东西。

      ModelsForm的创建    

    from django.forms import ModelForm
    
    class userModelForm(ModelForm):
        psp = forms.CharField(max_length=32)

        和form不同的是:他不需要自己建对应的字段。只要关联一下app中的models就行,关联方式:在下面建一个meta。   

    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',) # 本地化,如:根据不同时区显示数据

    定义 widget

    class CustomerForm(ModelForm):
        def __new__(cls, *args, **kwargs):
            for field_name,field_obj in cls.base_fields.items():
                if field_name in cls.Meta.readonly_fields:
                    field_obj.widget.attrs['class'] = 'readonly_fields form-control'
                else:
                    field_obj.widget.attrs['class'] = 'form-control'
    
            return ModelForm.__new__(cls)
    
        def clean_qq(self):
            if self.instance.qq != self.cleaned_data['qq']:
                self.add_error("qq","Unknown error")
            return self.cleaned_data['qq']
    
        class Meta:
            model = models.Customer
            fields = "__all__"
            exclude = ['tags','content','memo','status','referral_from','consult_course']
            readonly_fields = [ 'qq','consultant','source']
    View Code

    在new方法中即生成这个类之前找到了cls中的所有base_fields是个字典,循环这个字典使得每一个字段都加上了自己定义的class。这样在前端自动生成的as_p等中自动加上了class。最后return ModelForm.__new__(cls)。

    如何根据不同models自动生成相应的ModelForm?

     1 # _*_ coding:utf-8 _*_
     2 
     3 from django.forms import forms,ModelForm,ValidationError
     4 from django.utils.translation import ugettext as _
     5 from crm import models
     6 
     7 
     8 def create_model_form(request,admin_info):   # 生成modelform的方法 需要的参数为列表 
     9     admin_class = admin_info[1]         # 这是models的一些其他自定制的信息  类似于admin
    10     model_obj = admin_info[0]          # 这就是一个需要生成modelform的model表
    11     readonly_fields = admin_class.readonly_fields   # 拿到model中的只读字段   
    12     def __new__(cls, *args, **kwargs):  #定义modelform的new方法 作用是加上widget
    13         for field_name,field_obj in cls.base_fields.items():  #上边有写 每个字段的名字和对象
    14             if admin_class.readonly_table:    # 若是在整个表在只读中
    15                 field_obj.widget.attrs['id'] = 'readonly_fields'  # 加上自定好的id和class
    16                 field_obj.widget.attrs['class'] = 'my-input readonly_fields'
    17             elif field_name in readonly_fields:  # 同理  某个字段在只读中
    18                 field_obj.widget.attrs['id'] = 'readonly_fields'
    19                 field_obj.widget.attrs['class'] = 'my-input readonly_fields'
    20             else:
    21                 field_obj.widget.attrs['class'] = 'my-input'
    22         return ModelForm.__new__(cls)    #  最后别忘加返回值 返回这个new方法给type
    23 
    24 
    25     def read_clean(self):   #  这里是自己自定制的一个只读验证方法 名字无所谓 是对每一次POST过来的信息校对是否偷偷该只读数据
    26         error_list  = []    # 目的是防止有人通过更改前端来修改只读数据
    27         for i in readonly_fields:   #  循环设定好的只读的字段
    28             field_val = getattr(self.instance, i)  # val in db  这里拿到了实例就是model的这个只读字段
    29             if hasattr(field_val, "select_related"):  # m2m   查看这个字段是否是m2m
    30                 m2m_objs = getattr(field_val, "select_related")().select_related()
    31                 m2m_vals = [j[0] for j in m2m_objs.values_list('id')]  #  拿到一个queryset 关联的多对多的id
    32                 set_m2m_vals = set(m2m_vals)  # 转换成set更利于进行对比数据是否有被改变
    33                 set_m2m_vals_from_frontend = set([i.id for i in self.cleaned_data.get(i)])  #  从自身的数据库取出正确未被改变的数据
    34                 if set_m2m_vals != set_m2m_vals_from_frontend:   #若是不同 说明只读数据被偷偷修改
    35                     # error_list.append(ValidationError(
    36                     #     _('Field %(field)s is readonly'),
    37                     #     code='invalid',
    38                     #     params={'field': field},
    39                     # ))
    40                     self.add_error(i, "readonly field")  # 最后发现self中的有add_error这个方法 外部modelform有try过
    41                 continue
    42 
    43             value_custom = getattr(self.instance,i)   #  不是多对多的数据可以直接比较
    44             value_field = self.cleaned_data.get(i)
    45             if value_custom != value_field:            #  数据被偷偷修改  
    46                 error_list.append(ValidationError(   #  添加一个错误的标准模式,还有列表形式
    47                     _('Field %(field)s is readonly,data should be %(val)s'),
    48                     code='invalid',
    49                     params={'field': i, 'val': value_custom},
    50                 ))
    51 
    52         if admin_class.readonly_table:    #  只有是post方式才会触发 modelform
    53             raise ValidationError(
    54                                 _('Table is  readonly,cannot be modified or added'),
    55                                 code='invalid'
    56                            )
    57 
    58         if error_list:
    59             raise ValidationError(error_list)
    60 
    61 
    62     class Meta:            # 定义modelform的meta函数
    63         model = model_obj
    64         fields = "__all__"    #  默认为all
    65         exclude =  admin_class.exclude_fields  # 除外的从其传过来的里面找
    66     attrs = {'Meta':Meta}       #  这里必须这样定义 要不报错
    67     _model_form_class =  type("DynamicModelForm",(ModelForm,),attrs)  #生成类的方法是用type 3个参数 分别是名字(自定义) 继承 和 meta方法
    68     setattr(_model_form_class,'__new__',__new__)  #通过setattr的方式将一个函数变为类的方法
    69     setattr(_model_form_class,'clean',read_clean)  #通过setattr的方式将一个函数变为类的方法
    70     return _model_form_class   #  返回已经生成的modelform

    这样,通过调用这个方法就可以生成不同表的modelform。用于model表的验证,前端生成,增删改查。前端循环这个modelform每个字段自动生成。

    一般的modelform操作

    1     if request.method == "POST":
    2         form_obj = model_form_class(request.POST,instance=obj) #更新
    3         if form_obj.is_valid():
    4             form_obj.save()
    5     else:
    6         form_obj = model_form_class(instance=obj)

    这里是一个更新操作,也是一个修改操作。与增加唯一不同的是是否有实例:instance 验证和save方法一步解决。

  • 相关阅读:
    python可变的参数列表
    python函数中的关键字参数
    python中的else子句
    python3中的range函数
    python列表和分片
    jmeter 参数化四种方式
    redis集群和单点可以共存
    localhost与127.0.0.1的区别是什么
    Pytest单元测试框架-Pytest环境安装
    Nginx、HAProxy、LVS三者的优缺点
  • 原文地址:https://www.cnblogs.com/khal-Cgg/p/6237575.html
Copyright © 2011-2022 走看看