Skip to content

Commit 9568245

Browse files
committed
Add PrimaryKeyWriteSerializerReadField.
1 parent 53f0aff commit 9568245

File tree

7 files changed

+117
-5
lines changed

7 files changed

+117
-5
lines changed

ckc/fields.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from rest_framework import serializers
2+
3+
4+
class PrimaryKeyWriteSerializerReadField(serializers.PrimaryKeyRelatedField):
5+
def __init__(self, *args, **kwargs):
6+
read_serializer = kwargs.pop('read_serializer', None)
7+
assert read_serializer is not None, (
8+
'PrimaryKeyWriteSerializerReadField must provide `read_serializer` argument.'
9+
)
10+
super().__init__(*args, **kwargs)
11+
self.read_serializer = read_serializer
12+
13+
# Related fields will not look up any of the object's values for normal primary
14+
# key serialization. This override forces the lookup of the entire object.
15+
def use_pk_only_optimization(self):
16+
return False
17+
18+
def to_representation(self, value):
19+
return self.read_serializer(value, context=self.context).data
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from rest_framework.serializers import ModelSerializer
2+
3+
from ckc.fields import PrimaryKeyWriteSerializerReadField
4+
from testapp.models import AModel, BModel
5+
6+
7+
class MisconfiguredSerializer(ModelSerializer):
8+
a = PrimaryKeyWriteSerializerReadField(
9+
queryset=AModel.objects.all(), # This field requires a read_serializer kwarg
10+
)
11+
12+
class Meta:
13+
model = BModel
14+
fields = (
15+
'id',
16+
'a'
17+
)

testproject/testapp/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ class AModel(SoftDeletableModel):
1212
title = models.CharField(max_length=255, default="I'm a test!")
1313

1414

15+
class BModel(models.Model):
16+
a = models.ForeignKey(AModel, on_delete=models.CASCADE)
17+
18+
1519
class ModelWithACreator(models.Model):
1620
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
1721

testproject/testapp/serializers.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from rest_framework.serializers import ModelSerializer
22

3+
from ckc.fields import PrimaryKeyWriteSerializerReadField
34
from ckc.serializers import DefaultUserCreateMixin
4-
from testapp.models import ModelWithACreator, ModelWithADifferentNamedCreator
5+
from testapp.models import ModelWithACreator, ModelWithADifferentNamedCreator, AModel, BModel
56

67

78
class TestModelWithACreatorSerializer(DefaultUserCreateMixin, ModelSerializer):
@@ -15,3 +16,26 @@ class Meta:
1516
model = ModelWithADifferentNamedCreator
1617
fields = []
1718
user_field = 'owner'
19+
20+
21+
class AModelSerializer(ModelSerializer):
22+
class Meta:
23+
model = AModel
24+
fields = (
25+
'id',
26+
'title',
27+
)
28+
29+
30+
class BModelSerializer(ModelSerializer):
31+
a = PrimaryKeyWriteSerializerReadField(
32+
queryset=AModel.objects.all(),
33+
read_serializer=AModelSerializer,
34+
)
35+
36+
class Meta:
37+
model = BModel
38+
fields = (
39+
'id',
40+
'a'
41+
)

testproject/testapp/viewsets.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from rest_framework.viewsets import ModelViewSet
22

3-
from testapp.models import ModelWithACreator, ModelWithADifferentNamedCreator
4-
from testapp.serializers import TestModelWithACreatorSerializer, TestModelWithADifferentNamedCreatorSerializer
3+
from testapp.models import ModelWithACreator, ModelWithADifferentNamedCreator, BModel
4+
from testapp.serializers import TestModelWithACreatorSerializer, TestModelWithADifferentNamedCreatorSerializer, \
5+
BModelSerializer
56

67

78
class TestModelWithACreatorViewSet(ModelViewSet):
@@ -12,3 +13,8 @@ class TestModelWithACreatorViewSet(ModelViewSet):
1213
class TestModelWithADifferentNamedCreatorViewSet(ModelViewSet):
1314
queryset = ModelWithADifferentNamedCreator.objects.all()
1415
serializer_class = TestModelWithADifferentNamedCreatorSerializer
16+
17+
18+
class BModelViewSet(ModelViewSet):
19+
queryset = BModel.objects.all()
20+
serializer_class = BModelSerializer

testproject/urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from rest_framework import routers
22

3-
from testapp.viewsets import TestModelWithACreatorViewSet, TestModelWithADifferentNamedCreatorViewSet
4-
3+
from testapp.viewsets import TestModelWithACreatorViewSet, TestModelWithADifferentNamedCreatorViewSet, BModelViewSet
54

65
router = routers.SimpleRouter()
76
router.register(r'creators', TestModelWithACreatorViewSet)
87
router.register(r'creators-alternative', TestModelWithADifferentNamedCreatorViewSet)
8+
router.register(r'bmodel', BModelViewSet)
99

1010
urlpatterns = router.urls
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from django.contrib.auth import get_user_model
2+
from django.urls import reverse
3+
from rest_framework.test import APITestCase
4+
5+
from testapp.models import AModel, BModel
6+
7+
User = get_user_model()
8+
9+
10+
class TestDefaultUserCreateMixin(APITestCase):
11+
TEST_TITLE_1 = 'test_title_1'
12+
TEST_TITLE_2 = 'test_title_2'
13+
14+
def setUp(self):
15+
self.user = User.objects.create_user(username="test", password="test")
16+
self.client.force_authenticate(self.user)
17+
self.a_1 = AModel.objects.create(title=self.TEST_TITLE_1)
18+
self.a_2 = AModel.objects.create(title=self.TEST_TITLE_2)
19+
self.b = BModel.objects.create(a=self.a_1)
20+
21+
def test_retrieve_renders_full_serializer_on_field(self):
22+
resp = self.client.get(reverse('bmodel-detail', args=(self.b.pk,)))
23+
assert resp.status_code == 200
24+
assert resp.json()['a']['title'] == self.TEST_TITLE_1
25+
26+
def test_update_accepts_pk_on_field(self):
27+
resp = self.client.patch(
28+
reverse('bmodel-detail', args=(self.b.pk,)),
29+
{
30+
'a': self.a_2.pk,
31+
}
32+
)
33+
assert resp.status_code == 200
34+
assert resp.json()['a']['title'] == self.TEST_TITLE_2
35+
36+
def test_field_fails_if_no_read_serializer_kwarg_passed(self):
37+
try:
38+
from testapp.misconfigured_serializers import MisconfiguredSerializer # noqa
39+
except AssertionError:
40+
pass
41+
else:
42+
assert False, 'MisconfiguredSerializer should have raised AssertionError'

0 commit comments

Comments
 (0)