diff options
| author | Ben Konrath | 2012-11-01 14:06:56 +0100 |
|---|---|---|
| committer | Ben Konrath | 2012-11-01 14:06:56 +0100 |
| commit | 9c82f9717e58f1bb250d5fd4b27619dbcbbd1f21 (patch) | |
| tree | e976854e6871a8b826e91d8eb16d9a139b90664f /rest_framework/serializers.py | |
| parent | c24997df3b943e5d7a3b2e101508e4b79ee82dc4 (diff) | |
| parent | 204db7bdaa59cd17f762d6cf0e6a8623c2cc9939 (diff) | |
| download | django-rest-framework-9c82f9717e58f1bb250d5fd4b27619dbcbbd1f21.tar.bz2 | |
Merge branch 'master' into restframework2-filter
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 128 |
1 files changed, 82 insertions, 46 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 06330017..3d134a74 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -3,6 +3,7 @@ import datetime import types from decimal import Decimal from django.db import models +from django.forms import widgets from django.utils.datastructures import SortedDict from rest_framework.compat import get_concrete_model from rest_framework.fields import * @@ -22,10 +23,6 @@ class SortedDictWithMetadata(SortedDict, DictWithMetadata): pass -class RecursionOccured(BaseException): - pass - - def _is_protected_type(obj): """ True if the object is a native datatype that does not need to @@ -33,10 +30,10 @@ def _is_protected_type(obj): """ return isinstance(obj, ( types.NoneType, - int, long, - datetime.datetime, datetime.date, datetime.time, - float, Decimal, - basestring) + int, long, + datetime.datetime, datetime.date, datetime.time, + float, Decimal, + basestring) ) @@ -73,7 +70,7 @@ class SerializerOptions(object): Meta class options for Serializer """ def __init__(self, meta): - self.nested = getattr(meta, 'nested', False) + self.depth = getattr(meta, 'depth', 0) self.fields = getattr(meta, 'fields', ()) self.exclude = getattr(meta, 'exclude', ()) @@ -92,7 +89,6 @@ class BaseSerializer(Field): self.parent = None self.root = None - self.stack = [] self.context = context or {} self.init_data = data @@ -151,14 +147,11 @@ class BaseSerializer(Field): def initialize(self, parent): """ Same behaviour as usual Field, except that we need to keep track - of state so that we can deal with handling maximum depth and recursion. + of state so that we can deal with handling maximum depth. """ super(BaseSerializer, self).initialize(parent) - self.stack = parent.stack[:] - if parent.opts.nested and not isinstance(parent.opts.nested, bool): - self.opts.nested = parent.opts.nested - 1 - else: - self.opts.nested = parent.opts.nested + if parent.opts.depth: + self.opts.depth = parent.opts.depth - 1 ##### # Methods to convert or revert from objects <--> primative representations. @@ -174,21 +167,13 @@ class BaseSerializer(Field): Core of serialization. Convert an object into a dictionary of serialized field values. """ - if obj in self.stack and not self.source == '*': - raise RecursionOccured() - self.stack.append(obj) - ret = self._dict_class() ret.fields = {} - fields = self.get_fields(serialize=True, obj=obj, nested=self.opts.nested) + fields = self.get_fields(serialize=True, obj=obj, nested=bool(self.opts.depth)) for field_name, field in fields.items(): key = self.get_field_key(field_name) - try: - value = field.field_to_native(obj, field_name) - except RecursionOccured: - field = self.get_fields(serialize=True, obj=obj, nested=False)[field_name] - value = field.field_to_native(obj, field_name) + value = field.field_to_native(obj, field_name) ret[key] = value ret.fields[key] = field return ret @@ -198,7 +183,7 @@ class BaseSerializer(Field): Core of deserialization, together with `restore_object`. Converts a dictionary of data into a dictionary of deserialized fields. """ - fields = self.get_fields(serialize=False, data=data, nested=self.opts.nested) + fields = self.get_fields(serialize=False, data=data, nested=bool(self.opts.depth)) reverted_data = {} for field_name, field in fields.items(): try: @@ -208,6 +193,35 @@ class BaseSerializer(Field): return reverted_data + def perform_validation(self, attrs): + """ + Run `validate_<fieldname>()` and `validate()` methods on the serializer + """ + # TODO: refactor this so we're not determining the fields again + fields = self.get_fields(serialize=False, data=attrs, nested=bool(self.opts.depth)) + + for field_name, field in fields.items(): + try: + validate_method = getattr(self, 'validate_%s' % field_name, None) + if validate_method: + source = field.source or field_name + attrs = validate_method(attrs, source) + except ValidationError as err: + self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) + + try: + attrs = self.validate(attrs) + except ValidationError as err: + self._errors['non_field_errors'] = err.messages + + return attrs + + def validate(self, attrs): + """ + Stub method, to be overridden in Serializer subclasses + """ + return attrs + def restore_object(self, attrs, instance=None): """ Deserialize a dictionary of attributes into an object instance. @@ -241,17 +255,31 @@ class BaseSerializer(Field): self._errors = {} if data is not None: attrs = self.restore_fields(data) + attrs = self.perform_validation(attrs) else: - self._errors['non_field_errors'] = 'No input provided' + self._errors['non_field_errors'] = ['No input provided'] if not self._errors: return self.restore_object(attrs, instance=getattr(self, 'object', None)) + def field_to_native(self, obj, field_name): + """ + Override default so that we can apply ModelSerializer as a nested + field to relationships. + """ + obj = getattr(obj, self.source or field_name) + + # If the object has an "all" method, assume it's a relationship + if is_simple_callable(getattr(obj, 'all', None)): + return [self.to_native(item) for item in obj.all()] + + return self.to_native(obj) + @property def errors(self): """ Run deserialization and return error data, - setting self.object if no errors occured. + setting self.object if no errors occurred. """ if self._errors is None: obj = self.from_native(self.init_data) @@ -295,16 +323,6 @@ class ModelSerializer(Serializer): """ _options_class = ModelSerializerOptions - def field_to_native(self, obj, field_name): - """ - Override default so that we can apply ModelSerializer as a nested - field to relationships. - """ - obj = getattr(obj, self.source or field_name) - if obj.__class__.__name__ in ('RelatedManager', 'ManyRelatedManager'): - return [self.to_native(item) for item in obj.all()] - return self.to_native(obj) - def default_fields(self, serialize, obj=None, data=None, nested=False): """ Return all the fields that should be serialized for the model. @@ -374,25 +392,43 @@ class ModelSerializer(Serializer): """ Creates a default instance of a basic non-relational field. """ + kwargs = {} + + kwargs['blank'] = model_field.blank + + if model_field.null: + kwargs['required'] = False + + if model_field.has_default(): + kwargs['required'] = False + kwargs['default'] = model_field.get_default() + + if model_field.__class__ == models.TextField: + kwargs['widget'] = widgets.Textarea + + # TODO: TypedChoiceField? + if model_field.flatchoices: # This ModelField contains choices + kwargs['choices'] = model_field.flatchoices + return ChoiceField(**kwargs) + field_mapping = { models.FloatField: FloatField, models.IntegerField: IntegerField, + models.PositiveIntegerField: IntegerField, + models.SmallIntegerField: IntegerField, + models.PositiveSmallIntegerField: IntegerField, models.DateTimeField: DateTimeField, models.DateField: DateField, models.EmailField: EmailField, models.CharField: CharField, + models.TextField: CharField, models.CommaSeparatedIntegerField: CharField, models.BooleanField: BooleanField, } try: - ret = field_mapping[model_field.__class__]() + return field_mapping[model_field.__class__](**kwargs) except KeyError: - ret = ModelField(model_field=model_field) - - if model_field.default: - ret.required = False - - return ret + return ModelField(model_field=model_field, **kwargs) def restore_object(self, attrs, instance=None): """ |
