Skip to content

Commit ac93145

Browse files
icerevalsliverc
authored andcommitted
Support polymorphic nested serializer url fields (#485)
1 parent 7a9cca6 commit ac93145

File tree

11 files changed

+140
-5
lines changed

11 files changed

+140
-5
lines changed

‎AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Jonathan Senecal <contact@jonathansenecal.com>
1010
Léo S. <leo@naeka.fr>
1111
Luc Cary <luc.cary@gmail.com>
1212
Matt Layman <https://www.mattlayman.com>
13+
Michael Haselton <icereval@gmail.com>
1314
Ola Tarkowska <ola@red-aries.com>
1415
Oliver Sauder <os@esite.ch>
1516
Raphael Cohen <raphael.cohen.utt@gmail.com>

‎CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
Note that in line with [Django REST Framework policy](http://www.django-rest-framework.org/topics/release-notes/),
99
any parts of the framework not mentioned in the documentation should generally be considered private API, and may be subject to change.
1010

11+
12+
## [Unreleased]
13+
14+
### Added
15+
16+
### Deprecated
17+
18+
### Changed
19+
20+
### Fixed
21+
22+
* Pass context from `PolymorphicModelSerializer` to child serializers to support fields which require a `request` context such as `url`.
23+
24+
1125
## [2.6.0] - 2018-09-20
1226

1327
### Added

‎example/factories.py

+10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
Comment,
1313
Company,
1414
Entry,
15+
ProjectType,
1516
ResearchProject,
1617
TaggedItem
1718
)
@@ -89,12 +90,20 @@ class Meta:
8990
tag = factory.LazyAttribute(lambda x: faker.word())
9091

9192

93+
class ProjectTypeFactory(factory.django.DjangoModelFactory):
94+
class Meta:
95+
model = ProjectType
96+
97+
name = factory.LazyAttribute(lambda x: faker.name())
98+
99+
92100
class ArtProjectFactory(factory.django.DjangoModelFactory):
93101
class Meta:
94102
model = ArtProject
95103

96104
topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
97105
artist = factory.LazyAttribute(lambda x: faker.name())
106+
project_type = factory.SubFactory(ProjectTypeFactory)
98107

99108

100109
class ResearchProjectFactory(factory.django.DjangoModelFactory):
@@ -103,6 +112,7 @@ class Meta:
103112

104113
topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
105114
supervisor = factory.LazyAttribute(lambda x: faker.name())
115+
project_type = factory.SubFactory(ProjectTypeFactory)
106116

107117

108118
class CompanyFactory(factory.django.DjangoModelFactory):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Generated by Django 2.1.1 on 2018-09-22 15:08
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('example', '0004_auto_20171011_0631'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='ProjectType',
16+
fields=[
17+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('created_at', models.DateTimeField(auto_now_add=True)),
19+
('modified_at', models.DateTimeField(auto_now=True)),
20+
('name', models.CharField(max_length=50)),
21+
],
22+
options={
23+
'ordering': ('id',),
24+
},
25+
),
26+
migrations.AlterModelOptions(
27+
name='artproject',
28+
options={'base_manager_name': 'objects'},
29+
),
30+
migrations.AlterModelOptions(
31+
name='project',
32+
options={'base_manager_name': 'objects'},
33+
),
34+
migrations.AlterModelOptions(
35+
name='researchproject',
36+
options={'base_manager_name': 'objects'},
37+
),
38+
migrations.AddField(
39+
model_name='project',
40+
name='project_type',
41+
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='example.ProjectType'),
42+
),
43+
]

‎example/models.py

+12
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,20 @@ class Meta:
119119
ordering = ('id',)
120120

121121

122+
@python_2_unicode_compatible
123+
class ProjectType(BaseModel):
124+
name = models.CharField(max_length=50)
125+
126+
def __str__(self):
127+
return self.name
128+
129+
class Meta:
130+
ordering = ('id',)
131+
132+
122133
class Project(PolymorphicModel):
123134
topic = models.CharField(max_length=30)
135+
project_type = models.ForeignKey(ProjectType, null=True, on_delete=models.CASCADE)
124136

125137

126138
class ArtProject(Project):

‎example/serializers.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Company,
1616
Entry,
1717
Project,
18+
ProjectType,
1819
ResearchProject,
1920
TaggedItem
2021
)
@@ -218,19 +219,34 @@ class Meta:
218219
# fields = ('entry', 'body', 'author',)
219220

220221

221-
class ArtProjectSerializer(serializers.ModelSerializer):
222+
class ProjectTypeSerializer(serializers.ModelSerializer):
223+
class Meta:
224+
model = ProjectType
225+
fields = ('name', 'url',)
226+
227+
228+
class BaseProjectSerializer(serializers.ModelSerializer):
229+
included_serializers = {
230+
'project_type': ProjectTypeSerializer,
231+
}
232+
233+
234+
class ArtProjectSerializer(BaseProjectSerializer):
222235
class Meta:
223236
model = ArtProject
224237
exclude = ('polymorphic_ctype',)
225238

226239

227-
class ResearchProjectSerializer(serializers.ModelSerializer):
240+
class ResearchProjectSerializer(BaseProjectSerializer):
228241
class Meta:
229242
model = ResearchProject
230243
exclude = ('polymorphic_ctype',)
231244

232245

233246
class ProjectSerializer(serializers.PolymorphicModelSerializer):
247+
included_serializers = {
248+
'project_type': ProjectTypeSerializer,
249+
}
234250
polymorphic_serializers = [ArtProjectSerializer, ResearchProjectSerializer]
235251

236252
class Meta:

‎example/tests/integration/test_polymorphism.py

+27
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import pytest
44
from django.urls import reverse
55

6+
from example.factories import ArtProjectFactory, ProjectTypeFactory
7+
68
pytestmark = pytest.mark.django_db
79

810

@@ -57,13 +59,22 @@ def test_polymorphism_on_polymorphic_model_detail_patch(single_art_project, clie
5759
def test_polymorphism_on_polymorphic_model_list_post(client):
5860
test_topic = 'New test topic {}'.format(random.randint(0, 999999))
5961
test_artist = 'test-{}'.format(random.randint(0, 999999))
62+
test_project_type = ProjectTypeFactory()
6063
url = reverse('project-list')
6164
data = {
6265
'data': {
6366
'type': 'artProjects',
6467
'attributes': {
6568
'topic': test_topic,
6669
'artist': test_artist
70+
},
71+
'relationships': {
72+
'projectType': {
73+
'data': {
74+
'type': 'projectTypes',
75+
'id': test_project_type.pk
76+
}
77+
}
6778
}
6879
}
6980
}
@@ -73,6 +84,22 @@ def test_polymorphism_on_polymorphic_model_list_post(client):
7384
assert content['data']['type'] == "artProjects"
7485
assert content['data']['attributes']['topic'] == test_topic
7586
assert content['data']['attributes']['artist'] == test_artist
87+
assert content['data']['relationships']['projectType']['data']['id'] == \
88+
str(test_project_type.pk)
89+
90+
91+
def test_polymorphism_on_polymorphic_model_w_included_serializers(client):
92+
test_project = ArtProjectFactory()
93+
query = '?include=projectType'
94+
url = reverse('project-list')
95+
response = client.get(url + query)
96+
content = response.json()
97+
assert content['data'][0]['id'] == str(test_project.pk)
98+
assert content['data'][0]['type'] == 'artProjects'
99+
assert content['data'][0]['relationships']['projectType']['data']['id'] == \
100+
str(test_project.project_type.pk)
101+
assert content['included'][0]['type'] == 'projectTypes'
102+
assert content['included'][0]['id'] == str(test_project.project_type.pk)
76103

77104

78105
def test_polymorphic_model_without_any_instance(client):

‎example/urls.py

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
EntryRelationshipView,
1414
EntryViewSet,
1515
NonPaginatedEntryViewSet,
16+
ProjectTypeViewset,
1617
ProjectViewset
1718
)
1819

@@ -25,6 +26,7 @@
2526
router.register(r'comments', CommentViewSet)
2627
router.register(r'companies', CompanyViewset)
2728
router.register(r'projects', ProjectViewset)
29+
router.register(r'project-types', ProjectTypeViewset)
2830

2931
urlpatterns = [
3032
url(r'^', include(router.urls)),

‎example/urls_test.py

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
FiltersetEntryViewSet,
1616
NoFiltersetEntryViewSet,
1717
NonPaginatedEntryViewSet,
18+
ProjectTypeViewset,
1819
ProjectViewset
1920
)
2021

@@ -30,6 +31,7 @@
3031
router.register(r'comments', CommentViewSet)
3132
router.register(r'companies', CompanyViewset)
3233
router.register(r'projects', ProjectViewset)
34+
router.register(r'project-types', ProjectTypeViewset)
3335

3436
# for the old tests
3537
router.register(r'identities', Identity)

‎example/views.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313
from rest_framework_json_api.utils import format_drf_errors
1414
from rest_framework_json_api.views import ModelViewSet, RelationshipView
1515

16-
from example.models import Author, Blog, Comment, Company, Entry, Project
16+
from example.models import Author, Blog, Comment, Company, Entry, Project, ProjectType
1717
from example.serializers import (
1818
AuthorSerializer,
1919
BlogSerializer,
2020
CommentSerializer,
2121
CompanySerializer,
2222
EntrySerializer,
23-
ProjectSerializer
23+
ProjectSerializer,
24+
ProjectTypeSerializer
2425
)
2526

2627
HTTP_422_UNPROCESSABLE_ENTITY = 422
@@ -176,6 +177,11 @@ class ProjectViewset(ModelViewSet):
176177
serializer_class = ProjectSerializer
177178

178179

180+
class ProjectTypeViewset(ModelViewSet):
181+
queryset = ProjectType.objects.all()
182+
serializer_class = ProjectTypeSerializer
183+
184+
179185
class EntryRelationshipView(RelationshipView):
180186
queryset = Entry.objects.all()
181187

‎rest_framework_json_api/renderers.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,9 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
595595
if isinstance(serializer.child, rest_framework_json_api.
596596
serializers.PolymorphicModelSerializer):
597597
resource_serializer_class = serializer.child.\
598-
get_polymorphic_serializer_for_instance(resource_instance)()
598+
get_polymorphic_serializer_for_instance(resource_instance)(
599+
context=serializer.child.context
600+
)
599601
else:
600602
resource_serializer_class = serializer.child
601603

0 commit comments

Comments
 (0)