diff options
| author | Tom Christie | 2013-03-15 19:17:54 +0000 |
|---|---|---|
| committer | Tom Christie | 2013-03-15 19:17:54 +0000 |
| commit | fb3b57803f8dbedc632458b762de9d3323a3360e (patch) | |
| tree | 402c37017693c32d6030d2c095c778b2a3a03f05 /rest_framework/serializers.py | |
| parent | 22a389d0ba4dd5ac7b4fa3839491ec2708bbe7df (diff) | |
| parent | 3006e3825f29e920f881b816fd71566bf0e8d341 (diff) | |
| download | django-rest-framework-fb3b57803f8dbedc632458b762de9d3323a3360e.tar.bz2 | |
one 2 one nested relationships
Diffstat (limited to 'rest_framework/serializers.py')
| -rw-r--r-- | rest_framework/serializers.py | 146 |
1 files changed, 113 insertions, 33 deletions
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4fe857a6..f073e00a 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -20,6 +20,25 @@ from rest_framework.relations import * from rest_framework.fields import * +class NestedValidationError(ValidationError): + """ + The default ValidationError behavior is to stringify each item in the list + if the messages are a list of error messages. + + In the case of nested serializers, where the parent has many children, + then the child's `serializer.errors` will be a list of dicts. In the case + of a single child, the `serializer.errors` will be a dict. + + We need to override the default behavior to get properly nested error dicts. + """ + + def __init__(self, message): + if isinstance(message, dict): + self.messages = [message] + else: + self.messages = message + + class DictWithMetadata(dict): """ A dict-like object, that can have additional properties attached. @@ -98,7 +117,7 @@ class SerializerOptions(object): self.exclude = getattr(meta, 'exclude', ()) -class BaseSerializer(Field): +class BaseSerializer(WritableField): """ This is the Serializer implementation. We need to implement it as `BaseSerializer` due to metaclass magicks. @@ -128,6 +147,7 @@ class BaseSerializer(Field): self._data = None self._files = None self._errors = None + self._delete = False ##### # Methods to determine which fields to use when (de)serializing objects. @@ -296,40 +316,77 @@ class BaseSerializer(Field): def field_to_native(self, obj, field_name): """ - Override default so that we can apply ModelSerializer as a nested - field to relationships. + Override default so that the serializer can be used as a nested field + across relationships. """ if self.source == '*': return self.to_native(obj) try: - if self.source: - for component in self.source.split('.'): - obj = getattr(obj, component) - if is_simple_callable(obj): - obj = obj() - else: - obj = getattr(obj, field_name) - if is_simple_callable(obj): - obj = obj() + source = self.source or field_name + value = obj + + for component in source.split('.'): + value = get_component(value, component) + if value is None: + break except ObjectDoesNotExist: return None - # 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()] + if is_simple_callable(getattr(value, 'all', None)): + return [self.to_native(item) for item in value.all()] - if obj is None: + if value is None: return None if self.many is not None: many = self.many else: - many = hasattr(obj, '__iter__') and not isinstance(obj, (Page, dict, six.text_type)) + many = hasattr(value, '__iter__') and not isinstance(value, (Page, dict, six.text_type)) if many: - return [self.to_native(item) for item in obj] - return self.to_native(obj) + return [self.to_native(item) for item in value] + return self.to_native(value) + + def field_from_native(self, data, files, field_name, into): + """ + Override default so that the serializer can be used as a writable + nested field across relationships. + """ + if self.read_only: + return + + try: + value = data[field_name] + except KeyError: + if self.required: + raise ValidationError(self.error_messages['required']) + return + + # Set the serializer object if it exists + obj = getattr(self.parent.object, field_name) if self.parent.object else None + + if value in (None, ''): + into[(self.source or field_name)] = None + else: + kwargs = { + 'instance': obj, + 'data': value, + 'context': self.context, + 'partial': self.partial, + 'many': self.many + } + serializer = self.__class__(**kwargs) + + if serializer.is_valid(): + if isinstance(serializer, ModelSerializer): + into[self.source or field_name] = serializer + else: + into[self.source or field_name] = serializer.object + # into[self.source or field_name] = serializer.object + else: + # Propagate errors up to our parent + raise NestedValidationError(serializer.errors) @property def errors(self): @@ -584,25 +641,25 @@ class ModelSerializer(Serializer): """ Restore the model instance. """ - self.m2m_data = {} - self.related_data = {} + m2m_data = {} + related_data = {} # Reverse fk relations for (obj, model) in self.opts.model._meta.get_all_related_objects_with_model(): field_name = obj.field.related_query_name() if field_name in attrs: - self.related_data[field_name] = attrs.pop(field_name) + related_data[field_name] = attrs.pop(field_name) # Reverse m2m relations for (obj, model) in self.opts.model._meta.get_all_related_m2m_objects_with_model(): field_name = obj.field.related_query_name() if field_name in attrs: - self.m2m_data[field_name] = attrs.pop(field_name) + m2m_data[field_name] = attrs.pop(field_name) # Forward m2m relations for field in self.opts.model._meta.many_to_many: if field.name in attrs: - self.m2m_data[field.name] = attrs.pop(field.name) + m2m_data[field.name] = attrs.pop(field.name) if instance is not None: for key, val in attrs.items(): @@ -611,6 +668,9 @@ class ModelSerializer(Serializer): else: instance = self.opts.model(**attrs) + instance._related_data = related_data + instance._m2m_data = m2m_data + return instance def from_native(self, data, files): @@ -621,21 +681,41 @@ class ModelSerializer(Serializer): if instance: return self.full_clean(instance) - def save_object(self, obj, **kwargs): +# def save_object(self, obj, **kwargs): +# """ +# Save the deserialized object and return it. +# """ +# obj.save(**kwargs) +# ======= + def save_object(self, obj, parent=None, fk_field=None, **kwargs): """ Save the deserialized object and return it. """ - obj.save(**kwargs) + if parent and fk_field: + setattr(self.object, fk_field, parent) - if getattr(self, 'm2m_data', None): - for accessor_name, object_list in self.m2m_data.items(): - setattr(self.object, accessor_name, object_list) - self.m2m_data = {} + obj.save(**kwargs) - if getattr(self, 'related_data', None): - for accessor_name, object_list in self.related_data.items(): + if getattr(obj, '_m2m_data', None): + for accessor_name, object_list in obj._m2m_data.items(): setattr(self.object, accessor_name, object_list) - self.related_data = {} + obj._m2m_data = {} + + if getattr(obj, '_related_data', None): + for accessor_name, related in obj._related_data.items(): + if related is None: + previous = getattr(self.object, accessor_name, related) + previous.delete() + elif isinstance(related, ModelSerializer): + # print related.object + # print related.related_data, related.m2m_data + fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name + related.save_object(related.object, parent=self.object, fk_field=fk_field) + # setattr(related, fk_field, obj) + # related.save(**kwargs) + else: + setattr(self.object, accessor_name, related) + obj._related_data = {} class HyperlinkedModelSerializerOptions(ModelSerializerOptions): |
