mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-12 04:37:13 +03:00
Merge pull request #796 from maspwr/writable-nested-modelserializer
Fix model serializer nestesd delete behavior
This commit is contained in:
commit
1148b25ccf
|
@ -20,6 +20,9 @@ from rest_framework.relations import *
|
||||||
from rest_framework.fields import *
|
from rest_framework.fields import *
|
||||||
|
|
||||||
|
|
||||||
|
class RelationsList(list):
|
||||||
|
_deleted = []
|
||||||
|
|
||||||
class NestedValidationError(ValidationError):
|
class NestedValidationError(ValidationError):
|
||||||
"""
|
"""
|
||||||
The default ValidationError behavior is to stringify each item in the list
|
The default ValidationError behavior is to stringify each item in the list
|
||||||
|
@ -149,7 +152,6 @@ class BaseSerializer(WritableField):
|
||||||
self._data = None
|
self._data = None
|
||||||
self._files = None
|
self._files = None
|
||||||
self._errors = None
|
self._errors = None
|
||||||
self._deleted = None
|
|
||||||
|
|
||||||
if many and instance is not None and not hasattr(instance, '__iter__'):
|
if many and instance is not None and not hasattr(instance, '__iter__'):
|
||||||
raise ValueError('instance should be a queryset or other iterable with many=True')
|
raise ValueError('instance should be a queryset or other iterable with many=True')
|
||||||
|
@ -288,15 +290,8 @@ class BaseSerializer(WritableField):
|
||||||
You should override this method to control how deserialized objects
|
You should override this method to control how deserialized objects
|
||||||
are instantiated.
|
are instantiated.
|
||||||
"""
|
"""
|
||||||
removed_relations = []
|
|
||||||
|
|
||||||
# Deleted related objects
|
|
||||||
if self._deleted:
|
|
||||||
removed_relations = list(self._deleted)
|
|
||||||
|
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
instance.update(attrs)
|
instance.update(attrs)
|
||||||
instance._removed_relations = removed_relations
|
|
||||||
return instance
|
return instance
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
@ -438,7 +433,7 @@ class BaseSerializer(WritableField):
|
||||||
PendingDeprecationWarning, stacklevel=3)
|
PendingDeprecationWarning, stacklevel=3)
|
||||||
|
|
||||||
if many:
|
if many:
|
||||||
ret = []
|
ret = RelationsList()
|
||||||
errors = []
|
errors = []
|
||||||
update = self.object is not None
|
update = self.object is not None
|
||||||
|
|
||||||
|
@ -466,7 +461,7 @@ class BaseSerializer(WritableField):
|
||||||
errors.append(self._errors)
|
errors.append(self._errors)
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
self._deleted = identity_to_objects.values()
|
ret._deleted = identity_to_objects.values()
|
||||||
|
|
||||||
self._errors = any(errors) and errors or []
|
self._errors = any(errors) and errors or []
|
||||||
else:
|
else:
|
||||||
|
@ -509,9 +504,6 @@ class BaseSerializer(WritableField):
|
||||||
def save_object(self, obj, **kwargs):
|
def save_object(self, obj, **kwargs):
|
||||||
obj.save(**kwargs)
|
obj.save(**kwargs)
|
||||||
|
|
||||||
if self.allow_add_remove and hasattr(obj, '_removed_relations'):
|
|
||||||
[self.delete_object(item) for item in obj._removed_relations]
|
|
||||||
|
|
||||||
def delete_object(self, obj):
|
def delete_object(self, obj):
|
||||||
obj.delete()
|
obj.delete()
|
||||||
|
|
||||||
|
@ -521,12 +513,12 @@ class BaseSerializer(WritableField):
|
||||||
"""
|
"""
|
||||||
if isinstance(self.object, list):
|
if isinstance(self.object, list):
|
||||||
[self.save_object(item, **kwargs) for item in self.object]
|
[self.save_object(item, **kwargs) for item in self.object]
|
||||||
|
|
||||||
|
if self.allow_add_remove and self.object._deleted:
|
||||||
|
[self.delete_object(item) for item in self.object._deleted]
|
||||||
else:
|
else:
|
||||||
self.save_object(self.object, **kwargs)
|
self.save_object(self.object, **kwargs)
|
||||||
|
|
||||||
if self.allow_add_remove and self._deleted:
|
|
||||||
[self.delete_object(item) for item in self._deleted]
|
|
||||||
|
|
||||||
return self.object
|
return self.object
|
||||||
|
|
||||||
|
|
||||||
|
@ -715,7 +707,6 @@ class ModelSerializer(Serializer):
|
||||||
m2m_data = {}
|
m2m_data = {}
|
||||||
related_data = {}
|
related_data = {}
|
||||||
nested_forward_relations = {}
|
nested_forward_relations = {}
|
||||||
removed_relations = []
|
|
||||||
meta = self.opts.model._meta
|
meta = self.opts.model._meta
|
||||||
|
|
||||||
# Reverse fk or one-to-one relations
|
# Reverse fk or one-to-one relations
|
||||||
|
@ -741,10 +732,6 @@ class ModelSerializer(Serializer):
|
||||||
if isinstance(self.fields.get(field_name, None), Serializer):
|
if isinstance(self.fields.get(field_name, None), Serializer):
|
||||||
nested_forward_relations[field_name] = attrs[field_name]
|
nested_forward_relations[field_name] = attrs[field_name]
|
||||||
|
|
||||||
# Deleted related objects
|
|
||||||
if self._deleted:
|
|
||||||
removed_relations = list(self._deleted)
|
|
||||||
|
|
||||||
# Update an existing instance...
|
# Update an existing instance...
|
||||||
if instance is not None:
|
if instance is not None:
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
|
@ -761,7 +748,6 @@ class ModelSerializer(Serializer):
|
||||||
instance._related_data = related_data
|
instance._related_data = related_data
|
||||||
instance._m2m_data = m2m_data
|
instance._m2m_data = m2m_data
|
||||||
instance._nested_forward_relations = nested_forward_relations
|
instance._nested_forward_relations = nested_forward_relations
|
||||||
instance._removed_relations = removed_relations
|
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@ -786,9 +772,6 @@ class ModelSerializer(Serializer):
|
||||||
|
|
||||||
obj.save(**kwargs)
|
obj.save(**kwargs)
|
||||||
|
|
||||||
if self.allow_add_remove and hasattr(obj, '_removed_relations'):
|
|
||||||
[self.delete_object(item) for item in obj._removed_relations]
|
|
||||||
|
|
||||||
if getattr(obj, '_m2m_data', None):
|
if getattr(obj, '_m2m_data', None):
|
||||||
for accessor_name, object_list in obj._m2m_data.items():
|
for accessor_name, object_list in obj._m2m_data.items():
|
||||||
setattr(obj, accessor_name, object_list)
|
setattr(obj, accessor_name, object_list)
|
||||||
|
@ -804,6 +787,11 @@ class ModelSerializer(Serializer):
|
||||||
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
|
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
|
||||||
setattr(related_item, fk_field, obj)
|
setattr(related_item, fk_field, obj)
|
||||||
self.save_object(related_item)
|
self.save_object(related_item)
|
||||||
|
|
||||||
|
# Delete any removed objects
|
||||||
|
if field.allow_add_remove and related._deleted:
|
||||||
|
[self.delete_object(item) for item in related._deleted]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Nested reverse one-one relationship
|
# Nested reverse one-one relationship
|
||||||
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
|
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
|
||||||
|
|
|
@ -287,3 +287,22 @@ class ReverseNestedOneToManyTests(TestCase):
|
||||||
|
|
||||||
]
|
]
|
||||||
self.assertEqual(serializer.data, expected)
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
||||||
|
def test_one_to_many_delete(self):
|
||||||
|
data = {'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
|
||||||
|
{'id': 3, 'name': 'source-3'}]}
|
||||||
|
instance = OneToManyTarget.objects.get(pk=1)
|
||||||
|
serializer = self.Serializer(instance, data=data)
|
||||||
|
self.assertTrue(serializer.is_valid())
|
||||||
|
serializer.save()
|
||||||
|
|
||||||
|
# Ensure source 2 is deleted, and everything else is as
|
||||||
|
# expected.
|
||||||
|
queryset = OneToManyTarget.objects.all()
|
||||||
|
serializer = self.Serializer(queryset)
|
||||||
|
expected = [
|
||||||
|
{'id': 1, 'name': 'target-1', 'sources': [{'id': 1, 'name': 'source-1'},
|
||||||
|
{'id': 3, 'name': 'source-3'}]}
|
||||||
|
|
||||||
|
]
|
||||||
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user