""" 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記錄。
Form: UserForm -> Form -> BaseForm
ModelForm: UserModelForm -> ModelForm -> BaseModelForm -> BaseForm
上述一直在敘述form其實說的就是ModelForm二者繼承的同樣的東西。
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',) # 本地化,如:根據不一樣時區顯示數據
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']
在new方法中即生成這個類以前找到了cls中的全部base_fields是個字典,循環這個字典使得每個字段都加上了本身定義的class。這樣在前端自動生成的as_p等中自動加上了class。最後return ModelForm.__new__(cls)。
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每一個字段自動生成。
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方法一步解決。