diff options
Diffstat (limited to 'djangorestframework/validators.py')
| -rw-r--r-- | djangorestframework/validators.py | 87 |
1 files changed, 63 insertions, 24 deletions
diff --git a/djangorestframework/validators.py b/djangorestframework/validators.py index aefad4b3..3d0a7794 100644 --- a/djangorestframework/validators.py +++ b/djangorestframework/validators.py @@ -22,6 +22,7 @@ class FormValidatorMixin(ValidatorMixin): """The form class that should be used for validation, or None to turn off form validation.""" form = None + bound_form_instance = None def validate(self, content): """Given some content as input return some cleaned, validated content. @@ -34,29 +35,39 @@ class FormValidatorMixin(ValidatorMixin): If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}.""" return self._validate(content) - def _validate(self, content, extra_fields=()): + def _validate(self, content, allowed_extra_fields=()): """Wrapped by validate to hide the extra_fields option that the ModelValidatorMixin uses. extra_fields is a list of fields which are not defined by the form, but which we still expect to see on the input.""" - if self.form is None: + bound_form = self.get_bound_form(content) + + if bound_form is None: return content - bound_form = self.get_bound_form(content) + self.bound_form_instance = bound_form - # In addition to regular validation we also ensure no additional fields are being passed in... - unknown_fields = set(content.keys()) - set(self.form().fields.keys()) - set(extra_fields) + seen_fields_set = set(content.keys()) + form_fields_set = set(bound_form.fields.keys()) + allowed_extra_fields_set = set(allowed_extra_fields) - # And that any extra fields we have specified are all present. - missing_extra_fields = set(extra_fields) - set(content.keys()) + # In addition to regular validation we also ensure no additional fields are being passed in... + unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set) # Check using both regular validation, and our stricter no additional fields rule - if bound_form.is_valid() and not unknown_fields and not missing_extra_fields: - return bound_form.cleaned_data + if bound_form.is_valid() and not unknown_fields: + # Validation succeeded... + cleaned_data = bound_form.cleaned_data + + # Add in any extra fields to the cleaned content... + for key in (allowed_extra_fields_set & seen_fields_set) - set(cleaned_data.keys()): + cleaned_data[key] = content[key] + + return cleaned_data # Validation failed... detail = {} - if not bound_form.errors and not unknown_fields and not missing_extra_fields: + if not bound_form.errors and not unknown_fields: detail = {u'errors': [u'No content was supplied.']} else: @@ -70,10 +81,6 @@ class FormValidatorMixin(ValidatorMixin): # Add any unknown field errors for key in unknown_fields: field_errors[key] = [u'This field does not exist.'] - - # Add any missing fields that we required by the extra fields argument - for key in missing_extra_fields: - field_errors[key] = [u'This field is required.'] if field_errors: detail[u'field-errors'] = field_errors @@ -105,8 +112,14 @@ class ModelFormValidatorMixin(FormValidatorMixin): model = None """The list of fields we expect to receive as input. Fields in this list will may be received with - raising non-existent field errors, even if they do not exist as fields on the ModelForm.""" + raising non-existent field errors, even if they do not exist as fields on the ModelForm. + + Setting the fields class attribute causes the exclude_fields class attribute to be disregarded.""" fields = None + + """The list of fields to exclude from the Model. This is only used if the fields class attribute is not set.""" + exclude_fields = ('id', 'pk') + # TODO: test the different validation here to allow for get get_absolute_url to be supplied on input and not bork out # TODO: be really strict on fields - check they match in the handler methods. (this isn't a validator thing tho.) @@ -122,8 +135,7 @@ class ModelFormValidatorMixin(FormValidatorMixin): On failure the ResponseException content is a dict which may contain 'errors' and 'field-errors' keys. If the 'errors' key exists it is a list of strings of non-field errors. If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}.""" - extra_fields = set(as_tuple(self.fields)) - set(self.get_bound_form().fields) - return self._validate(content, extra_fields) + return self._validate(content, allowed_extra_fields=self._property_fields_set) def get_bound_form(self, content=None): @@ -131,23 +143,50 @@ class ModelFormValidatorMixin(FormValidatorMixin): If the form class attribute has been explicitly set then use that class to create a Form, otherwise if model is set use that class to create a ModelForm, otherwise return None.""" + if self.form: # Use explict Form return super(ModelFormValidatorMixin, self).get_bound_form(content) elif self.model: # Fall back to ModelForm which we create on the fly - class ModelForm(forms.ModelForm): + class OnTheFlyModelForm(forms.ModelForm): class Meta: model = self.model - fields = tuple(set.intersection(self.model._meta.fields, self.fields)) - + #fields = tuple(self._model_fields_set) + # Instantiate the ModelForm as appropriate if content and isinstance(content, models.Model): - return ModelForm(instance=content) + return OnTheFlyModelForm(instance=content) elif content: - return ModelForm(content) - return ModelForm() + return OnTheFlyModelForm(content) + return OnTheFlyModelForm() # Both form and model not set? Okay bruv, whatevs... - return None
\ No newline at end of file + return None + + + @property + def _model_fields_set(self): + """Return a set containing the names of validated fields on the model.""" + model_fields = set(field.name for field in self.model._meta.fields) + + if self.fields: + return model_fields & set(as_tuple(self.fields)) + + return model_fields - set(as_tuple(self.exclude_fields)) + + @property + def _property_fields_set(self): + """Returns a set containing the names of validated properties on the model.""" + property_fields = set(attr for attr in dir(self.model) if + isinstance(getattr(self.model, attr, None), property) + and not attr.startswith('_')) + + if self.fields: + return property_fields & set(as_tuple(self.fields)) + + return property_fields - set(as_tuple(self.exclude_fields)) + + +
\ No newline at end of file |
