Fix issue when serializing certain foreign keys.

If a foreign key has a default value that is not an instance of the
related model (i.e. its a value for the primary key for that model),
the foreign key field has a uniqueness constraint, and the field was not
provided as data for the serializer, then we need to set that default
value to the attname of the field when creating the model instance.
Otherwise, a ValueError will be raised when creating the model instance,
saying 'Cannot assign "<default value>": "<field>" must be a
"<related_model>" instance.'

This change ensures that the kwargs passed to model instance creation
are modified when appropriate to account for this.

Signed-off-by: Tommy Beadle <tbeadle@gmail.com>
This commit is contained in:
Tommy Beadle 2017-07-13 15:40:42 -04:00
parent 2a1fd3b45a
commit 34d2f7c529
3 changed files with 60 additions and 5 deletions

View File

@ -909,8 +909,14 @@ class ModelSerializer(Serializer):
info = model_meta.get_field_info(ModelClass)
many_to_many = {}
for field_name, relation_info in info.relations.items():
if relation_info.to_many and (field_name in validated_data):
if field_name in validated_data:
if relation_info.to_many:
many_to_many[field_name] = validated_data.pop(field_name)
elif not isinstance(
validated_data[field_name],
relation_info.related_model):
validated_data[relation_info.model_field.attname] = \
validated_data.pop(field_name)
try:
instance = ModelClass.objects.create(**validated_data)
@ -1368,7 +1374,7 @@ class ModelSerializer(Serializer):
elif getattr(unique_constraint_field, 'auto_now', None):
default = timezone.now
elif unique_constraint_field.has_default():
default = unique_constraint_field.default
default = unique_constraint_field.get_default()
else:
default = empty

View File

@ -61,6 +61,19 @@ class ForeignKeySource(RESTFrameworkModel):
on_delete=models.CASCADE)
class ForeignKeyInUniquenessConstraintSource(RESTFrameworkModel):
name = models.CharField(max_length=100)
target = models.ForeignKey(ForeignKeyTarget,
related_name='sources_with_uniqueness',
help_text='Target',
verbose_name='Target',
default=1,
on_delete=models.CASCADE)
class Meta:
unique_together = ('name', 'target')
# Nullable ForeignKey
class NullableForeignKeySource(RESTFrameworkModel):
name = models.CharField(max_length=100)

View File

@ -5,8 +5,9 @@ from django.utils import six
from rest_framework import serializers
from tests.models import (
ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget,
NullableForeignKeySource, NullableOneToOneSource, NullableUUIDForeignKeySource,
ForeignKeyInUniquenessConstraintSource, ForeignKeySource, ForeignKeyTarget,
ManyToManySource, ManyToManyTarget, NullableForeignKeySource,
NullableOneToOneSource, NullableUUIDForeignKeySource,
OneToOnePKSource, OneToOneTarget, UUIDForeignKeyTarget
)
@ -37,6 +38,12 @@ class ForeignKeySourceSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'target')
class ForeignKeyUniquenessSourceSerializer(serializers.ModelSerializer):
class Meta:
model = ForeignKeyInUniquenessConstraintSource
fields = ('id', 'name', 'target')
# Nullable ForeignKey
class NullableForeignKeySourceSerializer(serializers.ModelSerializer):
class Meta:
@ -360,6 +367,35 @@ class PKForeignKeyTests(TestCase):
assert 'target' not in serializer.validated_data
class PKForeignKeyInUniquenessConstraintTests(TestCase):
def test_use_default_value_for_fk(self):
default_val = ForeignKeyInUniquenessConstraintSource._meta.get_field(
'target'
).default
target = ForeignKeyTarget.objects.create(pk=default_val)
serializer = ForeignKeyUniquenessSourceSerializer(
data={'name': 'source-1'}
)
assert serializer.is_valid()
serializer.save()
source = ForeignKeyInUniquenessConstraintSource.objects.get(
name='source-1',
)
assert source.target == target
def test_provide_value_for_fk(self):
target = ForeignKeyTarget.objects.create(pk=10)
serializer = ForeignKeyUniquenessSourceSerializer(
data={'name': 'source-2', 'target': target.pk}
)
assert serializer.is_valid()
serializer.save()
source = ForeignKeyInUniquenessConstraintSource.objects.get(
name='source-2',
)
assert source.target == target
class PKNullableForeignKeyTests(TestCase):
def setUp(self):
target = ForeignKeyTarget(name='target-1')