Skip to content

Commit 6115815

Browse files
committed
Special case for when OneToOneField is also primary key.
encode#5135
1 parent 9956919 commit 6115815

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

rest_framework/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,11 @@ def build_standard_field(self, field_name, model_field):
11591159
field_class = field_mapping[model_field]
11601160
field_kwargs = get_field_kwargs(field_name, model_field)
11611161

1162+
# Special case to handle when a OneToOneField is also the primary key
1163+
if model_field.one_to_one and model_field.primary_key:
1164+
field_class = self.serializer_related_field
1165+
field_kwargs['queryset'] = model_field.related_model.objects
1166+
11621167
if 'choices' in field_kwargs:
11631168
# Fields with choices get coerced into `ChoiceField`
11641169
# instead of using their regular typed field.

tests/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,11 @@ class NullableOneToOneSource(RESTFrameworkModel):
8888
target = models.OneToOneField(
8989
OneToOneTarget, null=True, blank=True,
9090
related_name='nullable_source', on_delete=models.CASCADE)
91+
92+
93+
class OneToOnePKSource(RESTFrameworkModel):
94+
""" Test model where the primary key is a OneToOneField with another model. """
95+
name = models.CharField(max_length=100)
96+
target = models.OneToOneField(
97+
OneToOneTarget, primary_key=True,
98+
related_name='required_source', on_delete=models.CASCADE)

tests/test_relations_pk.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from rest_framework import serializers
77
from tests.models import (
88
ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget,
9-
NullableForeignKeySource, NullableOneToOneSource,
9+
NullableForeignKeySource, NullableOneToOneSource, OneToOnePKSource,
1010
NullableUUIDForeignKeySource, OneToOneTarget, UUIDForeignKeyTarget
1111
)
1212

@@ -63,6 +63,13 @@ class Meta:
6363
fields = ('id', 'name', 'nullable_source')
6464

6565

66+
class OneToOnePKSourceSerializer(serializers.ModelSerializer):
67+
68+
class Meta:
69+
model = OneToOnePKSource
70+
fields = '__all__'
71+
72+
6673
# TODO: Add test that .data cannot be accessed prior to .is_valid
6774

6875
class PKManyToManyTests(TestCase):
@@ -486,3 +493,51 @@ def test_reverse_foreign_key_retrieve_with_null(self):
486493
{'id': 2, 'name': 'target-2', 'nullable_source': 1},
487494
]
488495
assert serializer.data == expected
496+
497+
498+
class OneToOnePrimaryKeyTests(TestCase):
499+
500+
def setUp(self):
501+
# Given: Some target models already exist
502+
self.target = target = OneToOneTarget(name='target-1')
503+
target.save()
504+
self.alt_target = alt_target = OneToOneTarget(name='target-2')
505+
alt_target.save()
506+
507+
def test_one_to_one_when_primary_key(self):
508+
# When: Creating a Source pointing at the id of the second Target
509+
target_pk = self.alt_target.id
510+
source = OneToOnePKSourceSerializer(data={'name': 'source-2', 'target': target_pk})
511+
# Then: The source is valid with the serializer
512+
if not source.is_valid():
513+
self.fail("Expected OneToOnePKTargetSerializer to be valid but had errors: {}".format(source.errors))
514+
# Then: Saving the serializer creates a new object
515+
new_source = source.save()
516+
# Then: The new object has the same pk as the target object
517+
self.assertEqual(new_source.pk, target_pk)
518+
519+
def test_one_to_one_when_primary_key_no_duplicates(self):
520+
# When: Creating a Source pointing at the id of the second Target
521+
target_pk = self.target.id
522+
data = {'name': 'source-1', 'target': target_pk}
523+
source = OneToOnePKSourceSerializer(data=data)
524+
# Then: The source is valid with the serializer
525+
self.assertTrue(source.is_valid())
526+
# Then: Saving the serializer creates a new object
527+
new_source = source.save()
528+
# Then: The new object has the same pk as the target object
529+
self.assertEqual(new_source.pk, target_pk)
530+
# When: Trying to create a second object
531+
second_source = OneToOnePKSourceSerializer(data=data)
532+
self.assertFalse(second_source.is_valid())
533+
expected = {'target': [u'one to one pk source with this target already exists.']}
534+
self.assertDictEqual(second_source.errors, expected)
535+
536+
def test_one_to_one_when_primary_key_does_not_exist(self):
537+
# Given: a target PK that does not exist
538+
target_pk = self.target.pk + self.alt_target.pk
539+
source = OneToOnePKSourceSerializer(data={'name': 'source-2', 'target': target_pk})
540+
# Then: The source is not valid with the serializer
541+
self.assertFalse(source.is_valid())
542+
self.assertIn("Invalid pk", source.errors['target'][0])
543+
self.assertIn("object does not exist", source.errors['target'][0])

0 commit comments

Comments
 (0)