Commit a9df917d by Tom Christie

Lots of validator tests passing after refactor

parent 136c9b52
......@@ -9,7 +9,6 @@ from django.template import RequestContext, loader
from django import forms
from djangorestframework.response import NoContent, ResponseException
from djangorestframework.validators import FormValidatorMixin
from djangorestframework.utils import dict2xml, url_resolves
from djangorestframework.markdownwrapper import apply_markdown
from djangorestframework.breadcrumbs import get_breadcrumbs
......@@ -217,15 +216,11 @@ class DocumentingTemplateEmitter(BaseEmitter):
#form_instance = resource.form_instance
# TODO! Reinstate this
form_instance = None
form_instance = getattr(resource, 'bound_form_instance', None)
if isinstance(resource, FormValidatorMixin):
# If we already have a bound form instance (IE provided by the input parser, then use that)
if resource.bound_form_instance is not None:
form_instance = resource.bound_form_instance
if not form_instance and hasattr(resource, 'get_bound_form'):
# Otherwise if we have a response that is valid against the form then use that
if not form_instance and resource.response.has_content_body:
if resource.response.has_content_body:
try:
form_instance = resource.get_bound_form(resource.response.cleaned_content)
if form_instance and not form_instance.is_valid():
......@@ -233,12 +228,12 @@ class DocumentingTemplateEmitter(BaseEmitter):
except:
form_instance = None
# If we still don't have a form instance then try to get an unbound form
if not form_instance:
try:
form_instance = resource.get_bound_form()
except:
pass
# If we still don't have a form instance then try to get an unbound form
if not form_instance:
try:
form_instance = resource.get_bound_form()
except:
pass
# If we still don't have a form instance then try to get an unbound form which can tunnel arbitrary content types
if not form_instance:
......
......@@ -5,15 +5,14 @@ from django.db.models.fields.related import RelatedField
from djangorestframework.response import Response, ResponseException
from djangorestframework.resource import Resource
from djangorestframework.validators import ModelFormValidatorMixin
from djangorestframework import status
from djangorestframework import status, validators
import decimal
import inspect
import re
class ModelResource(Resource, ModelFormValidatorMixin):
class ModelResource(Resource):
"""A specialized type of Resource, for resources that map directly to a Django Model.
Useful things this provides:
......@@ -21,6 +20,9 @@ class ModelResource(Resource, ModelFormValidatorMixin):
1. Nice serialization of returned Models and QuerySets.
2. A default set of create/read/update/delete operations."""
# List of validators to validate, cleanup and type-ify the request content
validators = (validators.ModelFormValidator,)
# The model attribute refers to the Django Model which this Resource maps to.
# (The Model's class, rather than an instance of the Model)
model = None
......
......@@ -196,6 +196,29 @@ class RequestMixin(object):
return parser.parse(stream)
def validate(self, content):
"""
Validate, cleanup, and type-ify the request content.
"""
for validator_cls in self.validators:
validator = validator_cls(self)
content = validator.validate(content)
return content
def get_bound_form(self, content=None):
"""
Return a bound form instance for the given content,
if there is an appropriate form validator attached to the view.
"""
for validator_cls in self.validators:
if hasattr(validator_cls, 'get_bound_form'):
validator = validator_cls(self)
return validator.get_bound_form(content)
return None
@property
def parsed_media_types(self):
"""Return an list of all the media types that this view can parse."""
......
......@@ -3,10 +3,9 @@ from django.views.decorators.csrf import csrf_exempt
from djangorestframework.compat import View
from djangorestframework.emitters import EmitterMixin
from djangorestframework.validators import FormValidatorMixin
from djangorestframework.response import Response, ResponseException
from djangorestframework.request import RequestMixin, AuthMixin
from djangorestframework import emitters, parsers, authenticators, status
from djangorestframework import emitters, parsers, authenticators, validators, status
# TODO: Figure how out references and named urls need to work nicely
......@@ -17,7 +16,7 @@ from djangorestframework import emitters, parsers, authenticators, status
__all__ = ['Resource']
class Resource(EmitterMixin, AuthMixin, FormValidatorMixin, RequestMixin, View):
class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
"""Handles incoming requests and maps them to REST operations,
performing authentication, input deserialization, input validation, output serialization."""
......@@ -38,7 +37,10 @@ class Resource(EmitterMixin, AuthMixin, FormValidatorMixin, RequestMixin, View):
parsers = ( parsers.JSONParser,
parsers.FormParser,
parsers.MultipartParser )
# List of validators to validate, cleanup and type-ify the request content
validators = (validators.FormValidator,)
# List of all authenticating methods to attempt.
authenticators = ( authenticators.UserLoggedInAuthenticator,
authenticators.BasicAuthenticator )
......
......@@ -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))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment