Skip to content

Commit d7a2c10

Browse files
authored
Removed support exposing Polymorphic resources as JSON:API endpoints (#1336)
This has been deprecated since version 8.1.0 (one year ago) and will now be removed.
1 parent 0ba9539 commit d7a2c10

24 files changed

Lines changed: 72 additions & 930 deletions

‎CHANGELOG.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ any parts of the framework not mentioned in the documentation should generally b
2525
* Removed support for Django 4.2.
2626
* Removed support for Django 5.1.
2727

28+
### Removed
29+
30+
* Removed support for exposing Polymorphic resources through [django-polymorphic](https://github.com/django-commons/django-polymorphic) as JSON:API endpoints.
31+
2832
## [8.1.0] - 2025-10-17
2933

3034
This is the last release supporting Python 3.9, Django 4.2, Django 5.1, Django REST framework 3.15 and Django REST framework 3.16.
@@ -39,7 +43,7 @@ This is the last release supporting Python 3.9, Django 4.2, Django 5.1, Django R
3943

4044
### Deprecated
4145

42-
* Deprecated support for exposing Polymorphic resources through [django-polymorphic](https://github.com/jazzband/django-polymorphic) as JSON:API endpoints.
46+
* Deprecated support for exposing Polymorphic resources through [django-polymorphic](https://github.com/django-commons/django-polymorphic) as JSON:API endpoints.
4347
There is currently no replacement. In case you are affected of this change please
4448
join [our discussion](https://github.com/django-json-api/django-rest-framework-json-api/discussions/1194).
4549

‎README.rst‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ Install using ``pip``...
113113
$ pip install djangorestframework-jsonapi
114114
$ # for optional package integrations
115115
$ pip install djangorestframework-jsonapi['django-filter']
116-
$ pip install djangorestframework-jsonapi['django-polymorphic']
117116
118117
119118
or from source...

‎docs/getting-started.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ Install using `pip`...
6868
pip install djangorestframework-jsonapi
6969
# for optional package integrations
7070
pip install djangorestframework-jsonapi['django-filter']
71-
pip install djangorestframework-jsonapi['django-polymorphic']
7271

7372
or from source...
7473

‎docs/usage.md‎

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -874,74 +874,6 @@ field_name_mapping = {
874874
}
875875
```
876876

877-
### Working with polymorphic resources
878-
879-
--
880-
881-
**Deprecation notice:**
882-
883-
884-
Support for using Polymorphic resources through [django-polymorphic](https://github.com/jazzband/django-polymorphic) is deprecated and will be removed in a future release.
885-
886-
There is currently no replacement. In case you are affected of this change please join [our discussion](https://github.com/django-json-api/django-rest-framework-json-api/discussions/1194).
887-
888-
---
889-
890-
Polymorphic resources allow you to use specialized subclasses without requiring
891-
special endpoints to expose the specialized versions. For example, if you had a
892-
`Project` that could be either an `ArtProject` or a `ResearchProject`, you can
893-
have both kinds at the same URL.
894-
895-
DJA tests its polymorphic support against [django-polymorphic](https://django-polymorphic.readthedocs.io/en/stable/).
896-
The polymorphic feature should also work with other popular libraries like
897-
django-polymodels or django-typed-models.
898-
899-
As this feature depends on `django-polymorphic` you need to run
900-
901-
pip install djangorestframework-jsonapi['django-polymorphic']
902-
903-
#### Writing polymorphic resources
904-
905-
A polymorphic endpoint can be set up if associated with a polymorphic serializer.
906-
A polymorphic serializer takes care of (de)serializing the correct instances types and can be defined like this:
907-
908-
```python
909-
class ProjectSerializer(serializers.PolymorphicModelSerializer):
910-
polymorphic_serializers = [ArtProjectSerializer, ResearchProjectSerializer]
911-
912-
class Meta:
913-
model = models.Project
914-
```
915-
916-
It must inherit from `serializers.PolymorphicModelSerializer` and define the `polymorphic_serializers` list.
917-
This attribute defines the accepted resource types.
918-
919-
920-
Polymorphic relations can also be handled with `relations.PolymorphicResourceRelatedField` like this:
921-
922-
```python
923-
class CompanySerializer(serializers.ModelSerializer):
924-
current_project = relations.PolymorphicResourceRelatedField(
925-
ProjectSerializer, queryset=models.Project.objects.all())
926-
future_projects = relations.PolymorphicResourceRelatedField(
927-
ProjectSerializer, queryset=models.Project.objects.all(), many=True)
928-
929-
class Meta:
930-
model = models.Company
931-
```
932-
933-
They must be explicitly declared with the `polymorphic_serializer` (first positional argument) correctly defined.
934-
It must be a subclass of `serializers.PolymorphicModelSerializer`.
935-
936-
<div class="warning">
937-
<strong>Note:</strong>
938-
Polymorphic resources are not compatible with
939-
<code class="docutils literal">
940-
<span class="pre">resource_name</span>
941-
</code>
942-
defined on the view.
943-
</div>
944-
945877
### Meta
946878

947879
You may add metadata to the rendered json in two different ways: `meta_fields` and `get_root_meta`.

‎example/factories.py‎

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@
22
from faker import Factory as FakerFactory
33

44
from example.models import (
5-
ArtProject,
65
Author,
76
AuthorBio,
87
AuthorBioMetadata,
98
AuthorType,
109
Blog,
1110
Comment,
12-
Company,
1311
Entry,
1412
ProjectType,
1513
Questionnaire,
16-
ResearchProject,
1714
TaggedItem,
1815
)
1916

@@ -112,41 +109,6 @@ class Meta:
112109
name = factory.LazyAttribute(lambda x: faker.name())
113110

114111

115-
class ArtProjectFactory(factory.django.DjangoModelFactory):
116-
class Meta:
117-
model = ArtProject
118-
119-
topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
120-
artist = factory.LazyAttribute(lambda x: faker.name())
121-
project_type = factory.SubFactory(ProjectTypeFactory)
122-
123-
124-
class ResearchProjectFactory(factory.django.DjangoModelFactory):
125-
class Meta:
126-
model = ResearchProject
127-
128-
topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
129-
supervisor = factory.LazyAttribute(lambda x: faker.name())
130-
project_type = factory.SubFactory(ProjectTypeFactory)
131-
132-
133-
class CompanyFactory(factory.django.DjangoModelFactory):
134-
class Meta:
135-
model = Company
136-
skip_postgeneration_save = True
137-
138-
name = factory.LazyAttribute(lambda x: faker.company())
139-
current_project = factory.SubFactory(ArtProjectFactory)
140-
141-
@factory.post_generation
142-
def future_projects(self, create, extracted, **kwargs):
143-
if not create: # pragma: no cover
144-
return
145-
if extracted:
146-
for project in extracted:
147-
self.future_projects.add(project)
148-
149-
150112
class QuestionnaireFactory(factory.django.DjangoModelFactory):
151113
class Meta:
152114
model = Questionnaire
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Generated by Django 6.0.7 on 2026-10-08 02:18
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("example", "0014_questionnaire_metadata"),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name="company",
15+
name="current_project",
16+
),
17+
migrations.RemoveField(
18+
model_name="company",
19+
name="future_projects",
20+
),
21+
migrations.RemoveField(
22+
model_name="labresults",
23+
name="author",
24+
),
25+
migrations.RemoveField(
26+
model_name="labresults",
27+
name="research_project",
28+
),
29+
migrations.RemoveField(
30+
model_name="project",
31+
name="polymorphic_ctype",
32+
),
33+
migrations.RemoveField(
34+
model_name="project",
35+
name="project_type",
36+
),
37+
migrations.RemoveField(
38+
model_name="researchproject",
39+
name="project_ptr",
40+
),
41+
migrations.DeleteModel(
42+
name="ArtProject",
43+
),
44+
migrations.DeleteModel(
45+
name="Company",
46+
),
47+
migrations.DeleteModel(
48+
name="LabResults",
49+
),
50+
migrations.DeleteModel(
51+
name="Project",
52+
),
53+
migrations.DeleteModel(
54+
name="ResearchProject",
55+
),
56+
]

‎example/models.py‎

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
22
from django.contrib.contenttypes.models import ContentType
33
from django.db import models
4-
from polymorphic.models import PolymorphicModel
54

65

76
class BaseModel(models.Model):
@@ -139,49 +138,6 @@ class Meta:
139138
ordering = ("id",)
140139

141140

142-
class Project(PolymorphicModel):
143-
topic = models.CharField(max_length=30)
144-
project_type = models.ForeignKey(ProjectType, null=True, on_delete=models.CASCADE)
145-
146-
147-
class ArtProject(Project):
148-
artist = models.CharField(max_length=30)
149-
description = models.CharField(max_length=100, null=True)
150-
151-
152-
class ResearchProject(Project):
153-
supervisor = models.CharField(max_length=30)
154-
155-
156-
class LabResults(models.Model):
157-
research_project = models.ForeignKey(
158-
ResearchProject, related_name="lab_results", on_delete=models.CASCADE
159-
)
160-
date = models.DateField()
161-
measurements = models.TextField()
162-
author = models.ForeignKey(
163-
Author,
164-
null=True,
165-
blank=True,
166-
on_delete=models.CASCADE,
167-
related_name="lab_results",
168-
)
169-
170-
class Meta:
171-
ordering = ("id",)
172-
173-
174-
class Company(models.Model):
175-
name = models.CharField(max_length=100)
176-
current_project = models.ForeignKey(
177-
Project, related_name="companies", on_delete=models.CASCADE
178-
)
179-
future_projects = models.ManyToManyField(Project)
180-
181-
def __str__(self):
182-
return self.name
183-
184-
185141
class Questionnaire(models.Model):
186142
name = models.CharField(max_length=100)
187143
questions = models.JSONField()

‎example/serializers.py‎

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
from datetime import datetime
22

3-
from rest_framework import fields as drf_fields
43
from rest_framework import serializers as drf_serilazers
54

65
from rest_framework_json_api import relations, serializers
76

87
from example.models import (
9-
ArtProject,
108
Author,
119
AuthorBio,
1210
AuthorBioMetadata,
1311
AuthorType,
1412
Blog,
1513
Comment,
16-
Company,
1714
Entry,
18-
LabResults,
19-
Project,
2015
ProjectType,
2116
Questionnaire,
22-
ResearchProject,
2317
TaggedItem,
2418
)
2519

@@ -347,83 +341,6 @@ class BaseProjectSerializer(serializers.ModelSerializer):
347341
}
348342

349343

350-
class ArtProjectSerializer(BaseProjectSerializer):
351-
class Meta:
352-
model = ArtProject
353-
exclude = ("polymorphic_ctype",)
354-
355-
356-
class ResearchProjectSerializer(BaseProjectSerializer):
357-
# testing exclusive related field on inherited polymorphic model
358-
lab_results = relations.ResourceRelatedField(many=True, read_only=True)
359-
360-
class Meta:
361-
model = ResearchProject
362-
exclude = ("polymorphic_ctype",)
363-
364-
365-
class LabResultsSerializer(serializers.ModelSerializer):
366-
included_serializers = {"author": AuthorSerializer}
367-
368-
class Meta:
369-
model = LabResults
370-
fields = ("date", "measurements", "author")
371-
372-
373-
class ProjectSerializer(serializers.PolymorphicModelSerializer):
374-
included_serializers = {
375-
"project_type": ProjectTypeSerializer,
376-
}
377-
polymorphic_serializers = [ArtProjectSerializer, ResearchProjectSerializer]
378-
379-
class Meta:
380-
model = Project
381-
exclude = ("polymorphic_ctype",)
382-
383-
384-
class CurrentProjectRelatedField(relations.PolymorphicResourceRelatedField):
385-
def get_attribute(self, instance):
386-
obj = super().get_attribute(instance)
387-
388-
is_art = self.field_name == "current_art_project" and isinstance(
389-
obj, ArtProject
390-
)
391-
is_res = self.field_name == "current_research_project" and isinstance(
392-
obj, ResearchProject
393-
)
394-
395-
if is_art or is_res:
396-
return obj
397-
398-
raise drf_fields.SkipField()
399-
400-
401-
class CompanySerializer(serializers.ModelSerializer):
402-
current_project = relations.PolymorphicResourceRelatedField(
403-
ProjectSerializer, queryset=Project.objects.all()
404-
)
405-
current_art_project = CurrentProjectRelatedField(
406-
ProjectSerializer, source="current_project", read_only=True
407-
)
408-
current_research_project = CurrentProjectRelatedField(
409-
ProjectSerializer, source="current_project", read_only=True
410-
)
411-
future_projects = relations.PolymorphicResourceRelatedField(
412-
ProjectSerializer, queryset=Project.objects.all(), many=True
413-
)
414-
415-
included_serializers = {
416-
"current_project": ProjectSerializer,
417-
"future_projects": ProjectSerializer,
418-
"current_art_project": ProjectSerializer,
419-
"current_research_project": ProjectSerializer,
420-
}
421-
422-
class Meta:
423-
model = Company
424-
fields = "__all__"
425-
426-
427344
class QuestionSerializer(serializers.Serializer):
428345
text = serializers.CharField()
429346
required = serializers.BooleanField(default=False)

0 commit comments

Comments
 (0)