From 4b691c402707775c3048a90531024f3bc5be6f91 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Sep 2012 13:06:27 +0100 Subject: Change package name: djangorestframework -> rest_framework --- rest_framework/serializers.py | 348 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 rest_framework/serializers.py (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py new file mode 100644 index 00000000..afc9f511 --- /dev/null +++ b/rest_framework/serializers.py @@ -0,0 +1,348 @@ +from decimal import Decimal +from django.core.serializers.base import DeserializedObject +from django.utils.datastructures import SortedDict +import copy +import datetime +import types +from rest_framework.fields import * + + +class DictWithMetadata(dict): + """ + A dict-like object, that can have additional properties attached. + """ + pass + + +class SortedDictWithMetadata(SortedDict, DictWithMetadata): + """ + A sorted dict-like object, that can have additional properties attached. + """ + pass + + +class RecursionOccured(BaseException): + pass + + +def _is_protected_type(obj): + """ + True if the object is a native datatype that does not need to + be serialized further. + """ + return isinstance(obj, ( + types.NoneType, + int, long, + datetime.datetime, datetime.date, datetime.time, + float, Decimal, + basestring) + ) + + +def _get_declared_fields(bases, attrs): + """ + Create a list of serializer field instances from the passed in 'attrs', + plus any fields on the base classes (in 'bases'). + + Note that all fields from the base classes are used. + """ + fields = [(field_name, attrs.pop(field_name)) + for field_name, obj in attrs.items() + if isinstance(obj, Field)] + fields.sort(key=lambda x: x[1].creation_counter) + + # If this class is subclassing another Serializer, add that Serializer's + # fields. Note that we loop over the bases in *reverse*. This is necessary + # in order to the correct order of fields. + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + + return SortedDict(fields) + + +class SerializerMetaclass(type): + def __new__(cls, name, bases, attrs): + attrs['base_fields'] = _get_declared_fields(bases, attrs) + return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) + + +class SerializerOptions(object): + """ + Meta class options for ModelSerializer + """ + def __init__(self, meta): + self.nested = getattr(meta, 'nested', False) + self.fields = getattr(meta, 'fields', ()) + self.exclude = getattr(meta, 'exclude', ()) + + +class BaseSerializer(Field): + class Meta(object): + pass + + _options_class = SerializerOptions + _dict_class = SortedDictWithMetadata # Set to unsorted dict for backwards compatability with unsorted implementations. + + def __init__(self, data=None, instance=None, context=None, **kwargs): + super(BaseSerializer, self).__init__(**kwargs) + self.fields = copy.deepcopy(self.base_fields) + self.opts = self._options_class(self.Meta) + self.parent = None + self.root = None + + self.stack = [] + self.context = context or {} + + self.init_data = data + self.instance = instance + + self._data = None + self._errors = None + + ##### + # Methods to determine which fields to use when (de)serializing objects. + + def default_fields(self, serialize, obj=None, data=None, nested=False): + """ + Return the complete set of default fields for the object, as a dict. + """ + return {} + + def get_fields(self, serialize, obj=None, data=None, nested=False): + """ + Returns the complete set of fields for the object as a dict. + + This will be the set of any explicitly declared fields, + plus the set of fields returned by get_default_fields(). + """ + ret = SortedDict() + + # Get the explicitly declared fields + for key, field in self.fields.items(): + ret[key] = field + # Determine if the declared field corrosponds to a model field. + try: + if key == 'pk': + model_field = obj._meta.pk + else: + model_field = obj._meta.get_field_by_name(key)[0] + except: + model_field = None + # Set up the field + field.initialize(parent=self, model_field=model_field) + + # Add in the default fields + fields = self.default_fields(serialize, obj, data, nested) + for key, val in fields.items(): + if key not in ret: + ret[key] = val + + # If 'fields' is specified, use those fields, in that order. + if self.opts.fields: + new = SortedDict() + for key in self.opts.fields: + new[key] = ret[key] + ret = new + + # Remove anything in 'exclude' + if self.opts.exclude: + for key in self.opts.exclude: + ret.pop(key, None) + + return ret + + ##### + # Field methods - used when the serializer class is itself used as a field. + + def initialize(self, parent, model_field=None): + """ + 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. + """ + super(BaseSerializer, self).initialize(parent, model_field) + 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 + + ##### + # Methods to convert or revert from objects <--> primative representations. + + def get_field_key(self, field_name): + """ + Return the key that should be used for a given field. + """ + return field_name + + def convert_object(self, obj): + """ + 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) + 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) + ret[key] = value + ret.fields[key] = field + return ret + + def restore_fields(self, data): + """ + 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) + reverted_data = {} + for field_name, field in fields.items(): + try: + field.field_from_native(data, field_name, reverted_data) + except ValidationError as err: + self._errors[field_name] = list(err.messages) + + return reverted_data + + def restore_object(self, attrs, instance=None): + """ + Deserialize a dictionary of attributes into an object instance. + You should override this method to control how deserialized objects + are instantiated. + """ + if instance is not None: + instance.update(attrs) + return instance + return attrs + + def to_native(self, obj): + """ + Serialize objects -> primatives. + """ + if isinstance(obj, dict): + return dict([(key, self.to_native(val)) + for (key, val) in obj.items()]) + elif hasattr(obj, '__iter__'): + return (self.to_native(item) for item in obj) + return self.convert_object(obj) + + def from_native(self, data): + """ + Deserialize primatives -> objects. + """ + if hasattr(data, '__iter__') and not isinstance(data, dict): + # TODO: error data when deserializing lists + return (self.from_native(item) for item in data) + self._errors = {} + attrs = self.restore_fields(data) + if not self._errors: + return self.restore_object(attrs, instance=getattr(self, 'instance', None)) + + @property + def errors(self): + """ + Run deserialization and return error data, + setting self.object if no errors occured. + """ + if self._errors is None: + obj = self.from_native(self.init_data) + if not self._errors: + self.object = obj + return self._errors + + def is_valid(self): + return not self.errors + + @property + def data(self): + if self._data is None: + self._data = self.to_native(self.instance) + return self._data + + +class Serializer(BaseSerializer): + __metaclass__ = SerializerMetaclass + + +class ModelSerializerOptions(SerializerOptions): + """ + Meta class options for ModelSerializer + """ + def __init__(self, meta): + super(ModelSerializerOptions, self).__init__(meta) + self.model = getattr(meta, 'model', None) + + +class ModelSerializer(RelatedField, Serializer): + """ + A serializer that deals with model instances and querysets. + """ + _options_class = ModelSerializerOptions + + def default_fields(self, serialize, obj=None, data=None, nested=False): + """ + Return all the fields that should be serialized for the model. + """ + if serialize: + cls = obj.__class__ + else: + cls = self.opts.model + + opts = cls._meta.concrete_model._meta + pk_field = opts.pk + while pk_field.rel: + pk_field = pk_field.rel.to._meta.pk + fields = [pk_field] + fields += [field for field in opts.fields if field.serialize] + fields += [field for field in opts.many_to_many if field.serialize] + + ret = SortedDict() + for model_field in fields: + if model_field.rel and nested: + field = self.get_nested_field(model_field) + elif model_field.rel: + field = self.get_related_field(model_field) + else: + field = self.get_field(model_field) + field.initialize(parent=self, model_field=model_field) + ret[model_field.name] = field + return ret + + def get_nested_field(self, model_field): + """ + Creates a default instance of a nested relational field. + """ + return ModelSerializer() + + def get_related_field(self, model_field): + """ + Creates a default instance of a flat relational field. + """ + return PrimaryKeyRelatedField() + + def get_field(self, model_field): + """ + Creates a default instance of a basic field. + """ + return Field() + + def restore_object(self, attrs, instance=None): + """ + Restore the model instance. + """ + m2m_data = {} + for field in self.opts.model._meta.many_to_many: + if field.name in attrs: + m2m_data[field.name] = attrs.pop(field.name) + return DeserializedObject(self.opts.model(**attrs), m2m_data) -- cgit v1.2.3 From d9cba6398e2323b8d8cd34f791442528517e01b4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Sep 2012 17:44:34 +0100 Subject: Clean up bits of templates etc --- rest_framework/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index afc9f511..96e46d94 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1,9 +1,9 @@ -from decimal import Decimal -from django.core.serializers.base import DeserializedObject -from django.utils.datastructures import SortedDict import copy import datetime import types +from decimal import Decimal +from django.core.serializers.base import DeserializedObject +from django.utils.datastructures import SortedDict from rest_framework.fields import * -- cgit v1.2.3 From e0913e29b846a5b6aedc08ef8243ce62e80ecbfd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Sep 2012 13:20:12 +0100 Subject: Fix some bits of serialization --- rest_framework/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 96e46d94..4d5958d2 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -95,7 +95,7 @@ class BaseSerializer(Field): self.context = context or {} self.init_data = data - self.instance = instance + self.object = instance self._data = None self._errors = None @@ -247,7 +247,7 @@ class BaseSerializer(Field): self._errors = {} attrs = self.restore_fields(data) if not self._errors: - return self.restore_object(attrs, instance=getattr(self, 'instance', None)) + return self.restore_object(attrs, instance=getattr(self, 'object', None)) @property def errors(self): @@ -267,7 +267,7 @@ class BaseSerializer(Field): @property def data(self): if self._data is None: - self._data = self.to_native(self.instance) + self._data = self.to_native(self.object) return self._data -- cgit v1.2.3 From e003cc91b627a64b6d4d364f9de936ed1bec03e1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 28 Sep 2012 14:28:50 +0100 Subject: Get test-only models properly working --- rest_framework/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4d5958d2..986d4225 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -114,7 +114,7 @@ class BaseSerializer(Field): Returns the complete set of fields for the object as a dict. This will be the set of any explicitly declared fields, - plus the set of fields returned by get_default_fields(). + plus the set of fields returned by default_fields(). """ ret = SortedDict() @@ -234,7 +234,7 @@ class BaseSerializer(Field): return dict([(key, self.to_native(val)) for (key, val) in obj.items()]) elif hasattr(obj, '__iter__'): - return (self.to_native(item) for item in obj) + return [self.to_native(item) for item in obj] return self.convert_object(obj) def from_native(self, data): -- cgit v1.2.3 From 4ebd701be74fbb1f44f7763f7ab9e19f6483ac96 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 28 Sep 2012 14:53:22 +0100 Subject: Fix serialization issue with Django 1.3 --- rest_framework/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 986d4225..bdb0f10a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -4,6 +4,7 @@ import types from decimal import Decimal from django.core.serializers.base import DeserializedObject from django.utils.datastructures import SortedDict +from rest_framework.compat import get_concrete_model from rest_framework.fields import * @@ -299,7 +300,7 @@ class ModelSerializer(RelatedField, Serializer): else: cls = self.opts.model - opts = cls._meta.concrete_model._meta + opts = get_concrete_model(cls)._meta pk_field = opts.pk while pk_field.rel: pk_field = pk_field.rel.to._meta.pk -- cgit v1.2.3 From 9f71f8e618555d888fff8edd322c440b49f331bc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 28 Sep 2012 15:54:00 +0100 Subject: Fix broken bits of generic views --- rest_framework/serializers.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index bdb0f10a..9cbdb9de 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -231,6 +231,9 @@ class BaseSerializer(Field): """ Serialize objects -> primatives. """ + if isinstance(obj, DeserializedObject): + obj = obj.object + if isinstance(obj, dict): return dict([(key, self.to_native(val)) for (key, val) in obj.items()]) @@ -295,11 +298,7 @@ class ModelSerializer(RelatedField, Serializer): """ Return all the fields that should be serialized for the model. """ - if serialize: - cls = obj.__class__ - else: - cls = self.opts.model - + cls = self.opts.model opts = get_concrete_model(cls)._meta pk_field = opts.pk while pk_field.rel: @@ -342,6 +341,11 @@ class ModelSerializer(RelatedField, Serializer): """ Restore the model instance. """ + if instance: + for key, val in attrs.items(): + setattr(instance, key, val) + return DeserializedObject(instance) + m2m_data = {} for field in self.opts.model._meta.many_to_many: if field.name in attrs: -- cgit v1.2.3 From b16fb5777168246b1e217640b818a82eb6e2141b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 1 Oct 2012 15:49:19 +0100 Subject: Expand pagination support, add docs --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 9cbdb9de..bb48e381 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -70,7 +70,7 @@ class SerializerMetaclass(type): class SerializerOptions(object): """ - Meta class options for ModelSerializer + Meta class options for Serializer """ def __init__(self, meta): self.nested = getattr(meta, 'nested', False) -- cgit v1.2.3 From ab173fd8f9070ccdb70f86f400d2ffa780977ce4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 2 Oct 2012 15:37:13 +0100 Subject: Fix bug where pk could be set in post data --- rest_framework/serializers.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index bb48e381..d0d34094 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -308,17 +308,31 @@ class ModelSerializer(RelatedField, Serializer): fields += [field for field in opts.many_to_many if field.serialize] ret = SortedDict() + is_pk = True # First field in the list is the pk + for model_field in fields: - if model_field.rel and nested: + if is_pk: + field = self.get_pk_field(model_field) + is_pk = False + elif model_field.rel and nested: field = self.get_nested_field(model_field) elif model_field.rel: field = self.get_related_field(model_field) else: field = self.get_field(model_field) - field.initialize(parent=self, model_field=model_field) - ret[model_field.name] = field + + if field is not None: + field.initialize(parent=self, model_field=model_field) + ret[model_field.name] = field + return ret + def get_pk_field(self, model_field): + """ + Returns a default instance of the pk field. + """ + return Field(readonly=True) + def get_nested_field(self, model_field): """ Creates a default instance of a nested relational field. @@ -333,7 +347,7 @@ class ModelSerializer(RelatedField, Serializer): def get_field(self, model_field): """ - Creates a default instance of a basic field. + Creates a default instance of a basic non-relational field. """ return Field() -- cgit v1.2.3 From d1b99f350aded62fe480f7dc4749cd63d52715d2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 2 Oct 2012 16:16:49 +0100 Subject: Added model form field -> serializer form field mapping --- rest_framework/serializers.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d0d34094..a2f211ab 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.core.serializers.base import DeserializedObject +from django.db import models from django.utils.datastructures import SortedDict from rest_framework.compat import get_concrete_model from rest_framework.fields import * @@ -349,7 +350,20 @@ class ModelSerializer(RelatedField, Serializer): """ Creates a default instance of a basic non-relational field. """ - return Field() + field_mapping = dict([ + [models.FloatField, FloatField], + [models.IntegerField, IntegerField], + [models.DateTimeField, DateTimeField], + [models.DateField, DateField], + [models.EmailField, EmailField], + [models.CharField, CharField], + [models.CommaSeparatedIntegerField, CharField], + [models.BooleanField, BooleanField] + ]) + try: + return field_mapping[model_field.__class__]() + except KeyError: + return Field() def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From c30e0795bebd9980a66ae7db1a0d8c43f77d4c11 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Oct 2012 09:26:15 +0100 Subject: Rename generic views --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index a2f211ab..83ec1470 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -322,7 +322,7 @@ class ModelSerializer(RelatedField, Serializer): else: field = self.get_field(model_field) - if field is not None: + if field: field.initialize(parent=self, model_field=model_field) ret[model_field.name] = field -- cgit v1.2.3 From 1a05942166abfc68f83caea535aa44733b1e37a9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Oct 2012 09:45:27 +0100 Subject: Stopgap fix for model fields rendering in browseable API --- rest_framework/serializers.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 83ec1470..5935bce5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -350,16 +350,16 @@ class ModelSerializer(RelatedField, Serializer): """ Creates a default instance of a basic non-relational field. """ - field_mapping = dict([ - [models.FloatField, FloatField], - [models.IntegerField, IntegerField], - [models.DateTimeField, DateTimeField], - [models.DateField, DateField], - [models.EmailField, EmailField], - [models.CharField, CharField], - [models.CommaSeparatedIntegerField, CharField], - [models.BooleanField, BooleanField] - ]) + field_mapping = { + models.FloatField: FloatField, + models.IntegerField: IntegerField, + models.DateTimeField: DateTimeField, + models.DateField: DateField, + models.EmailField: EmailField, + models.CharField: CharField, + models.CommaSeparatedIntegerField: CharField, + models.BooleanField: BooleanField + } try: return field_mapping[model_field.__class__]() except KeyError: -- cgit v1.2.3 From 7c303411a914430252f482ca24501bf02ba7193c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Oct 2012 11:08:02 +0100 Subject: Add Serializer.save() to clean up differences between serializer.object being either a DeserializedObject or a model instance --- rest_framework/serializers.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5935bce5..683b9efc 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -275,6 +275,13 @@ class BaseSerializer(Field): self._data = self.to_native(self.object) return self._data + def save(self): + """ + Save the deserialized object and return it. + """ + self.object.save() + return self.object + class Serializer(BaseSerializer): __metaclass__ = SerializerMetaclass @@ -379,3 +386,10 @@ class ModelSerializer(RelatedField, Serializer): if field.name in attrs: m2m_data[field.name] = attrs.pop(field.name) return DeserializedObject(self.opts.model(**attrs), m2m_data) + + def save(self): + """ + Save the deserialized object and return it. + """ + self.object.save() + return self.object.object -- cgit v1.2.3 From 58c1263267e5947f8243568edb33273effdc2787 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Oct 2012 12:16:30 +0100 Subject: Use either PrimaryKeyRelatedField or ManyPrimaryKeyRelatedField as appropriate (fixes test) --- rest_framework/serializers.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 683b9efc..d3ae9b8a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -351,6 +351,8 @@ class ModelSerializer(RelatedField, Serializer): """ Creates a default instance of a flat relational field. """ + if isinstance(model_field, models.fields.related.ManyToManyField): + return ManyPrimaryKeyRelatedField() return PrimaryKeyRelatedField() def get_field(self, model_field): -- cgit v1.2.3 From a366d6e61261b9050c85a76d26ccf1544f165486 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Oct 2012 16:08:20 +0100 Subject: M2M fields supported --- rest_framework/serializers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d3ae9b8a..03763824 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -351,9 +351,10 @@ class ModelSerializer(RelatedField, Serializer): """ Creates a default instance of a flat relational field. """ + queryset = model_field.rel.to._default_manager # .using(db).complex_filter(self.rel.limit_choices_to) if isinstance(model_field, models.fields.related.ManyToManyField): - return ManyPrimaryKeyRelatedField() - return PrimaryKeyRelatedField() + return ManyPrimaryKeyRelatedField(queryset=queryset) + return PrimaryKeyRelatedField(queryset=queryset) def get_field(self, model_field): """ @@ -367,7 +368,7 @@ class ModelSerializer(RelatedField, Serializer): models.EmailField: EmailField, models.CharField: CharField, models.CommaSeparatedIntegerField: CharField, - models.BooleanField: BooleanField + models.BooleanField: BooleanField, } try: return field_mapping[model_field.__class__]() -- cgit v1.2.3 From d89d6887d2eb8293348cb1a7a043a05352819cb8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Oct 2012 11:26:41 +0100 Subject: HyperlinkedModelSerializer with working HyperlinkedIdentityField, but no hyperlinked relations --- rest_framework/serializers.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 03763824..990a4f9a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -396,3 +396,40 @@ class ModelSerializer(RelatedField, Serializer): """ self.object.save() return self.object.object + + +class HyperlinkedModelSerializerOptions(ModelSerializerOptions): + """ + Options for HyperlinkedModelSerializer + """ + def __init__(self, meta): + super(HyperlinkedModelSerializerOptions, self).__init__(meta) + self.view_name = getattr(meta, 'view_name', None) + + +class HyperlinkedModelSerializer(ModelSerializer): + """ + """ + _options_class = HyperlinkedModelSerializerOptions + _default_view_name = '%(model_name)s-detail' + + url = HyperlinkedIdentityField() + + def __init__(self, *args, **kwargs): + super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs) + if self.opts.view_name is None: + self.opts.view_name = self._get_default_view_name() + + def _get_default_view_name(self): + """ + Return the view name to use if 'view_name' is not specified in 'Meta' + """ + model_meta = self.opts.model._meta + format_kwargs = { + 'app_label': model_meta.app_label, + 'model_name': model_meta.object_name.lower() + } + return self._default_view_name % format_kwargs + + def get_pk_field(self, model_field): + return None -- cgit v1.2.3 From 3a06dde8848dd18810b04db4b7dcb5f8bd768c29 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Oct 2012 13:28:14 +0100 Subject: Clean up field classes --- rest_framework/serializers.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 990a4f9a..ae0b3cdf 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -123,16 +123,8 @@ class BaseSerializer(Field): # Get the explicitly declared fields for key, field in self.fields.items(): ret[key] = field - # Determine if the declared field corrosponds to a model field. - try: - if key == 'pk': - model_field = obj._meta.pk - else: - model_field = obj._meta.get_field_by_name(key)[0] - except: - model_field = None # Set up the field - field.initialize(parent=self, model_field=model_field) + field.initialize(parent=self) # Add in the default fields fields = self.default_fields(serialize, obj, data, nested) @@ -157,12 +149,12 @@ class BaseSerializer(Field): ##### # Field methods - used when the serializer class is itself used as a field. - def initialize(self, parent, model_field=None): + 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. """ - super(BaseSerializer, self).initialize(parent, model_field) + 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 @@ -296,12 +288,22 @@ class ModelSerializerOptions(SerializerOptions): self.model = getattr(meta, 'model', None) -class ModelSerializer(RelatedField, Serializer): +class ModelSerializer(Serializer): """ A serializer that deals with model instances and querysets. """ _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. @@ -330,7 +332,7 @@ class ModelSerializer(RelatedField, Serializer): field = self.get_field(model_field) if field: - field.initialize(parent=self, model_field=model_field) + field.initialize(parent=self) ret[model_field.name] = field return ret @@ -339,7 +341,7 @@ class ModelSerializer(RelatedField, Serializer): """ Returns a default instance of the pk field. """ - return Field(readonly=True) + return Field() def get_nested_field(self, model_field): """ @@ -373,7 +375,7 @@ class ModelSerializer(RelatedField, Serializer): try: return field_mapping[model_field.__class__]() except KeyError: - return Field() + return ModelField(model_field=model_field) def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From aac6b6cf4e61814e392829b1101ace4789bb0871 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Oct 2012 15:00:23 +0100 Subject: Tweak comment --- rest_framework/serializers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ae0b3cdf..4ffff65d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -353,7 +353,9 @@ class ModelSerializer(Serializer): """ Creates a default instance of a flat relational field. """ - queryset = model_field.rel.to._default_manager # .using(db).complex_filter(self.rel.limit_choices_to) + # TODO: filter queryset using: + # .using(db).complex_filter(self.rel.limit_choices_to) + queryset = model_field.rel.to._default_manager if isinstance(model_field, models.fields.related.ManyToManyField): return ManyPrimaryKeyRelatedField(queryset=queryset) return PrimaryKeyRelatedField(queryset=queryset) -- cgit v1.2.3 From c91d926b0664981de0fd239a4398dd71367a5911 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 4 Oct 2012 16:58:18 +0100 Subject: Initial tests for hyperlinked relationships --- rest_framework/serializers.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4ffff65d..ba8bf8ad 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -422,13 +422,13 @@ class HyperlinkedModelSerializer(ModelSerializer): def __init__(self, *args, **kwargs): super(HyperlinkedModelSerializer, self).__init__(*args, **kwargs) if self.opts.view_name is None: - self.opts.view_name = self._get_default_view_name() + self.opts.view_name = self._get_default_view_name(self.opts.model) - def _get_default_view_name(self): + def _get_default_view_name(self, model): """ Return the view name to use if 'view_name' is not specified in 'Meta' """ - model_meta = self.opts.model._meta + model_meta = model._meta format_kwargs = { 'app_label': model_meta.app_label, 'model_name': model_meta.object_name.lower() @@ -437,3 +437,19 @@ class HyperlinkedModelSerializer(ModelSerializer): def get_pk_field(self, model_field): return None + + def get_related_field(self, model_field): + """ + Creates a default instance of a flat relational field. + """ + # TODO: filter queryset using: + # .using(db).complex_filter(self.rel.limit_choices_to) + rel = model_field.rel.to + queryset = rel._default_manager + kwargs = { + 'queryset': queryset, + 'view_name': self._get_default_view_name(rel) + } + if isinstance(model_field, models.fields.related.ManyToManyField): + return ManyHyperlinkedRelatedField(**kwargs) + return HyperlinkedRelatedField(**kwargs) -- cgit v1.2.3 From 52ba2e333375c6829fb89b6b43e4d19b2f2a86a4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 8 Oct 2012 12:52:56 +0100 Subject: Fix #285 --- rest_framework/serializers.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ba8bf8ad..1770c4ce 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -2,7 +2,6 @@ import copy import datetime import types from decimal import Decimal -from django.core.serializers.base import DeserializedObject from django.db import models from django.utils.datastructures import SortedDict from rest_framework.compat import get_concrete_model @@ -224,9 +223,6 @@ class BaseSerializer(Field): """ Serialize objects -> primatives. """ - if isinstance(obj, DeserializedObject): - obj = obj.object - if isinstance(obj, dict): return dict([(key, self.to_native(val)) for (key, val) in obj.items()]) @@ -383,23 +379,30 @@ class ModelSerializer(Serializer): """ Restore the model instance. """ + self.m2m_data = {} + if instance: for key, val in attrs.items(): setattr(instance, key, val) - return DeserializedObject(instance) + return instance - m2m_data = {} for field in self.opts.model._meta.many_to_many: if field.name in attrs: - m2m_data[field.name] = attrs.pop(field.name) - return DeserializedObject(self.opts.model(**attrs), m2m_data) + self.m2m_data[field.name] = attrs.pop(field.name) + return self.opts.model(**attrs) - def save(self): + def save(self, save_m2m=True): """ Save the deserialized object and return it. """ self.object.save() - return self.object.object + + if self.m2m_data and save_m2m: + for accessor_name, object_list in self.m2m_data.items(): + setattr(self.object, accessor_name, object_list) + self.m2m_data = {} + + return self.object class HyperlinkedModelSerializerOptions(ModelSerializerOptions): -- cgit v1.2.3 From 65f592866c5cd5103e99ed453543807bcbdaa9da Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 8 Oct 2012 17:53:18 +0100 Subject: Fix issue where required fields were not being properly validated. Also make model fields with a default value be not required --- rest_framework/serializers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 1770c4ce..2141619f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -371,9 +371,14 @@ class ModelSerializer(Serializer): models.BooleanField: BooleanField, } try: - return field_mapping[model_field.__class__]() + ret = field_mapping[model_field.__class__]() except KeyError: - return ModelField(model_field=model_field) + ret = ModelField(model_field=model_field) + + if model_field.default: + ret.required = False + + return ret def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From 9bbc1cc403e3cf171710ae02255e2b7e6185f823 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 9 Oct 2012 17:49:04 +0100 Subject: Add flag in get_related_field --- rest_framework/serializers.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2141619f..0faad703 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -323,7 +323,9 @@ class ModelSerializer(Serializer): elif model_field.rel and nested: field = self.get_nested_field(model_field) elif model_field.rel: - field = self.get_related_field(model_field) + to_many = isinstance(model_field, + models.fields.related.ManyToManyField) + field = self.get_related_field(model_field, to_many=to_many) else: field = self.get_field(model_field) @@ -345,14 +347,14 @@ class ModelSerializer(Serializer): """ return ModelSerializer() - def get_related_field(self, model_field): + def get_related_field(self, model_field, to_many=False): """ Creates a default instance of a flat relational field. """ # TODO: filter queryset using: # .using(db).complex_filter(self.rel.limit_choices_to) queryset = model_field.rel.to._default_manager - if isinstance(model_field, models.fields.related.ManyToManyField): + if to_many: return ManyPrimaryKeyRelatedField(queryset=queryset) return PrimaryKeyRelatedField(queryset=queryset) @@ -446,7 +448,7 @@ class HyperlinkedModelSerializer(ModelSerializer): def get_pk_field(self, model_field): return None - def get_related_field(self, model_field): + def get_related_field(self, model_field, to_many): """ Creates a default instance of a flat relational field. """ @@ -458,6 +460,6 @@ class HyperlinkedModelSerializer(ModelSerializer): 'queryset': queryset, 'view_name': self._get_default_view_name(rel) } - if isinstance(model_field, models.fields.related.ManyToManyField): + if to_many: return ManyHyperlinkedRelatedField(**kwargs) return HyperlinkedRelatedField(**kwargs) -- cgit v1.2.3 From a3ab66eca4be63ae35ae0c7a45267c9458fd7fcf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 9 Oct 2012 17:51:32 +0100 Subject: Add 'to_many' flag in get_related_field --- rest_framework/serializers.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 0faad703..5c91fee3 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -304,6 +304,13 @@ class ModelSerializer(Serializer): """ Return all the fields that should be serialized for the model. """ + # TODO: Modfiy this so that it's called on init, and drop + # serialize/obj/data arguments. + # + # We *could* provide a hook for dynamic fields, but + # it'd be nice if the default was to generate fields statically + # at the point of __init__ + cls = self.opts.model opts = get_concrete_model(cls)._meta pk_field = opts.pk -- cgit v1.2.3 From 83f39b3dce4028ff6b2ebe0be55c2a00d67ede00 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 10 Oct 2012 16:41:08 +0100 Subject: Don't fail when no data provided --- rest_framework/serializers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5c91fee3..06330017 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -237,8 +237,13 @@ class BaseSerializer(Field): if hasattr(data, '__iter__') and not isinstance(data, dict): # TODO: error data when deserializing lists return (self.from_native(item) for item in data) + self._errors = {} - attrs = self.restore_fields(data) + if data is not None: + attrs = self.restore_fields(data) + else: + self._errors['non_field_errors'] = 'No input provided' + if not self._errors: return self.restore_object(attrs, instance=getattr(self, 'object', None)) -- cgit v1.2.3 From 241be38340dcea9a49ce741ba844171ce02db2bd Mon Sep 17 00:00:00 2001 From: Jens Alm Date: Mon, 15 Oct 2012 09:14:01 +0200 Subject: Added TextField to recognized fields --- rest_framework/serializers.py | 1 + 1 file changed, 1 insertion(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 06330017..0f19956e 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -381,6 +381,7 @@ class ModelSerializer(Serializer): models.DateField: DateField, models.EmailField: EmailField, models.CharField: CharField, + models.TextField: TextField, models.CommaSeparatedIntegerField: CharField, models.BooleanField: BooleanField, } -- cgit v1.2.3 From 36cc56bc9d49832ca990ba8568903966f46a2938 Mon Sep 17 00:00:00 2001 From: Jens Alm Date: Mon, 15 Oct 2012 10:06:50 +0200 Subject: Added tests for TextField --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 0f19956e..5afbced2 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -381,7 +381,7 @@ class ModelSerializer(Serializer): models.DateField: DateField, models.EmailField: EmailField, models.CharField: CharField, - models.TextField: TextField, + models.TextField: TextField, models.CommaSeparatedIntegerField: CharField, models.BooleanField: BooleanField, } -- cgit v1.2.3 From c94272650915eef368cdc5d157644884c3eecccb Mon Sep 17 00:00:00 2001 From: Jens Alm Date: Mon, 15 Oct 2012 13:46:44 +0200 Subject: Added docs, integer fields and refactored models.TextField to use CharField I realized that per the django forms, there is no need for a separate TextField, an unlimited CharField is perfectly good. Also added default field for the different IntegerField types --- rest_framework/serializers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5afbced2..13f8cde2 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -377,11 +377,14 @@ class ModelSerializer(Serializer): 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: TextField, + models.TextField: CharField, models.CommaSeparatedIntegerField: CharField, models.BooleanField: BooleanField, } -- cgit v1.2.3 From 38673c35d4aa5487e175ac7c917c66c45ddb6ba4 Mon Sep 17 00:00:00 2001 From: Rob Dobson Date: Wed, 17 Oct 2012 19:12:34 +0100 Subject: Make default field check safe for boolean values whereby 'False' may be an acceptable default value --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 13f8cde2..6724bbdf 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -393,7 +393,7 @@ class ModelSerializer(Serializer): except KeyError: ret = ModelField(model_field=model_field) - if model_field.default: + if model_field.default is not None: ret.required = False return ret -- cgit v1.2.3 From 45d4622f090f8d81a04b4d3e888017419676bbc0 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Mon, 22 Oct 2012 15:12:25 +0100 Subject: Fix serialization of reverse relationships --- rest_framework/serializers.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 6724bbdf..221cbf2f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -247,6 +247,19 @@ class BaseSerializer(Field): 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): """ @@ -295,16 +308,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. -- cgit v1.2.3 From 51fae73f3d565e2702c72ff9841cc072d6490804 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 09:28:10 +0100 Subject: Implement per-field validation on Serializers --- rest_framework/serializers.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 221cbf2f..c9c4faa3 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -208,6 +208,23 @@ class BaseSerializer(Field): return reverted_data + def clean_fields(self, data): + """ + Run clean_ validators on the serializer + """ + fields = self.get_fields(serialize=False, data=data, nested=self.opts.nested) + + for field_name, field in fields.items(): + try: + clean_method = getattr(self, 'clean_%s' % field_name, None) + if clean_method: + source = field.source or field_name + data = clean_method(data, source) + except ValidationError as err: + self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) + + return data + def restore_object(self, attrs, instance=None): """ Deserialize a dictionary of attributes into an object instance. @@ -241,6 +258,7 @@ class BaseSerializer(Field): self._errors = {} if data is not None: attrs = self.restore_fields(data) + attrs = self.clean_fields(attrs) else: self._errors['non_field_errors'] = 'No input provided' -- cgit v1.2.3 From 388a807f64f60d84556288e2ade4f0fe57a8e66b Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 11:27:01 +0100 Subject: Switch from clean_ to validate_, clarify documentation --- rest_framework/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c9c4faa3..802ca55f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -210,13 +210,13 @@ class BaseSerializer(Field): def clean_fields(self, data): """ - Run clean_ validators on the serializer + Run validate_ methods on the serializer """ fields = self.get_fields(serialize=False, data=data, nested=self.opts.nested) for field_name, field in fields.items(): try: - clean_method = getattr(self, 'clean_%s' % field_name, None) + clean_method = getattr(self, 'validate_%s' % field_name, None) if clean_method: source = field.source or field_name data = clean_method(data, source) -- cgit v1.2.3 From ac2d39892d6b3fbbe5cd53b9ef83367249ba4880 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 11:39:17 +0100 Subject: Add cross-field validate method --- rest_framework/serializers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 802ca55f..15fe26ee 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -225,6 +225,18 @@ class BaseSerializer(Field): return data + def clean_all(self, attrs): + """ + Run the `validate` method on the serializer, if it exists + """ + try: + validate_method = getattr(self, 'validate', None) + if validate_method: + attrs = validate_method(attrs) + except ValidationError as err: + self._errors['non_field_errors'] = err.messages + return attrs + def restore_object(self, attrs, instance=None): """ Deserialize a dictionary of attributes into an object instance. @@ -259,6 +271,7 @@ class BaseSerializer(Field): if data is not None: attrs = self.restore_fields(data) attrs = self.clean_fields(attrs) + attrs = self.clean_all(attrs) else: self._errors['non_field_errors'] = 'No input provided' -- cgit v1.2.3 From d60d598e0255fb3d55a1213d1025447d83523658 Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 11:43:30 +0100 Subject: Clean up internal names and documentation --- rest_framework/serializers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 15fe26ee..2f8108d1 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -208,24 +208,24 @@ class BaseSerializer(Field): return reverted_data - def clean_fields(self, data): + def validate_fields(self, attrs): """ Run validate_ methods on the serializer """ - fields = self.get_fields(serialize=False, data=data, nested=self.opts.nested) + fields = self.get_fields(serialize=False, data=attrs, nested=self.opts.nested) for field_name, field in fields.items(): try: clean_method = getattr(self, 'validate_%s' % field_name, None) if clean_method: source = field.source or field_name - data = clean_method(data, source) + attrs = clean_method(attrs, source) except ValidationError as err: self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) - return data + return attrs - def clean_all(self, attrs): + def validate_all(self, attrs): """ Run the `validate` method on the serializer, if it exists """ @@ -270,10 +270,10 @@ class BaseSerializer(Field): self._errors = {} if data is not None: attrs = self.restore_fields(data) - attrs = self.clean_fields(attrs) - attrs = self.clean_all(attrs) + attrs = self.validate_fields(attrs) + attrs = self.validate_all(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)) -- cgit v1.2.3 From 607c31c6d880501e5dc524fc5a5e1fc136b162fc Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Wed, 24 Oct 2012 12:12:27 +0100 Subject: Move per-field and cross-field validation into a single method --- rest_framework/serializers.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2f8108d1..c9f025bc 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -208,33 +208,32 @@ class BaseSerializer(Field): return reverted_data - def validate_fields(self, attrs): + def perform_validation(self, attrs): """ - Run validate_ methods on the serializer + Run `validate_()` and `validate()` methods on the serializer """ fields = self.get_fields(serialize=False, data=attrs, nested=self.opts.nested) for field_name, field in fields.items(): try: - clean_method = getattr(self, 'validate_%s' % field_name, None) - if clean_method: + validate_method = getattr(self, 'validate_%s' % field_name, None) + if validate_method: source = field.source or field_name - attrs = clean_method(attrs, source) + 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_all(self, attrs): + def validate(self, attrs): """ - Run the `validate` method on the serializer, if it exists + Stub method, to be overridden in Serializer subclasses """ - try: - validate_method = getattr(self, 'validate', None) - if validate_method: - attrs = validate_method(attrs) - except ValidationError as err: - self._errors['non_field_errors'] = err.messages return attrs def restore_object(self, attrs, instance=None): @@ -270,8 +269,7 @@ class BaseSerializer(Field): self._errors = {} if data is not None: attrs = self.restore_fields(data) - attrs = self.validate_fields(attrs) - attrs = self.validate_all(attrs) + attrs = self.perform_validation(attrs) else: self._errors['non_field_errors'] = ['No input provided'] -- cgit v1.2.3 From 3e751ccd8aa2870c125a17de6af6e1909aa2b35e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 24 Oct 2012 20:58:10 +0100 Subject: Fix ModelSerializer logic for fields with default value, which should have required=False set --- rest_framework/serializers.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c9f025bc..8ee9a0ec 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -406,6 +406,10 @@ class ModelSerializer(Serializer): """ Creates a default instance of a basic non-relational field. """ + kwargs = {} + if model_field.has_default(): + kwargs['required'] = False + field_mapping = { models.FloatField: FloatField, models.IntegerField: IntegerField, @@ -421,14 +425,9 @@ class ModelSerializer(Serializer): 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 is not None: - ret.required = False - - return ret + return ModelField(model_field=model_field, **kwargs) def restore_object(self, attrs, instance=None): """ -- cgit v1.2.3 From 32d602880fc88e2b3e8d8f2a82132bed224f8b49 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Oct 2012 12:45:52 +0100 Subject: Choice fields from ModelSerializer. --- rest_framework/serializers.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8ee9a0ec..d4fcddd5 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 * @@ -409,6 +410,15 @@ class ModelSerializer(Serializer): kwargs = {} if model_field.has_default(): kwargs['required'] = False + kwargs['default'] = model_field.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, -- cgit v1.2.3 From 67f1265e493adc35239d90aeb3bfeb8492fbd741 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Oct 2012 13:20:30 +0100 Subject: Fix failing 'default' on ModelSerializer --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d4fcddd5..db6f9f61 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -410,7 +410,7 @@ class ModelSerializer(Serializer): kwargs = {} if model_field.has_default(): kwargs['required'] = False - kwargs['default'] = model_field.default + kwargs['default'] = model_field.get_default() if model_field.__class__ == models.TextField: kwargs['widget'] = widgets.Textarea -- cgit v1.2.3 From 44207a347ac765d900f15b65bdd24dbf8eb9ead2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 27 Oct 2012 10:32:49 +0100 Subject: pep8 --- rest_framework/serializers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index db6f9f61..8a895343 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -34,10 +34,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) ) -- cgit v1.2.3 From 351382fe35f966c989b27add5bb04d0d983a99ee Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 28 Oct 2012 20:43:43 +0000 Subject: nested -> depth --- rest_framework/serializers.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8a895343..31d9d7a5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -74,7 +74,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', ()) @@ -156,10 +156,8 @@ class BaseSerializer(Field): """ 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. @@ -182,14 +180,10 @@ class BaseSerializer(Field): 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 @@ -199,7 +193,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: @@ -213,7 +207,8 @@ class BaseSerializer(Field): """ Run `validate_()` and `validate()` methods on the serializer """ - fields = self.get_fields(serialize=False, data=attrs, nested=self.opts.nested) + # 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: -- cgit v1.2.3 From de6908fbef89f9fb02b5a2a7bfcd85280448f241 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sun, 28 Oct 2012 20:50:14 +0000 Subject: Remove recursion detection --- rest_framework/serializers.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 31d9d7a5..ce04b3e2 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -23,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 @@ -93,7 +89,6 @@ class BaseSerializer(Field): self.parent = None self.root = None - self.stack = [] self.context = context or {} self.init_data = data @@ -152,10 +147,9 @@ 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.depth: self.opts.depth = parent.opts.depth - 1 @@ -173,10 +167,6 @@ 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 = {} -- cgit v1.2.3 From 206859529780adbe7f49ee1e4d466e3b4fe13099 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 30 Oct 2012 00:09:40 +0100 Subject: Set a ModelSerializer field to required=False if the ModelField has null=True set. --- rest_framework/serializers.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ce04b3e2..e705bf77 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -393,6 +393,9 @@ class ModelSerializer(Serializer): Creates a default instance of a basic non-relational field. """ kwargs = {} + if model_field.null: + kwargs['required'] = False + if model_field.has_default(): kwargs['required'] = False kwargs['default'] = model_field.get_default() -- cgit v1.2.3 From 4d90bb4af4e565f156ca5d84eb205cbb63810b46 Mon Sep 17 00:00:00 2001 From: Marko Tibold Date: Tue, 30 Oct 2012 00:30:52 +0100 Subject: Fix some typos. --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ce04b3e2..1cfcf103 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -279,7 +279,7 @@ class BaseSerializer(Field): 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) -- cgit v1.2.3 From 0047a4602045ed0ef9efae9aa88143aa6a70d93d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 30 Oct 2012 11:03:03 +0000 Subject: Merge fixes for 'blank=True' fields. Fixes #324, Fixes #325 Thanks to @Roarster. --- rest_framework/serializers.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework/serializers.py') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 16e2c835..3d134a74 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -393,6 +393,9 @@ 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 -- cgit v1.2.3