diff options
| author | Tom Christie | 2011-05-16 14:11:36 +0100 |
|---|---|---|
| committer | Tom Christie | 2011-05-16 14:11:36 +0100 |
| commit | 1e04790d505a1174f9e3c4481288982f9e7fd6c0 (patch) | |
| tree | 09c5b29acdc820dc3fc1108f3503cde03d725eb9 /djangorestframework/resources.py | |
| parent | e92002ddde31fcc4ba3dee0f4a92a114c3c3a959 (diff) | |
| download | django-rest-framework-1e04790d505a1174f9e3c4481288982f9e7fd6c0.tar.bz2 | |
Fixing some of the last blocking issues
Diffstat (limited to 'djangorestframework/resources.py')
| -rw-r--r-- | djangorestframework/resources.py | 75 |
1 files changed, 66 insertions, 9 deletions
diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py index 19218b35..8a9373cf 100644 --- a/djangorestframework/resources.py +++ b/djangorestframework/resources.py @@ -5,6 +5,9 @@ from django.db.models.query import QuerySet from django.db.models.fields.related import RelatedField from django.utils.encoding import smart_unicode +from djangorestframework.response import ErrorResponse +from djangorestframework.utils import as_tuple + import decimal import inspect import re @@ -12,6 +15,10 @@ import re # TODO: _IgnoreFieldException +# Map model classes to resource classes +#_model_to_resource = {} + + def _model_to_dict(instance, resource=None): """ Given a model instance, return a ``dict`` representing the model. @@ -179,18 +186,31 @@ class FormResource(Resource): return self._validate(data, files) - def _validate(self, data, files, allowed_extra_fields=()): + def _validate(self, data, files, allowed_extra_fields=(), fake_data=None): """ 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. """ + + # We'd like nice error messages even if no content is supplied. + # Typically if an empty dict is given to a form Django will + # return .is_valid() == False, but .errors == {} + # + # To get around this case we revalidate with some fake data. + if fake_data: + data[fake_data] = '_fake_data' + allowed_extra_fields = allowed_extra_fields + ('_fake_data',) + bound_form = self.get_bound_form(data, files) if bound_form is None: return data self.view.bound_form_instance = bound_form + + data = data and data or {} + files = files and files or {} seen_fields_set = set(data.keys()) form_fields_set = set(bound_form.fields.keys()) @@ -198,6 +218,7 @@ class FormResource(Resource): # 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) + unknown_fields = unknown_fields - set(('csrfmiddlewaretoken',)) # Check using both regular validation, and our stricter no additional fields rule if bound_form.is_valid() and not unknown_fields: @@ -216,6 +237,13 @@ class FormResource(Resource): detail = {} if not bound_form.errors and not unknown_fields: + # is_valid() was False, but errors was empty. + # If we havn't already done so attempt revalidation with some fake data + # to force django to give us an errors dict. + if fake_data is None: + return self._validate(data, files, allowed_extra_fields, '_fake_data') + + # If we've already set fake_dict and we're still here, fallback gracefully. detail = {u'errors': [u'No content was supplied.']} else: @@ -256,12 +284,29 @@ class FormResource(Resource): return self.form() + +#class _RegisterModelResource(type): +# """ +# Auto register new ModelResource classes into ``_model_to_resource`` +# """ +# def __new__(cls, name, bases, dct): +# resource_cls = type.__new__(cls, name, bases, dct) +# model_cls = dct.get('model', None) +# if model_cls: +# _model_to_resource[model_cls] = resource_cls +# return resource_cls + + + class ModelResource(FormResource): """ Resource 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. """ - + + # Auto-register new ModelResource classes into _model_to_resource + #__metaclass__ = _RegisterModelResource + """ The form class that should be used for request validation. If set to ``None`` then the default model form validation will be used. @@ -314,7 +359,7 @@ class ModelResource(FormResource): return self._validate(data, files, allowed_extra_fields=self._property_fields_set) - def get_bound_form(self, content=None): + def get_bound_form(self, data=None, files=None): """ Given some content return a ``Form`` instance bound to that content. @@ -334,11 +379,11 @@ class ModelResource(FormResource): #fields = tuple(self._model_fields_set) # Instantiate the ModelForm as appropriate - if content and isinstance(content, models.Model): + if data and isinstance(data, models.Model): # Bound to an existing model instance return OnTheFlyModelForm(instance=content) - elif content is not None: - return OnTheFlyModelForm(content) + elif data is not None: + return OnTheFlyModelForm(data, files) return OnTheFlyModelForm() # Both form and model not set? Okay bruv, whatevs... @@ -364,7 +409,19 @@ class ModelResource(FormResource): # pattern = tuple_item[1] # Note: defaults = tuple_item[2] for django >= 1.3 for result, params in possibility: - instance_attrs = dict([ (param, getattr(instance, param)) for param in params if hasattr(instance, param) ]) + + #instance_attrs = dict([ (param, getattr(instance, param)) for param in params if hasattr(instance, param) ]) + + instance_attrs = {} + for param in params: + if not hasattr(instance, param): + continue + attr = getattr(instance, param) + if isinstance(attr, models.Model): + instance_attrs[param] = attr.pk + else: + instance_attrs[param] = attr + try: return reverse(self.view_callable[0], kwargs=instance_attrs) except NoReverseMatch: @@ -393,7 +450,7 @@ class ModelResource(FormResource): isinstance(getattr(self.model, attr, None), property) and not attr.startswith('_')) - if fields: + if self.fields: return property_fields & set(as_tuple(self.fields)) - return property_fields - set(as_tuple(self.exclude)) + return property_fields.union(set(as_tuple(self.include))) - set(as_tuple(self.exclude)) |
