Validate in Model

Django 提供了model的实例validate(也就是数据库表的一行的验证), 这个功能由下面三块组成

1 Validate the model fields - Model.clean_fields()//验证每个field
2 Validate the model as a whole - Model.clean()// 验证整个model,这个是我们可以自己定制的
3 Validate the field uniqueness - Model.validate_unique() //验证field的唯一性

你可以通过显示调用实例的full_clean()方法调用上面三个验证,如果只调用了实例的save()方法,将不会调用验证

Model.full_clean(exclude=None, validate_unique=True)
该方法依次调用Model.clean_fields(),Model.clean(),Model.validate_unique()(validate_unique=True情况下才会调用validate_unique,默认是True)
如果验证过程中出现问题, 会产生ValidationError异常,该异常的属性message_dict包含以上三步中的所有异常
exclude 参数提供一个列表,该列表中列出的field会调过验证,默认是None
//下面直接上代码

 

 1       def full_clean(self, exclude=None, validate_unique=True):
 2         """
 3         Calls clean_fields, clean, and validate_unique, on the model,
 4         and raises a ``ValidationError`` for any errors that occurred.
 5         """
 6         errors = {}
 7         if exclude is None:
 8             exclude = []
 9         else:
10             exclude = list(exclude)
11 
12         try:
13             self.clean_fields(exclude=exclude)   //调用clean_fields
14         except ValidationError as e:
15             errors = e.update_error_dict(errors)
16 
17         # Form.clean() is run even if other validation fails, so do the
18         # same with Model.clean() for consistency.
19         try:
20             self.clean()    //调用clean
21         except ValidationError as e:
22             errors = e.update_error_dict(errors)
23 
24         # Run unique checks, but only for fields that passed validation.
25         if validate_unique:
26             for name in errors.keys():
27                 if name != NON_FIELD_ERRORS and name not in exclude:  //对通过clean_fields的fields进行unique的检查
28                     exclude.append(name)
29             try:
30                 self.validate_unique(exclude=exclude)
31             except ValidationError as e:
32                 errors = e.update_error_dict(errors)
33 
34         if errors:
35             raise ValidationError(errors)

 

Step1:clean_fields:
对每一个field进行clean,exclude包含不包括clean的field,可以看到本质上是对每一个field调用field.clean()

 1     def clean_fields(self, exclude=None):
 2         """
 3         Cleans all fields and raises a ValidationError containing a dict
 4         of all validation errors if any occur.
 5         """
 6         if exclude is None:
 7             exclude = []
 8 
 9         errors = {}
10         for f in self._meta.fields:  //取出每一个fiels
11             if f.name in exclude:   //调过exclude
12                 continue
13             # Skip validation for empty fields with blank=True. The developer
14             # is responsible for making sure they have a valid value.
15             raw_value = getattr(self, f.attname)  //取出原始值
16             if f.blank and raw_value in f.empty_values:  //如果该field是可以空的,并且raw_value是空的,则过
17                 continue
18             try:
19                 setattr(self, f.attname, f.clean(raw_value, self))  //调用field.clean() 并将clean之后的值放到f.attname,此处意味着如果有些clean会改变属性的值比如+1,那么调用两次就会+2,感觉这地方是个bug
20             except ValidationError as e:
21                 errors[f.name] = e.error_list  //{'field_name':'field_error_list'}
22 
23         if errors:
24             raise ValidationError(errors)

 

我们再看一下fields.clean() 这个方法

 

 1     def clean(self, value):
 2         """
 3         Validates the given value and returns its "cleaned" value as an
 4         appropriate Python object.
 5 
 6         Raises ValidationError for any errors.
 7         """
 8         value = self.to_python(value)  //将raw_value 转化为python类型
 9         self.validate(value)  //运行field.validate()
10         self.run_validators(value)  //运行feild.run_validators()
11         return value
12         
13     
14     def to_python(self, value):   //感觉这个方法什么事情都没做
15         return value
16 
17     def validate(self, value):            //仅仅检测了对于必须的field是否为空,注意为空的判断self.empty_values
18         if value in self.empty_values and self.required:
19             raise ValidationError(self.error_messages['required'], code='required')
20 
21     def run_validators(self, value):        //对提供的validators进行验证。validators是field定义的时候定义的
22         if value in self.empty_values:
23             return
24         errors = []
25         for v in self.validators:
26             try:
27                 v(value)
28             except ValidationError as e:
29                 if hasattr(e, 'code') and e.code in self.error_messages:
30                     e.message = self.error_messages[e.code]
31                 errors.extend(e.error_list)
32         if errors:
33             raise ValidationError(errors)    

 

 

Step2:Model.clean()
该方法由用户定制,进行模型的clean,代码里面也是空的

 

Step3.validate_unique
验证唯一性

 

 1     def validate_unique(self, exclude=None):
 2         """
 3         Checks unique constraints on the model and raises ``ValidationError``
 4         if any failed.
 5         """
 6         unique_checks, date_checks = self._get_unique_checks(exclude=exclude)  //可以看到分为unique_checks, date_checks
 7 
 8         errors = self._perform_unique_checks(unique_checks)
 9         date_errors = self._perform_date_checks(date_checks)
10 
11         for k, v in date_errors.items():
12             errors.setdefault(k, []).extend(v)
13 
14         if errors:
15             raise ValidationError(errors)

 

我们看一下self._get_unique_checks(exclude=exclude) 这个方法,这个方法返回unique_checks和date_checks

 

 1  def _get_unique_checks(self, exclude=None):
 2         """
 3         Gather a list of checks to perform. Since validate_unique could be
 4         called from a ModelForm, some fields may have been excluded; we can't
 5         perform a unique check on a model that is missing fields involved
 6         in that check.
 7         Fields that did not validate should also be excluded, but they need
 8         to be passed in via the exclude argument.
 9         """
10         if exclude is None:
11             exclude = []
12         unique_checks = []
13 
14         unique_togethers = [(self.__class__, self._meta.unique_together)]   //(class_name,uniq_list) unique_together不包括单独的field作为唯一性
15         for parent_class in self._meta.get_parent_list():
16             if parent_class._meta.unique_together:
17                 unique_togethers.append((parent_class, parent_class._meta.unique_together))   //[(class1,uniq1),(class2,uniq_2)]
18 
19         for model_class, unique_together in unique_togethers:
20             for check in unique_together:                                                // uniq = [(uniq1,uniq2)] 其中uniq1=(file1,filed2,filed3),如果一个uniq的组里面有一个filed是exclude的则调过
21                 for name in check:
22                     # If this is an excluded field, don't add this check.
23                     if name in exclude:
24                         break
25                 else:
26                     unique_checks.append((model_class, tuple(check)))   //check = (field1,field2...) 
27 
28         # These are checks for the unique_for_<date/year/month>.
29         date_checks = []
30 
31         # Gather a list of checks for fields declared as unique and add them to
32         # the list of checks.
33 
34         fields_with_class = [(self.__class__, self._meta.local_fields)]
35         for parent_class in self._meta.get_parent_list():
36             fields_with_class.append((parent_class, parent_class._meta.local_fields))
37 
38         for model_class, fields in fields_with_class:
39             for f in fields:
40                 name = f.name
41                 if name in exclude:
42                     continue
43                 if f.unique:
44                     unique_checks.append((model_class, (name,)))            //增加单独的唯一性的fields
45                 if f.unique_for_date and f.unique_for_date not in exclude:
46                     date_checks.append((model_class, 'date', name, f.unique_for_date))
47                 if f.unique_for_year and f.unique_for_year not in exclude:
48                     date_checks.append((model_class, 'year', name, f.unique_for_year))
49                 if f.unique_for_month and f.unique_for_month not in exclude:
50                     date_checks.append((model_class, 'month', name, f.unique_for_month))
51         return unique_checks, date_checks
52 
53   

 

  执行uniq的check

 

 1      def _perform_unique_checks(self, unique_checks):
 2         errors = {}
 3 
 4         for model_class, unique_check in unique_checks:
 5             # Try to look up an existing object with the same values as this
 6             # object's values for all the unique field.
 7 
 8             lookup_kwargs = {}
 9             for field_name in unique_check:
10                 f = self._meta.get_field(field_name)  //获取field名称对应的field,这地方为什么不直接传field呢?
11                 lookup_value = getattr(self, f.attname)
12                 if lookup_value is None:
13                     # no value, skip the lookup
14                     continue
15                 if f.primary_key and not self._state.adding:   //如果其中有一个field是主键并且是editing的话则不需要检测,因为主键是不可更改的
16                     # no need to check for unique primary key when editing
17                     continue
18                 lookup_kwargs[str(field_name)] = lookup_value
19 
20             # some fields were skipped, no reason to do the check  //前面某个field有continue的分支产生
21             if len(unique_check) != len(lookup_kwargs):
22                 continue
23 
24             qs = model_class._default_manager.filter(**lookup_kwargs)  //构建查询对象
25 
26             # Exclude the current object from the query if we are editing an
27             # instance (as opposed to creating a new one)
28             # Note that we need to use the pk as defined by model_class, not
29             # self.pk. These can be different fields because model inheritance
30             # allows single model to have effectively multiple primary keys.
31             # Refs #17615.
32             model_class_pk = self._get_pk_val(model_class._meta)
33             if not self._state.adding and model_class_pk is not None:
34                 qs = qs.exclude(pk=model_class_pk)
35             if qs.exists():                                //如果qs存在,则表示有重复
36                 if len(unique_check) == 1:
37                     key = unique_check[0]
38                 else:
39                     key = NON_FIELD_ERRORS
40                 errors.setdefault(key, []).append(self.unique_error_message(model_class, unique_check))
41 
42         return errors

 

#检测日期是否相同

 

 1     def _perform_date_checks(self, date_checks):
 2         errors = {}
 3         for model_class, lookup_type, field, unique_for in date_checks:
 4             lookup_kwargs = {}
 5             # there's a ticket to add a date lookup, we can remove this special
 6             # case if that makes it's way in
 7             date = getattr(self, unique_for)
 8             if date is None:
 9                 continue
10             if lookup_type == 'date':
11                 lookup_kwargs['%s__day' % unique_for] = date.day
12                 lookup_kwargs['%s__month' % unique_for] = date.month
13                 lookup_kwargs['%s__year' % unique_for] = date.year
14             else:
15                 lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(date, lookup_type)
16             lookup_kwargs[field] = getattr(self, field)
17 
18             qs = model_class._default_manager.filter(**lookup_kwargs)
19             # Exclude the current object from the query if we are editing an
20             # instance (as opposed to creating a new one)
21             if not self._state.adding and self.pk is not None:
22                 qs = qs.exclude(pk=self.pk)
23 
24             if qs.exists():
25                 errors.setdefault(field, []).append(
26                     self.date_error_message(lookup_type, field, unique_for)
27                 )
28         return errors

 

可以看到,在做validate的时候避免的很多不必要的validate,比如update的时候,当field是主键的时候就没有去做uniq的检查

 

posted @ 2015-04-11 09:35  zhupumpkin  阅读(697)  评论(0编辑  收藏  举报