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

  

  再點進去self.full_clean()。這裏的_errors是一個錯誤字典。 app

  

  在沒有其餘錯誤的時候,程序又執行了三個方法。第一個的做用是將每個提交過來的字段進行一個一個的驗證。第二個是全局驗證,第三個是hook。frontend

  

  在第一個函數中,看到name和field分別循環拿到了每個提交過來的字段和其Forms點後面的類進行一個一個驗證。沒有一個錯誤都會放到error中。ide

  注意紅框中的內容:去找一個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的值。源碼分析

  在第二個函數在中:post

  

  點進去clean函數發現是一個hook,這裏是讓用戶自定義全局驗證的hook。所以有:spa

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

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息