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的检查