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方法一步解决。

原文地址:https://www.cnblogs.com/khal-Cgg/p/6237575.html