mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-04 20:40:14 +03:00
Merge 5de99a60f9
into 3b899c9d57
This commit is contained in:
commit
8a52c11d86
|
@ -256,6 +256,7 @@ class WritableField(Field):
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'required': _('This field is required.'),
|
'required': _('This field is required.'),
|
||||||
'invalid': _('Invalid value.'),
|
'invalid': _('Invalid value.'),
|
||||||
|
'missing': _('Related object does not exist.'),
|
||||||
}
|
}
|
||||||
widget = widgets.TextInput
|
widget = widgets.TextInput
|
||||||
default = None
|
default = None
|
||||||
|
|
|
@ -21,6 +21,7 @@ from django.core.paginator import Page
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from rest_framework.compat import get_concrete_model, six
|
from rest_framework.compat import get_concrete_model, six
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
@ -941,6 +942,7 @@ class ModelSerializer(Serializer):
|
||||||
"""
|
"""
|
||||||
m2m_data = {}
|
m2m_data = {}
|
||||||
related_data = {}
|
related_data = {}
|
||||||
|
related_models_to_save = {}
|
||||||
nested_forward_relations = {}
|
nested_forward_relations = {}
|
||||||
meta = self.opts.model._meta
|
meta = self.opts.model._meta
|
||||||
|
|
||||||
|
@ -969,13 +971,33 @@ 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]
|
||||||
|
|
||||||
# Create an empty instance of the model
|
# Update an existing instance...
|
||||||
if instance is None:
|
if instance is None:
|
||||||
instance = self.opts.model()
|
instance = self.opts.model()
|
||||||
|
|
||||||
for key, val in attrs.items():
|
for key, val in attrs.items():
|
||||||
|
keys = key.split('.')
|
||||||
|
|
||||||
|
# Work on the current instance for this attribute
|
||||||
|
attr_instance = instance
|
||||||
|
|
||||||
|
# Raise an error if we span more than one relation
|
||||||
|
if len(keys) > 2:
|
||||||
|
self._errors[key] = 'Can not span more than a relation.'
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Mark the related instance as the one to save
|
||||||
|
if len(keys) == 2:
|
||||||
try:
|
try:
|
||||||
setattr(instance, key, val)
|
attr_instance = getattr(instance, keys[0])
|
||||||
|
related_models_to_save[key] = attr_instance
|
||||||
|
except (AttributeError, ObjectDoesNotExist):
|
||||||
|
self._errors[key] = self.error_messages['missing']
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Assign the value
|
||||||
|
try:
|
||||||
|
setattr(attr_instance, keys[-1], val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._errors[key] = [self.error_messages['required']]
|
self._errors[key] = [self.error_messages['required']]
|
||||||
|
|
||||||
|
@ -986,6 +1008,7 @@ 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._related_models_to_save = related_models_to_save
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@ -1044,6 +1067,10 @@ class ModelSerializer(Serializer):
|
||||||
setattr(obj, accessor_name, related)
|
setattr(obj, accessor_name, related)
|
||||||
del(obj._related_data)
|
del(obj._related_data)
|
||||||
|
|
||||||
|
if getattr(obj, '_related_models_to_save', None):
|
||||||
|
for related in obj._related_models_to_save.values():
|
||||||
|
self.save_object(related)
|
||||||
|
|
||||||
|
|
||||||
class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
|
class HyperlinkedModelSerializerOptions(ModelSerializerOptions):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1974,3 +1974,68 @@ class BoolenFieldTypeTest(TestCase):
|
||||||
'''
|
'''
|
||||||
bfield = self.serializer.get_fields()['started']
|
bfield = self.serializer.get_fields()['started']
|
||||||
self.assertEqual(type(bfield), fields.BooleanField)
|
self.assertEqual(type(bfield), fields.BooleanField)
|
||||||
|
|
||||||
|
|
||||||
|
class RelationSpanningSerializerTest(TestCase):
|
||||||
|
def test_model_traversal_creation(self):
|
||||||
|
"""Update a field through a foreign key during a creation."""
|
||||||
|
class TicketSerializer(serializers.ModelSerializer):
|
||||||
|
username = fields.CharField(source='assigned.name')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Ticket
|
||||||
|
fields = ('username',)
|
||||||
|
|
||||||
|
serializer = TicketSerializer(data={'username': 'doe'})
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
self.assertEqual(serializer.errors, {'username': 'You can not set dotted sources during creation.'})
|
||||||
|
|
||||||
|
def test_model_traversal_update(self):
|
||||||
|
"""Update a field through a foreign key during an update."""
|
||||||
|
class TicketSerializer(serializers.ModelSerializer):
|
||||||
|
username = fields.CharField(source='assigned.name')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Ticket
|
||||||
|
fields = ('username',)
|
||||||
|
|
||||||
|
owner = Person.objects.create(name='john')
|
||||||
|
reviewer = Person.objects.create(name='reviewer')
|
||||||
|
ticket = Ticket.objects.create(assigned=owner, reviewer=reviewer)
|
||||||
|
serializer = TicketSerializer(ticket, data={'username': 'doe'})
|
||||||
|
self.assertTrue(serializer.is_valid())
|
||||||
|
self.assertEqual(serializer.object.assigned.name, 'doe')
|
||||||
|
serializer.save()
|
||||||
|
self.assertEqual(Person.objects.get(id=owner.id).name, 'doe')
|
||||||
|
|
||||||
|
def test_failing_model_traversal(self):
|
||||||
|
"""Update a field through an unknown relation."""
|
||||||
|
class TicketSerializer(serializers.ModelSerializer):
|
||||||
|
username = fields.CharField(source='demo.name')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Ticket
|
||||||
|
fields = ('username',)
|
||||||
|
|
||||||
|
owner = Person(name='john')
|
||||||
|
reviewer = Person(name='reviewer')
|
||||||
|
ticket = Ticket(assigned=owner, reviewer=reviewer)
|
||||||
|
serializer = TicketSerializer(ticket, data={'username': 'doe'})
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
self.assertEqual(serializer.errors, {'username': 'Related object does not exist.'})
|
||||||
|
|
||||||
|
def test_multiple_model_traversal_update(self):
|
||||||
|
"""Update a field through a foreign key during an update."""
|
||||||
|
class TicketSerializer(serializers.ModelSerializer):
|
||||||
|
username = fields.CharField(source='assigned.demo.name')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Ticket
|
||||||
|
fields = ('username',)
|
||||||
|
|
||||||
|
owner = Person.objects.create(name='john')
|
||||||
|
reviewer = Person.objects.create(name='reviewer')
|
||||||
|
ticket = Ticket.objects.create(assigned=owner, reviewer=reviewer)
|
||||||
|
serializer = TicketSerializer(ticket, data={'username': 'doe'})
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
self.assertEqual(serializer.errors, {'username': 'Can not span more than a relation.'})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user