Skip to content

Commit f323049

Browse files
authored
Fix pk-only optimization for properties (#7142)
* Add callable/prop tests for pk-only optimization * Fix related field pk-only optimization for props
1 parent b3e0259 commit f323049

File tree

3 files changed

+63
-2
lines changed

3 files changed

+63
-2
lines changed

rest_framework/relations.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,13 @@ def get_attribute(self, instance):
175175
value = attribute_instance.serializable_value(self.source_attrs[-1])
176176
if is_simple_callable(value):
177177
# Handle edge case where the relationship `source` argument
178-
# points to a `get_relationship()` method on the model
179-
value = value().pk
178+
# points to a `get_relationship()` method on the model.
179+
value = value()
180+
181+
# Handle edge case where relationship `source` argument points
182+
# to an instance instead of a pk (e.g., a `@property`).
183+
value = getattr(value, 'pk', value)
184+
180185
return PKOnlyObject(pk=value)
181186
except AttributeError:
182187
pass

tests/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ class ManyToManySource(RESTFrameworkModel):
3737
class ForeignKeyTarget(RESTFrameworkModel):
3838
name = models.CharField(max_length=100)
3939

40+
def get_first_source(self):
41+
"""Used for testing related field against a callable."""
42+
return self.sources.all().order_by('pk')[0]
43+
44+
@property
45+
def first_source(self):
46+
"""Used for testing related field against a property."""
47+
return self.sources.all().order_by('pk')[0]
48+
4049

4150
class UUIDForeignKeyTarget(RESTFrameworkModel):
4251
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)

tests/test_relations_pk.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,25 @@ class Meta:
3030
fields = ('id', 'name', 'sources')
3131

3232

33+
class ForeignKeyTargetCallableSourceSerializer(serializers.ModelSerializer):
34+
first_source = serializers.PrimaryKeyRelatedField(
35+
source='get_first_source',
36+
read_only=True,
37+
)
38+
39+
class Meta:
40+
model = ForeignKeyTarget
41+
fields = ('id', 'name', 'first_source')
42+
43+
44+
class ForeignKeyTargetPropertySourceSerializer(serializers.ModelSerializer):
45+
first_source = serializers.PrimaryKeyRelatedField(read_only=True)
46+
47+
class Meta:
48+
model = ForeignKeyTarget
49+
fields = ('id', 'name', 'first_source')
50+
51+
3352
class ForeignKeySourceSerializer(serializers.ModelSerializer):
3453
class Meta:
3554
model = ForeignKeySource
@@ -389,6 +408,34 @@ class Meta:
389408
assert len(queryset) == 1
390409

391410

411+
class PKRelationTests(TestCase):
412+
413+
def setUp(self):
414+
self.target = ForeignKeyTarget.objects.create(name='target-1')
415+
ForeignKeySource.objects.create(name='source-1', target=self.target)
416+
ForeignKeySource.objects.create(name='source-2', target=self.target)
417+
418+
def test_relation_field_callable_source(self):
419+
serializer = ForeignKeyTargetCallableSourceSerializer(self.target)
420+
expected = {
421+
'id': 1,
422+
'name': 'target-1',
423+
'first_source': 1,
424+
}
425+
with self.assertNumQueries(1):
426+
self.assertEqual(serializer.data, expected)
427+
428+
def test_relation_field_property_source(self):
429+
serializer = ForeignKeyTargetPropertySourceSerializer(self.target)
430+
expected = {
431+
'id': 1,
432+
'name': 'target-1',
433+
'first_source': 1,
434+
}
435+
with self.assertNumQueries(1):
436+
self.assertEqual(serializer.data, expected)
437+
438+
392439
class PKNullableForeignKeyTests(TestCase):
393440
def setUp(self):
394441
target = ForeignKeyTarget(name='target-1')

0 commit comments

Comments
 (0)