From a9df917d10e5f84090074e11213eb6d550c174cc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 11 Apr 2011 15:03:49 +0100 Subject: Lots of validator tests passing after refactor --- djangorestframework/validators.py | 91 ++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 35 deletions(-) (limited to 'djangorestframework/validators.py') diff --git a/djangorestframework/validators.py b/djangorestframework/validators.py index d96e8d9e..609e6d36 100644 --- a/djangorestframework/validators.py +++ b/djangorestframework/validators.py @@ -4,25 +4,31 @@ from django.db import models from djangorestframework.response import ResponseException from djangorestframework.utils import as_tuple -class ValidatorMixin(object): - """Base class for all ValidatorMixin classes, which simply defines the interface they provide.""" + +class BaseValidator(object): + """Base class for all Validator classes, which simply defines the interface they provide.""" + + def __init__(self, view): + self.view = view def validate(self, content): """Given some content as input return some cleaned, validated content. - Raises a ResponseException with status code 400 (Bad Request) on failure. - + Typically raises a ResponseException with status code 400 (Bad Request) on failure. + Must be overridden to be implemented.""" raise NotImplementedError() -class FormValidatorMixin(ValidatorMixin): - """Validator Mixin that uses forms for validation. - Extends the ValidatorMixin interface to also provide a get_bound_form() method. - (Which may be used by some emitters.)""" +class FormValidator(BaseValidator): + """Validator class that uses forms for validation. + Also provides a get_bound_form() method which may be used by some renderers. - """The form class that should be used for validation, or None to turn off form validation.""" - form = None - bound_form_instance = None + The view class should provide `.form` attribute which specifies the form classmethod + to be used for validation. + + On calling validate() this validator may set a `.bound_form_instance` attribute on the + view, which may be used by some renderers.""" + def validate(self, content): """Given some content as input return some cleaned, validated content. @@ -44,7 +50,7 @@ class FormValidatorMixin(ValidatorMixin): if bound_form is None: return content - self.bound_form_instance = bound_form + self.view.bound_form_instance = bound_form seen_fields_set = set(content.keys()) form_fields_set = set(bound_form.fields.keys()) @@ -78,7 +84,10 @@ class FormValidatorMixin(ValidatorMixin): detail[u'errors'] = bound_form.non_field_errors() # Add standard field errors - field_errors = dict((key, map(unicode, val)) for (key, val) in bound_form.errors.iteritems() if not key.startswith('__')) + field_errors = dict((key, map(unicode, val)) + for (key, val) + in bound_form.errors.iteritems() + if not key.startswith('__')) # Add any unknown field errors for key in unknown_fields: @@ -94,20 +103,21 @@ class FormValidatorMixin(ValidatorMixin): def get_bound_form(self, content=None): """Given some content return a Django form bound to that content. If form validation is turned off (form class attribute is None) then returns None.""" - if not self.form: + form_cls = getattr(self.view, 'form', None) + + if not form_cls: return None - if not content is None: + if content is not None: if hasattr(content, 'FILES'): - return self.form(content, content.FILES) - return self.form(content) - return self.form() + return form_cls(content, content.FILES) + return form_cls(content) + return form_cls() -class ModelFormValidatorMixin(FormValidatorMixin): - """Validator Mixin that uses forms for validation and falls back to a model form if no form is set. - Extends the ValidatorMixin interface to also provide a get_bound_form() method. - (Which may be used by some emitters.)""" +class ModelFormValidator(FormValidator): + """Validator class that uses forms for validation and otherwise falls back to a model form if no form is set. + Also provides a get_bound_form() method which may be used by some renderers.""" """The form class that should be used for validation, or None to use model form validation.""" form = None @@ -148,15 +158,18 @@ 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: + form_cls = getattr(self.view, 'form', None) + model_cls = getattr(self.view, 'model', None) + + if form_cls: # Use explict Form - return super(ModelFormValidatorMixin, self).get_bound_form(content) + return super(ModelFormValidator, self).get_bound_form(content) - elif self.model: + elif model_cls: # Fall back to ModelForm which we create on the fly class OnTheFlyModelForm(forms.ModelForm): class Meta: - model = self.model + model = model_cls #fields = tuple(self._model_fields_set) # Instantiate the ModelForm as appropriate @@ -176,24 +189,32 @@ class ModelFormValidatorMixin(FormValidatorMixin): @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) + model = getattr(self.view, 'model', None) + fields = getattr(self.view, 'fields', self.fields) + exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields) + + model_fields = set(field.name for field in model._meta.fields) - if self.fields: - return model_fields & set(as_tuple(self.fields)) + if fields: + return model_fields & set(as_tuple(fields)) - return model_fields - set(as_tuple(self.exclude_fields)) + return model_fields - set(as_tuple(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) + model = getattr(self.view, 'model', None) + fields = getattr(self.view, 'fields', self.fields) + exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields) + + property_fields = set(attr for attr in dir(model) if + isinstance(getattr(model, attr, None), property) and not attr.startswith('_')) - if self.fields: - return property_fields & set(as_tuple(self.fields)) + if fields: + return property_fields & set(as_tuple(fields)) - return property_fields - set(as_tuple(self.exclude_fields)) + return property_fields - set(as_tuple(exclude_fields)) -- cgit v1.2.3 From 349ffcaf5f03b55d8ffe92999814ba97da5ca870 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 11 Apr 2011 16:38:00 +0100 Subject: Rename mixins into Mixin class, rename ResponseException to ErrorResponse, remove NoContent --- djangorestframework/validators.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'djangorestframework/validators.py') diff --git a/djangorestframework/validators.py b/djangorestframework/validators.py index 609e6d36..c612de55 100644 --- a/djangorestframework/validators.py +++ b/djangorestframework/validators.py @@ -1,7 +1,7 @@ """Mixin classes that provide a validate(content) function to validate and cleanup request content""" from django import forms from django.db import models -from djangorestframework.response import ResponseException +from djangorestframework.response import ErrorResponse from djangorestframework.utils import as_tuple @@ -13,7 +13,7 @@ class BaseValidator(object): def validate(self, content): """Given some content as input return some cleaned, validated content. - Typically raises a ResponseException with status code 400 (Bad Request) on failure. + Typically raises a ErrorResponse with status code 400 (Bad Request) on failure. Must be overridden to be implemented.""" raise NotImplementedError() @@ -32,11 +32,11 @@ class FormValidator(BaseValidator): def validate(self, content): """Given some content as input return some cleaned, validated content. - Raises a ResponseException with status code 400 (Bad Request) on failure. + Raises a ErrorResponse with status code 400 (Bad Request) on failure. Validation is standard form validation, with an additional constraint that no extra unknown fields may be supplied. - On failure the ResponseException content is a dict which may contain 'errors' and 'field-errors' keys. + On failure the ErrorResponse 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}.""" return self._validate(content) @@ -97,7 +97,7 @@ class FormValidator(BaseValidator): detail[u'field-errors'] = field_errors # Return HTTP 400 response (BAD REQUEST) - raise ResponseException(400, detail) + raise ErrorResponse(400, detail) def get_bound_form(self, content=None): @@ -139,14 +139,14 @@ class ModelFormValidator(FormValidator): # TODO: be really strict on fields - check they match in the handler methods. (this isn't a validator thing tho.) def validate(self, content): """Given some content as input return some cleaned, validated content. - Raises a ResponseException with status code 400 (Bad Request) on failure. + Raises a ErrorResponse with status code 400 (Bad Request) on failure. Validation is standard form or model form validation, with an additional constraint that no extra unknown fields may be supplied, and that all fields specified by the fields class attribute must be supplied, even if they are not validated by the form/model form. - On failure the ResponseException content is a dict which may contain 'errors' and 'field-errors' keys. + On failure the ErrorResponse 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}.""" return self._validate(content, allowed_extra_fields=self._property_fields_set) -- cgit v1.2.3 From d373b3a067796b8e181be9368fa24e89c572c45e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 4 May 2011 09:21:17 +0100 Subject: Decouple views and resources --- djangorestframework/validators.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'djangorestframework/validators.py') diff --git a/djangorestframework/validators.py b/djangorestframework/validators.py index c612de55..d806a614 100644 --- a/djangorestframework/validators.py +++ b/djangorestframework/validators.py @@ -159,7 +159,7 @@ class ModelFormValidator(FormValidator): otherwise if model is set use that class to create a ModelForm, otherwise return None.""" form_cls = getattr(self.view, 'form', None) - model_cls = getattr(self.view, 'model', None) + model_cls = getattr(self.view.resource, 'model', None) if form_cls: # Use explict Form @@ -189,9 +189,10 @@ class ModelFormValidator(FormValidator): @property def _model_fields_set(self): """Return a set containing the names of validated fields on the model.""" - model = getattr(self.view, 'model', None) - fields = getattr(self.view, 'fields', self.fields) - exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields) + resource = self.view.resource + model = getattr(resource, 'model', None) + fields = getattr(resource, 'fields', self.fields) + exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields) model_fields = set(field.name for field in model._meta.fields) @@ -203,9 +204,10 @@ class ModelFormValidator(FormValidator): @property def _property_fields_set(self): """Returns a set containing the names of validated properties on the model.""" - model = getattr(self.view, 'model', None) - fields = getattr(self.view, 'fields', self.fields) - exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields) + resource = self.view.resource + model = getattr(resource, 'model', None) + fields = getattr(resource, 'fields', self.fields) + exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields) property_fields = set(attr for attr in dir(model) if isinstance(getattr(model, attr, None), property) -- cgit v1.2.3 From 4d126796752cc3c79a24fd9caed49da6c525096f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 10 May 2011 16:01:58 +0100 Subject: More tests, getting new serialization into resource --- djangorestframework/validators.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'djangorestframework/validators.py') diff --git a/djangorestframework/validators.py b/djangorestframework/validators.py index d806a614..bef85af7 100644 --- a/djangorestframework/validators.py +++ b/djangorestframework/validators.py @@ -31,20 +31,24 @@ class FormValidator(BaseValidator): def validate(self, content): - """Given some content as input return some cleaned, validated content. + """ + Given some content as input return some cleaned, validated content. Raises a ErrorResponse with status code 400 (Bad Request) on failure. Validation is standard form validation, with an additional constraint that no extra unknown fields may be supplied. On failure the ErrorResponse 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}.""" + 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, allowed_extra_fields=()): - """Wrapped by validate to hide the extra_fields option that the ModelValidatorMixin uses. + """ + 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.""" + expect to see on the input. + """ bound_form = self.get_bound_form(content) if bound_form is None: @@ -138,7 +142,8 @@ class ModelFormValidator(FormValidator): # 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.) def validate(self, content): - """Given some content as input return some cleaned, validated content. + """ + Given some content as input return some cleaned, validated content. Raises a ErrorResponse with status code 400 (Bad Request) on failure. Validation is standard form or model form validation, @@ -148,7 +153,8 @@ class ModelFormValidator(FormValidator): On failure the ErrorResponse 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}.""" + If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}. + """ return self._validate(content, allowed_extra_fields=self._property_fields_set) -- cgit v1.2.3 From 3f6b6e437bf24a55c33af5379b8ae89974edba57 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 17 May 2011 08:21:35 +0100 Subject: Remove validators (logic into resources) --- djangorestframework/validators.py | 228 -------------------------------------- 1 file changed, 228 deletions(-) delete mode 100644 djangorestframework/validators.py (limited to 'djangorestframework/validators.py') diff --git a/djangorestframework/validators.py b/djangorestframework/validators.py deleted file mode 100644 index bef85af7..00000000 --- a/djangorestframework/validators.py +++ /dev/null @@ -1,228 +0,0 @@ -"""Mixin classes that provide a validate(content) function to validate and cleanup request content""" -from django import forms -from django.db import models -from djangorestframework.response import ErrorResponse -from djangorestframework.utils import as_tuple - - -class BaseValidator(object): - """Base class for all Validator classes, which simply defines the interface they provide.""" - - def __init__(self, view): - self.view = view - - def validate(self, content): - """Given some content as input return some cleaned, validated content. - Typically raises a ErrorResponse with status code 400 (Bad Request) on failure. - - Must be overridden to be implemented.""" - raise NotImplementedError() - - -class FormValidator(BaseValidator): - """Validator class that uses forms for validation. - Also provides a get_bound_form() method which may be used by some renderers. - - The view class should provide `.form` attribute which specifies the form classmethod - to be used for validation. - - On calling validate() this validator may set a `.bound_form_instance` attribute on the - view, which may be used by some renderers.""" - - - def validate(self, content): - """ - Given some content as input return some cleaned, validated content. - Raises a ErrorResponse with status code 400 (Bad Request) on failure. - - Validation is standard form validation, with an additional constraint that no extra unknown fields may be supplied. - - On failure the ErrorResponse 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}. - """ - return self._validate(content) - - 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. - """ - bound_form = self.get_bound_form(content) - - if bound_form is None: - return content - - self.view.bound_form_instance = bound_form - - seen_fields_set = set(content.keys()) - form_fields_set = set(bound_form.fields.keys()) - allowed_extra_fields_set = set(allowed_extra_fields) - - # 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: - # Validation succeeded... - cleaned_data = bound_form.cleaned_data - - cleaned_data.update(bound_form.files) - - # 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: - detail = {u'errors': [u'No content was supplied.']} - - else: - # Add any non-field errors - if bound_form.non_field_errors(): - detail[u'errors'] = bound_form.non_field_errors() - - # Add standard field errors - field_errors = dict((key, map(unicode, val)) - for (key, val) - in bound_form.errors.iteritems() - if not key.startswith('__')) - - # Add any unknown field errors - for key in unknown_fields: - field_errors[key] = [u'This field does not exist.'] - - if field_errors: - detail[u'field-errors'] = field_errors - - # Return HTTP 400 response (BAD REQUEST) - raise ErrorResponse(400, detail) - - - def get_bound_form(self, content=None): - """Given some content return a Django form bound to that content. - If form validation is turned off (form class attribute is None) then returns None.""" - form_cls = getattr(self.view, 'form', None) - - if not form_cls: - return None - - if content is not None: - if hasattr(content, 'FILES'): - return form_cls(content, content.FILES) - return form_cls(content) - return form_cls() - - -class ModelFormValidator(FormValidator): - """Validator class that uses forms for validation and otherwise falls back to a model form if no form is set. - Also provides a get_bound_form() method which may be used by some renderers.""" - - """The form class that should be used for validation, or None to use model form validation.""" - form = None - - """The model class from which the model form should be constructed if no form is set.""" - 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. - - 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.) - def validate(self, content): - """ - Given some content as input return some cleaned, validated content. - Raises a ErrorResponse with status code 400 (Bad Request) on failure. - - Validation is standard form or model form validation, - with an additional constraint that no extra unknown fields may be supplied, - and that all fields specified by the fields class attribute must be supplied, - even if they are not validated by the form/model form. - - On failure the ErrorResponse 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}. - """ - return self._validate(content, allowed_extra_fields=self._property_fields_set) - - - def get_bound_form(self, content=None): - """Given some content return a Django form bound to that content. - - 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.""" - - form_cls = getattr(self.view, 'form', None) - model_cls = getattr(self.view.resource, 'model', None) - - if form_cls: - # Use explict Form - return super(ModelFormValidator, self).get_bound_form(content) - - elif model_cls: - # Fall back to ModelForm which we create on the fly - class OnTheFlyModelForm(forms.ModelForm): - class Meta: - model = model_cls - #fields = tuple(self._model_fields_set) - - # Instantiate the ModelForm as appropriate - if content and isinstance(content, models.Model): - # Bound to an existing model instance - return OnTheFlyModelForm(instance=content) - elif not content is None: - if hasattr(content, 'FILES'): - return OnTheFlyModelForm(content, content.FILES) - return OnTheFlyModelForm(content) - return OnTheFlyModelForm() - - # Both form and model not set? Okay bruv, whatevs... - return None - - - @property - def _model_fields_set(self): - """Return a set containing the names of validated fields on the model.""" - resource = self.view.resource - model = getattr(resource, 'model', None) - fields = getattr(resource, 'fields', self.fields) - exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields) - - model_fields = set(field.name for field in model._meta.fields) - - if fields: - return model_fields & set(as_tuple(fields)) - - return model_fields - set(as_tuple(exclude_fields)) - - @property - def _property_fields_set(self): - """Returns a set containing the names of validated properties on the model.""" - resource = self.view.resource - model = getattr(resource, 'model', None) - fields = getattr(resource, 'fields', self.fields) - exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields) - - property_fields = set(attr for attr in dir(model) if - isinstance(getattr(model, attr, None), property) - and not attr.startswith('_')) - - if fields: - return property_fields & set(as_tuple(fields)) - - return property_fields - set(as_tuple(exclude_fields)) - - - -- cgit v1.2.3