Skip to content

Commit 598e587

Browse files
authored
Merge pull request #5192 from matteius/DRF-5135-one-to-one-pk
Special case for when OneToOneField is also primary key.
2 parents d48a745 + 88f9dbc commit 598e587

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
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: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from rest_framework import serializers
77
from tests.models import (
88
ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget,
9-
NullableForeignKeySource, NullableOneToOneSource,
10-
NullableUUIDForeignKeySource, OneToOneTarget, UUIDForeignKeyTarget
9+
NullableForeignKeySource, NullableOneToOneSource, NullableUUIDForeignKeySource,
10+
OneToOnePKSource, OneToOneTarget, UUIDForeignKeyTarget
1111
)
1212

1313

@@ -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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy