diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4bd3686..0e483e16 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,10 @@ any parts of the framework not mentioned in the documentation should generally b
* Removed support for Django 4.2.
* Removed support for Django 5.1.
+### Removed
+
+* Removed support for exposing Polymorphic resources through [django-polymorphic](https://github.com/django-commons/django-polymorphic) as JSON:API endpoints.
+
## [8.1.0] - 2025-10-17
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
### Deprecated
-* Deprecated support for exposing Polymorphic resources through [django-polymorphic](https://github.com/jazzband/django-polymorphic) as JSON:API endpoints.
+* Deprecated support for exposing Polymorphic resources through [django-polymorphic](https://github.com/django-commons/django-polymorphic) as JSON:API endpoints.
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).
diff --git a/README.rst b/README.rst
index c59d84fe..acaf0724 100644
--- a/README.rst
+++ b/README.rst
@@ -113,7 +113,6 @@ Install using ``pip``...
$ pip install djangorestframework-jsonapi
$ # for optional package integrations
$ pip install djangorestframework-jsonapi['django-filter']
- $ pip install djangorestframework-jsonapi['django-polymorphic']
or from source...
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 2d02c85b..6d0f81f6 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -68,7 +68,6 @@ Install using `pip`...
pip install djangorestframework-jsonapi
# for optional package integrations
pip install djangorestframework-jsonapi['django-filter']
- pip install djangorestframework-jsonapi['django-polymorphic']
or from source...
diff --git a/docs/usage.md b/docs/usage.md
index 6f6ba6af..1c429365 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -874,74 +874,6 @@ field_name_mapping = {
}
```
-### Working with polymorphic resources
-
---
-
-**Deprecation notice:**
-
-
-Support for using Polymorphic resources through [django-polymorphic](https://github.com/jazzband/django-polymorphic) is deprecated and will be removed in a future release.
-
-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).
-
----
-
-Polymorphic resources allow you to use specialized subclasses without requiring
-special endpoints to expose the specialized versions. For example, if you had a
-`Project` that could be either an `ArtProject` or a `ResearchProject`, you can
-have both kinds at the same URL.
-
-DJA tests its polymorphic support against [django-polymorphic](https://django-polymorphic.readthedocs.io/en/stable/).
-The polymorphic feature should also work with other popular libraries like
-django-polymodels or django-typed-models.
-
-As this feature depends on `django-polymorphic` you need to run
-
- pip install djangorestframework-jsonapi['django-polymorphic']
-
-#### Writing polymorphic resources
-
-A polymorphic endpoint can be set up if associated with a polymorphic serializer.
-A polymorphic serializer takes care of (de)serializing the correct instances types and can be defined like this:
-
-```python
-class ProjectSerializer(serializers.PolymorphicModelSerializer):
- polymorphic_serializers = [ArtProjectSerializer, ResearchProjectSerializer]
-
- class Meta:
- model = models.Project
-```
-
-It must inherit from `serializers.PolymorphicModelSerializer` and define the `polymorphic_serializers` list.
-This attribute defines the accepted resource types.
-
-
-Polymorphic relations can also be handled with `relations.PolymorphicResourceRelatedField` like this:
-
-```python
-class CompanySerializer(serializers.ModelSerializer):
- current_project = relations.PolymorphicResourceRelatedField(
- ProjectSerializer, queryset=models.Project.objects.all())
- future_projects = relations.PolymorphicResourceRelatedField(
- ProjectSerializer, queryset=models.Project.objects.all(), many=True)
-
- class Meta:
- model = models.Company
-```
-
-They must be explicitly declared with the `polymorphic_serializer` (first positional argument) correctly defined.
-It must be a subclass of `serializers.PolymorphicModelSerializer`.
-
-
- Note:
- Polymorphic resources are not compatible with
-
- resource_name
-
- defined on the view.
-
-
### Meta
You may add metadata to the rendered json in two different ways: `meta_fields` and `get_root_meta`.
diff --git a/example/factories.py b/example/factories.py
index 87698fa2..765bb024 100644
--- a/example/factories.py
+++ b/example/factories.py
@@ -2,18 +2,15 @@
from faker import Factory as FakerFactory
from example.models import (
- ArtProject,
Author,
AuthorBio,
AuthorBioMetadata,
AuthorType,
Blog,
Comment,
- Company,
Entry,
ProjectType,
Questionnaire,
- ResearchProject,
TaggedItem,
)
@@ -112,41 +109,6 @@ class Meta:
name = factory.LazyAttribute(lambda x: faker.name())
-class ArtProjectFactory(factory.django.DjangoModelFactory):
- class Meta:
- model = ArtProject
-
- topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
- artist = factory.LazyAttribute(lambda x: faker.name())
- project_type = factory.SubFactory(ProjectTypeFactory)
-
-
-class ResearchProjectFactory(factory.django.DjangoModelFactory):
- class Meta:
- model = ResearchProject
-
- topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
- supervisor = factory.LazyAttribute(lambda x: faker.name())
- project_type = factory.SubFactory(ProjectTypeFactory)
-
-
-class CompanyFactory(factory.django.DjangoModelFactory):
- class Meta:
- model = Company
- skip_postgeneration_save = True
-
- name = factory.LazyAttribute(lambda x: faker.company())
- current_project = factory.SubFactory(ArtProjectFactory)
-
- @factory.post_generation
- def future_projects(self, create, extracted, **kwargs):
- if not create: # pragma: no cover
- return
- if extracted:
- for project in extracted:
- self.future_projects.add(project)
-
-
class QuestionnaireFactory(factory.django.DjangoModelFactory):
class Meta:
model = Questionnaire
diff --git a/example/migrations/0015_remove_company_current_project_and_more.py b/example/migrations/0015_remove_company_current_project_and_more.py
new file mode 100644
index 00000000..07ed8ad8
--- /dev/null
+++ b/example/migrations/0015_remove_company_current_project_and_more.py
@@ -0,0 +1,56 @@
+# Generated by Django 6.0.7 on 2026-10-08 02:18
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("example", "0014_questionnaire_metadata"),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name="company",
+ name="current_project",
+ ),
+ migrations.RemoveField(
+ model_name="company",
+ name="future_projects",
+ ),
+ migrations.RemoveField(
+ model_name="labresults",
+ name="author",
+ ),
+ migrations.RemoveField(
+ model_name="labresults",
+ name="research_project",
+ ),
+ migrations.RemoveField(
+ model_name="project",
+ name="polymorphic_ctype",
+ ),
+ migrations.RemoveField(
+ model_name="project",
+ name="project_type",
+ ),
+ migrations.RemoveField(
+ model_name="researchproject",
+ name="project_ptr",
+ ),
+ migrations.DeleteModel(
+ name="ArtProject",
+ ),
+ migrations.DeleteModel(
+ name="Company",
+ ),
+ migrations.DeleteModel(
+ name="LabResults",
+ ),
+ migrations.DeleteModel(
+ name="Project",
+ ),
+ migrations.DeleteModel(
+ name="ResearchProject",
+ ),
+ ]
diff --git a/example/models.py b/example/models.py
index 35a15a8e..feefb225 100644
--- a/example/models.py
+++ b/example/models.py
@@ -1,7 +1,6 @@
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models
-from polymorphic.models import PolymorphicModel
class BaseModel(models.Model):
@@ -139,49 +138,6 @@ class Meta:
ordering = ("id",)
-class Project(PolymorphicModel):
- topic = models.CharField(max_length=30)
- project_type = models.ForeignKey(ProjectType, null=True, on_delete=models.CASCADE)
-
-
-class ArtProject(Project):
- artist = models.CharField(max_length=30)
- description = models.CharField(max_length=100, null=True)
-
-
-class ResearchProject(Project):
- supervisor = models.CharField(max_length=30)
-
-
-class LabResults(models.Model):
- research_project = models.ForeignKey(
- ResearchProject, related_name="lab_results", on_delete=models.CASCADE
- )
- date = models.DateField()
- measurements = models.TextField()
- author = models.ForeignKey(
- Author,
- null=True,
- blank=True,
- on_delete=models.CASCADE,
- related_name="lab_results",
- )
-
- class Meta:
- ordering = ("id",)
-
-
-class Company(models.Model):
- name = models.CharField(max_length=100)
- current_project = models.ForeignKey(
- Project, related_name="companies", on_delete=models.CASCADE
- )
- future_projects = models.ManyToManyField(Project)
-
- def __str__(self):
- return self.name
-
-
class Questionnaire(models.Model):
name = models.CharField(max_length=100)
questions = models.JSONField()
diff --git a/example/serializers.py b/example/serializers.py
index 94fe8556..ecf45e5f 100644
--- a/example/serializers.py
+++ b/example/serializers.py
@@ -1,25 +1,19 @@
from datetime import datetime
-from rest_framework import fields as drf_fields
from rest_framework import serializers as drf_serilazers
from rest_framework_json_api import relations, serializers
from example.models import (
- ArtProject,
Author,
AuthorBio,
AuthorBioMetadata,
AuthorType,
Blog,
Comment,
- Company,
Entry,
- LabResults,
- Project,
ProjectType,
Questionnaire,
- ResearchProject,
TaggedItem,
)
@@ -347,83 +341,6 @@ class BaseProjectSerializer(serializers.ModelSerializer):
}
-class ArtProjectSerializer(BaseProjectSerializer):
- class Meta:
- model = ArtProject
- exclude = ("polymorphic_ctype",)
-
-
-class ResearchProjectSerializer(BaseProjectSerializer):
- # testing exclusive related field on inherited polymorphic model
- lab_results = relations.ResourceRelatedField(many=True, read_only=True)
-
- class Meta:
- model = ResearchProject
- exclude = ("polymorphic_ctype",)
-
-
-class LabResultsSerializer(serializers.ModelSerializer):
- included_serializers = {"author": AuthorSerializer}
-
- class Meta:
- model = LabResults
- fields = ("date", "measurements", "author")
-
-
-class ProjectSerializer(serializers.PolymorphicModelSerializer):
- included_serializers = {
- "project_type": ProjectTypeSerializer,
- }
- polymorphic_serializers = [ArtProjectSerializer, ResearchProjectSerializer]
-
- class Meta:
- model = Project
- exclude = ("polymorphic_ctype",)
-
-
-class CurrentProjectRelatedField(relations.PolymorphicResourceRelatedField):
- def get_attribute(self, instance):
- obj = super().get_attribute(instance)
-
- is_art = self.field_name == "current_art_project" and isinstance(
- obj, ArtProject
- )
- is_res = self.field_name == "current_research_project" and isinstance(
- obj, ResearchProject
- )
-
- if is_art or is_res:
- return obj
-
- raise drf_fields.SkipField()
-
-
-class CompanySerializer(serializers.ModelSerializer):
- current_project = relations.PolymorphicResourceRelatedField(
- ProjectSerializer, queryset=Project.objects.all()
- )
- current_art_project = CurrentProjectRelatedField(
- ProjectSerializer, source="current_project", read_only=True
- )
- current_research_project = CurrentProjectRelatedField(
- ProjectSerializer, source="current_project", read_only=True
- )
- future_projects = relations.PolymorphicResourceRelatedField(
- ProjectSerializer, queryset=Project.objects.all(), many=True
- )
-
- included_serializers = {
- "current_project": ProjectSerializer,
- "future_projects": ProjectSerializer,
- "current_art_project": ProjectSerializer,
- "current_research_project": ProjectSerializer,
- }
-
- class Meta:
- model = Company
- fields = "__all__"
-
-
class QuestionSerializer(serializers.Serializer):
text = serializers.CharField()
required = serializers.BooleanField(default=False)
diff --git a/example/settings/dev.py b/example/settings/dev.py
index 05cab4d1..0eecccdb 100644
--- a/example/settings/dev.py
+++ b/example/settings/dev.py
@@ -25,7 +25,6 @@
"django.contrib.auth",
"rest_framework_json_api",
"rest_framework",
- "polymorphic",
"example",
"django_filters",
"tests",
diff --git a/example/tests/conftest.py b/example/tests/conftest.py
index 6e4b05ba..659b8e0f 100644
--- a/example/tests/conftest.py
+++ b/example/tests/conftest.py
@@ -3,17 +3,14 @@
from rest_framework.test import APIClient
from example.factories import (
- ArtProjectFactory,
AuthorBioFactory,
AuthorBioMetadataFactory,
AuthorFactory,
AuthorTypeFactory,
BlogFactory,
CommentFactory,
- CompanyFactory,
EntryFactory,
QuestionnaireFactory,
- ResearchProjectFactory,
TaggedItemFactory,
)
@@ -25,9 +22,6 @@
register(EntryFactory)
register(CommentFactory)
register(TaggedItemFactory)
-register(ArtProjectFactory)
-register(ResearchProjectFactory)
-register(CompanyFactory)
register(QuestionnaireFactory)
diff --git a/example/tests/integration/test_polymorphism.py b/example/tests/integration/test_polymorphism.py
deleted file mode 100644
index 836a9046..00000000
--- a/example/tests/integration/test_polymorphism.py
+++ /dev/null
@@ -1,268 +0,0 @@
-import random
-
-import pytest
-from django.urls import reverse
-from rest_framework import status
-
-from example.factories import ArtProjectFactory, ProjectTypeFactory
-
-pytestmark = pytest.mark.django_db
-
-
-def test_polymorphism_on_detail(single_art_project, client):
- response = client.get(
- reverse("project-detail", kwargs={"pk": single_art_project.pk})
- )
- content = response.json()
- assert content["data"]["type"] == "artProjects"
-
-
-def test_polymorphism_on_detail_relations(single_company, client):
- response = client.get(reverse("company-detail", kwargs={"pk": single_company.pk}))
- content = response.json()
- assert (
- content["data"]["relationships"]["currentProject"]["data"]["type"]
- == "artProjects"
- )
- assert {
- rel["type"]
- for rel in content["data"]["relationships"]["futureProjects"]["data"]
- } == {"researchProjects", "artProjects"}
-
-
-def test_polymorphism_on_included_relations(single_company, client):
- response = client.get(
- reverse("company-detail", kwargs={"pk": single_company.pk})
- + "?include=current_project,future_projects,current_art_project,current_research_project" # noqa: B950
- )
- content = response.json()
- assert (
- content["data"]["relationships"]["currentProject"]["data"]["type"]
- == "artProjects"
- )
- assert (
- content["data"]["relationships"]["currentArtProject"]["data"]["type"]
- == "artProjects"
- )
- assert content["data"]["relationships"]["currentResearchProject"]["data"] is None
- assert {
- rel["type"]
- for rel in content["data"]["relationships"]["futureProjects"]["data"]
- } == {"researchProjects", "artProjects"}
- assert {x.get("type") for x in content.get("included")} == {
- "artProjects",
- "researchProjects",
- }, "Detail included types are incorrect"
- # Ensure that the child fields are present.
- assert content.get("included")[0].get("attributes").get("artist") is not None
- assert content.get("included")[1].get("attributes").get("artist") is not None
- assert content.get("included")[2].get("attributes").get("supervisor") is not None
-
-
-def test_polymorphism_on_polymorphic_model_detail_patch(single_art_project, client):
- url = reverse("project-detail", kwargs={"pk": single_art_project.pk})
- response = client.get(url)
- content = response.json()
- test_topic = f"test-{random.randint(0, 999999)}"
- test_artist = f"test-{random.randint(0, 999999)}"
- content["data"]["attributes"]["topic"] = test_topic
- content["data"]["attributes"]["artist"] = test_artist
- response = client.patch(url, data=content)
- new_content = response.json()
- assert new_content["data"]["type"] == "artProjects"
- assert new_content["data"]["attributes"]["topic"] == test_topic
- assert new_content["data"]["attributes"]["artist"] == test_artist
-
-
-def test_patch_on_polymorphic_model_without_including_required_field(
- single_art_project, client
-):
- url = reverse("project-detail", kwargs={"pk": single_art_project.pk})
- data = {
- "data": {
- "id": single_art_project.pk,
- "type": "artProjects",
- "attributes": {"description": "New description"},
- }
- }
- response = client.patch(url, data)
- assert response.status_code == status.HTTP_200_OK
- assert response.json()["data"]["attributes"]["description"] == "New description"
-
-
-def test_polymorphism_on_polymorphic_model_list_post(client):
- test_topic = f"New test topic {random.randint(0, 999999)}"
- test_artist = f"test-{random.randint(0, 999999)}"
- test_project_type = ProjectTypeFactory()
- url = reverse("project-list")
- data = {
- "data": {
- "type": "artProjects",
- "attributes": {"topic": test_topic, "artist": test_artist},
- "relationships": {
- "projectType": {
- "data": {"type": "projectTypes", "id": test_project_type.pk}
- }
- },
- }
- }
- response = client.post(url, data=data)
- content = response.json()
- assert content["data"]["id"] is not None
- assert content["data"]["type"] == "artProjects"
- assert content["data"]["attributes"]["topic"] == test_topic
- assert content["data"]["attributes"]["artist"] == test_artist
- assert content["data"]["relationships"]["projectType"]["data"]["id"] == str(
- test_project_type.pk
- )
-
-
-def test_polymorphism_on_polymorphic_model_w_included_serializers(client):
- test_project = ArtProjectFactory()
- query = "?include=projectType"
- url = reverse("project-list")
- response = client.get(url + query)
- content = response.json()
- assert content["data"][0]["id"] == str(test_project.pk)
- assert content["data"][0]["type"] == "artProjects"
- assert content["data"][0]["relationships"]["projectType"]["data"]["id"] == str(
- test_project.project_type.pk
- )
- assert content["included"][0]["type"] == "projectTypes"
- assert content["included"][0]["id"] == str(test_project.project_type.pk)
-
-
-def test_polymorphic_model_without_any_instance(client):
- expected = {
- "links": {
- "first": "http://testserver/projects?page%5Bnumber%5D=1",
- "last": "http://testserver/projects?page%5Bnumber%5D=1",
- "next": None,
- "prev": None,
- },
- "data": [],
- "meta": {"pagination": {"page": 1, "pages": 1, "count": 0}},
- }
-
- response = client.get(reverse("project-list"))
- assert response.status_code == 200
- content = response.json()
- assert expected == content
-
-
-def test_invalid_type_on_polymorphic_model(client):
- test_topic = f"New test topic {random.randint(0, 999999)}"
- test_artist = f"test-{random.randint(0, 999999)}"
- url = reverse("project-list")
- data = {
- "data": {
- "type": "invalidProjects",
- "attributes": {"topic": test_topic, "artist": test_artist},
- }
- }
- response = client.post(url, data=data)
- assert response.status_code == 409
- content = response.json()
- assert len(content["errors"]) == 1
- assert content["errors"][0]["status"] == "409"
- try:
- assert (
- content["errors"][0]["detail"]
- == "The resource object's type (invalidProjects) is not the type that constitute the " # noqa: B950
- "collection represented by the endpoint (one of [researchProjects, artProjects])."
- )
- except AssertionError:
- # Available type list order isn't enforced
- assert (
- content["errors"][0]["detail"]
- == "The resource object's type (invalidProjects) is not the type that constitute the " # noqa: B950
- "collection represented by the endpoint (one of [artProjects, researchProjects])."
- )
-
-
-def test_polymorphism_relations_update(
- single_company, research_project_factory, client
-):
- response = client.get(reverse("company-detail", kwargs={"pk": single_company.pk}))
- content = response.json()
- assert (
- content["data"]["relationships"]["currentProject"]["data"]["type"]
- == "artProjects"
- )
-
- research_project = research_project_factory()
- content["data"]["relationships"]["currentProject"]["data"] = {
- "type": "researchProjects",
- "id": research_project.pk,
- }
- response = client.patch(
- reverse("company-detail", kwargs={"pk": single_company.pk}), data=content
- )
- assert response.status_code == 200
- content = response.json()
- assert (
- content["data"]["relationships"]["currentProject"]["data"]["type"]
- == "researchProjects"
- )
- assert (
- int(content["data"]["relationships"]["currentProject"]["data"]["id"])
- == research_project.pk
- )
-
-
-def test_polymorphism_relations_put_405(
- single_company, research_project_factory, client
-):
- response = client.get(reverse("company-detail", kwargs={"pk": single_company.pk}))
- content = response.json()
- assert (
- content["data"]["relationships"]["currentProject"]["data"]["type"]
- == "artProjects"
- )
-
- research_project = research_project_factory()
- content["data"]["relationships"]["currentProject"]["data"] = {
- "type": "researchProjects",
- "id": research_project.pk,
- }
- response = client.put(
- reverse("company-detail", kwargs={"pk": single_company.pk}), data=content
- )
- assert response.status_code == 405
-
-
-def test_invalid_type_on_polymorphic_relation(
- single_company, research_project_factory, client
-):
- response = client.get(reverse("company-detail", kwargs={"pk": single_company.pk}))
- content = response.json()
- assert (
- content["data"]["relationships"]["currentProject"]["data"]["type"]
- == "artProjects"
- )
-
- research_project = research_project_factory()
- content["data"]["relationships"]["currentProject"]["data"] = {
- "type": "invalidProjects",
- "id": research_project.pk,
- }
- response = client.patch(
- reverse("company-detail", kwargs={"pk": single_company.pk}), data=content
- )
- assert response.status_code == 409
- content = response.json()
- assert len(content["errors"]) == 1
- assert content["errors"][0]["status"] == "409"
- try:
- assert (
- content["errors"][0]["detail"]
- == "Incorrect relation type. Expected one of [researchProjects, artProjects], "
- "received invalidProjects."
- )
- except AssertionError:
- # Available type list order isn't enforced
- assert (
- content["errors"][0]["detail"]
- == "Incorrect relation type. Expected one of [artProjects, researchProjects], "
- "received invalidProjects."
- )
diff --git a/example/tests/test_performance.py b/example/tests/test_performance.py
index ec4a81b2..cae11ed4 100644
--- a/example/tests/test_performance.py
+++ b/example/tests/test_performance.py
@@ -1,11 +1,8 @@
-from datetime import date, timedelta
-from random import randint
-
from django.utils import timezone
from rest_framework.test import APITestCase
from example.factories import CommentFactory, EntryFactory
-from example.models import Author, Blog, Comment, Entry, LabResults, ResearchProject
+from example.models import Author, Blog, Comment, Entry
class PerformanceTestCase(APITestCase):
@@ -87,32 +84,3 @@ def test_query_prefetch_uses_included_resources(self):
"/entries?fields[entries]=comments&page[size]=25"
)
self.assertEqual(len(response.data["results"]), 25)
-
- def test_query_prefetch_read_only(self):
- """We expect a read only list view with an include have five queries:
-
- 1. Primary resource COUNT query
- 2. Primary resource SELECT
- 3. Authors prefetched
- 4. Author types prefetched
- 5. Entries prefetched
- """
- project = ResearchProject.objects.create(
- topic="Mars Mission", supervisor="Elon Musk"
- )
-
- LabResults.objects.bulk_create(
- [
- LabResults(
- research_project=project,
- date=date.today() + timedelta(days=i),
- measurements=randint(0, 10000),
- author=self.author,
- )
- for i in range(20)
- ]
- )
-
- with self.assertNumQueries(5):
- response = self.client.get("/lab-results?include=author&page[size]=25")
- self.assertEqual(len(response.data["results"]), 20)
diff --git a/example/tests/test_serializers.py b/example/tests/test_serializers.py
index 9ad487e5..c9e9d093 100644
--- a/example/tests/test_serializers.py
+++ b/example/tests/test_serializers.py
@@ -12,13 +12,11 @@
DateField,
ModelSerializer,
ResourceIdentifierObjectSerializer,
- empty,
)
from rest_framework_json_api.utils import format_resource_type
-from example.factories import ArtProjectFactory
from example.models import Author, Blog, Entry
-from example.serializers import ArtProjectSerializer, BlogSerializer, ProjectSerializer
+from example.serializers import BlogSerializer
request_factory = APIRequestFactory()
pytestmark = pytest.mark.django_db
@@ -256,51 +254,3 @@ def test_model_serializer_with_subserializers(self, questionnaire, client):
assert response.status_code == 200
assert expected == response.json()
-
-
-class TestPolymorphicModelSerializer(TestCase):
- def setUp(self):
- self.project = ArtProjectFactory.create()
- self.child_init_args = {}
-
- # Override `__init__` with our own method
- def overridden_init(child_self, instance=None, data=empty, **kwargs):
- """
- Override `ArtProjectSerializer.__init__` with the same signature that
- `BaseSerializer.__init__` has to assert that it receives the parameters
- that `BaseSerializer` expects
- """
- self.child_init_args = dict(instance=instance, data=data, **kwargs)
-
- return super(ArtProjectSerializer, child_self).__init__(
- instance, data, **kwargs
- )
-
- self.child_serializer_init = ArtProjectSerializer.__init__
- ArtProjectSerializer.__init__ = overridden_init
-
- def tearDown(self):
- # Restore original init to avoid affecting other tests
- ArtProjectSerializer.__init__ = self.child_serializer_init
-
- def test_polymorphic_model_serializer_passes_instance_to_child(self):
- """
- Ensure that `PolymorphicModelSerializer` is passing the instance to the
- child serializer when initializing them
- """
- # Initialize a serializer that would partially update a model instance
- initial_data = {"artist": "Mark Bishop", "type": "artProjects"}
- parent_serializer = ProjectSerializer(
- instance=self.project, data=initial_data, partial=True
- )
-
- parent_serializer.is_valid(raise_exception=True)
-
- # Run save to force `ProjectSerializer` to init `ArtProjectSerializer`
- parent_serializer.save()
-
- # Assert that child init received the expected arguments
- assert self.child_init_args["instance"] == self.project
- assert self.child_init_args["data"] == initial_data
- assert self.child_init_args["partial"] == parent_serializer.partial
- assert self.child_init_args["context"] == parent_serializer.context
diff --git a/example/urls.py b/example/urls.py
index 471fbe81..b77ba0a2 100644
--- a/example/urls.py
+++ b/example/urls.py
@@ -8,13 +8,10 @@
BlogViewSet,
CommentRelationshipView,
CommentViewSet,
- CompanyViewset,
EntryRelationshipView,
EntryViewSet,
- LabResultViewSet,
NonPaginatedEntryViewSet,
ProjectTypeViewset,
- ProjectViewset,
QuestionnaireViewset,
)
@@ -25,10 +22,7 @@
router.register(r"nopage-entries", NonPaginatedEntryViewSet, "nopage-entry")
router.register(r"authors", AuthorViewSet)
router.register(r"comments", CommentViewSet)
-router.register(r"companies", CompanyViewset)
-router.register(r"projects", ProjectViewset)
router.register(r"project-types", ProjectTypeViewset)
-router.register(r"lab-results", LabResultViewSet)
router.register(r"questionnaires", QuestionnaireViewset)
urlpatterns = [
diff --git a/example/urls_test.py b/example/urls_test.py
index 26d4db21..ae62bf55 100644
--- a/example/urls_test.py
+++ b/example/urls_test.py
@@ -9,17 +9,14 @@
BlogViewSet,
CommentRelationshipView,
CommentViewSet,
- CompanyViewset,
DRFBlogViewSet,
DRFEntryViewSet,
EntryRelationshipView,
EntryViewSet,
FiltersetEntryViewSet,
- LabResultViewSet,
NoFiltersetEntryViewSet,
NonPaginatedEntryViewSet,
ProjectTypeViewset,
- ProjectViewset,
QuestionnaireViewset,
)
@@ -35,10 +32,7 @@
router.register(r"nofilterset-entries", NoFiltersetEntryViewSet, "nofilterset-entry")
router.register(r"authors", AuthorViewSet)
router.register(r"comments", CommentViewSet)
-router.register(r"companies", CompanyViewset)
-router.register(r"projects", ProjectViewset)
router.register(r"project-types", ProjectTypeViewset)
-router.register(r"lab-results", LabResultViewSet)
router.register(r"questionnaires", QuestionnaireViewset)
# for the old tests
diff --git a/example/views.py b/example/views.py
index da171698..94ebca6b 100644
--- a/example/views.py
+++ b/example/views.py
@@ -14,23 +14,9 @@
)
from rest_framework_json_api.pagination import JsonApiPageNumberPagination
from rest_framework_json_api.utils import format_drf_errors
-from rest_framework_json_api.views import (
- ModelViewSet,
- ReadOnlyModelViewSet,
- RelationshipView,
-)
+from rest_framework_json_api.views import ModelViewSet, RelationshipView
-from example.models import (
- Author,
- Blog,
- Comment,
- Company,
- Entry,
- LabResults,
- Project,
- ProjectType,
- Questionnaire,
-)
+from example.models import Author, Blog, Comment, Entry, ProjectType, Questionnaire
from example.serializers import (
AuthorDetailSerializer,
AuthorListSerializer,
@@ -38,11 +24,8 @@
BlogDRFSerializer,
BlogSerializer,
CommentSerializer,
- CompanySerializer,
EntryDRFSerializers,
EntrySerializer,
- LabResultsSerializer,
- ProjectSerializer,
ProjectTypeSerializer,
QuestionnaireSerializer,
)
@@ -258,16 +241,6 @@ def get_queryset(self, *args, **kwargs):
return queryset
-class CompanyViewset(ModelViewSet):
- queryset = Company.objects.all()
- serializer_class = CompanySerializer
-
-
-class ProjectViewset(ModelViewSet):
- queryset = Project.objects.all().order_by("pk")
- serializer_class = ProjectSerializer
-
-
class ProjectTypeViewset(ModelViewSet):
queryset = ProjectType.objects.all()
serializer_class = ProjectTypeSerializer
@@ -290,15 +263,6 @@ class AuthorRelationshipView(RelationshipView):
self_link_view_name = "author-relationships"
-class LabResultViewSet(ReadOnlyModelViewSet):
- queryset = LabResults.objects.all()
- serializer_class = LabResultsSerializer
- prefetch_for_includes = {
- "__all__": [],
- "author": ["author__bio", "author__entries"],
- }
-
-
class QuestionnaireViewset(ModelViewSet):
queryset = Questionnaire.objects.all()
serializer_class = QuestionnaireSerializer
diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt
index e38f445f..c2c7eb1e 100644
--- a/requirements/requirements-optionals.txt
+++ b/requirements/requirements-optionals.txt
@@ -1,2 +1 @@
django-filter==26.1
-django-polymorphic==4.11.7
diff --git a/rest_framework_json_api/parsers.py b/rest_framework_json_api/parsers.py
index 8940c653..30ec85bf 100644
--- a/rest_framework_json_api/parsers.py
+++ b/rest_framework_json_api/parsers.py
@@ -116,9 +116,7 @@ def parse_data(self, result, parser_context):
# Check for inconsistencies
if method in ("PUT", "POST", "PATCH"):
- resource_name = get_resource_name(
- parser_context, expand_polymorphic_types=True
- )
+ resource_name = get_resource_name(parser_context)
if isinstance(resource_name, str):
if data.get("type") != resource_name:
raise exceptions.Conflict(
diff --git a/rest_framework_json_api/relations.py b/rest_framework_json_api/relations.py
index 0742c1ec..244d4e19 100644
--- a/rest_framework_json_api/relations.py
+++ b/rest_framework_json_api/relations.py
@@ -169,7 +169,6 @@ def many_init(cls, *args, **kwargs):
class ResourceRelatedField(HyperlinkedMixin, PrimaryKeyRelatedField):
- _skip_polymorphic_optimization = True
self_link_view_name = None
related_link_view_name = None
related_link_lookup_field = "pk"
@@ -249,7 +248,7 @@ def to_internal_value(self, data):
def to_representation(self, value):
pk = self.get_resource_id(value)
resource_type = self.get_resource_type_from_included_serializer()
- if resource_type is None or not self._skip_polymorphic_optimization:
+ if resource_type is None:
resource_type = get_resource_type_from_instance(value)
return {"type": resource_type, "id": str(pk)}
@@ -310,59 +309,6 @@ def get_choices(self, cutoff=None):
}
-class PolymorphicResourceRelatedField(ResourceRelatedField):
- """
- Inform DRF that the relation must be considered polymorphic.
- Takes a `polymorphic_serializer` as the first positional argument to
- retrieve then validate the accepted types set.
- """
-
- _skip_polymorphic_optimization = False
- default_error_messages = dict(
- ResourceRelatedField.default_error_messages,
- **{
- "incorrect_relation_type": _(
- "Incorrect relation type. Expected one of [{relation_type}], "
- "received {received_type}."
- ),
- },
- )
-
- def __init__(self, polymorphic_serializer, *args, **kwargs):
- self.polymorphic_serializer = polymorphic_serializer
- super().__init__(*args, **kwargs)
-
- def use_pk_only_optimization(self):
- return False
-
- def to_internal_value(self, data):
- if isinstance(data, str):
- try:
- data = json.loads(data)
- except ValueError:
- # show a useful error if they send a `pk` instead of resource object
- self.fail("incorrect_type", data_type=type(data).__name__)
- if not isinstance(data, dict):
- self.fail("incorrect_type", data_type=type(data).__name__)
-
- if "type" not in data:
- self.fail("missing_type")
-
- if "id" not in data:
- self.fail("missing_id")
-
- expected_relation_types = self.polymorphic_serializer.get_polymorphic_types()
-
- if data["type"] not in expected_relation_types:
- self.conflict(
- "incorrect_relation_type",
- relation_type=", ".join(expected_relation_types),
- received_type=data["type"],
- )
-
- return super(ResourceRelatedField, self).to_internal_value(data["id"])
-
-
class SerializerMethodFieldBase(Field):
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
diff --git a/rest_framework_json_api/renderers.py b/rest_framework_json_api/renderers.py
index f418b080..e49484f2 100644
--- a/rest_framework_json_api/renderers.py
+++ b/rest_framework_json_api/renderers.py
@@ -1,7 +1,3 @@
-"""
-Renderers
-"""
-
import copy
from collections import defaultdict
from collections.abc import Iterable
@@ -15,7 +11,6 @@
from rest_framework.serializers import ListSerializer, Serializer
from rest_framework.settings import api_settings
-import rest_framework_json_api
from rest_framework_json_api.relations import (
HyperlinkedMixin,
ManySerializerMethodResourceRelatedField,
@@ -357,7 +352,6 @@ def extract_included(
nested_resource_instance,
resource_type,
serializer,
- getattr(serializer, "_poly_force_type_resolution", False),
)
included_cache[new_item["type"]][new_item["id"]] = new_item
@@ -381,7 +375,6 @@ def extract_included(
relation_instance,
relation_type,
field,
- getattr(field, "_poly_force_type_resolution", False),
)
included_cache[new_item["type"]][new_item["id"]] = new_item
@@ -459,14 +452,8 @@ def build_json_resource_obj(
resource_instance,
resource_name,
serializer,
- force_type_resolution=False,
):
- """
- Builds the resource object (type, id, attributes) and extracts relationships.
- """
- # Determine type from the instance if the underlying model is polymorphic
- if force_type_resolution:
- resource_name = get_resource_type_from_instance(resource_instance)
+ """Builds the resource object (type, id, attributes) and extracts relationships."""
resource_data = {
"type": resource_name,
"id": get_resource_id(resource_instance, resource),
@@ -567,22 +554,8 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
position
] # Get current instance
- if isinstance(
- serializer.child,
- rest_framework_json_api.serializers.PolymorphicModelSerializer,
- ):
- resource_serializer_class = (
- serializer.child.get_polymorphic_serializer_for_instance(
- resource_instance
- )(context=serializer.child.context)
- )
- else:
- resource_serializer_class = serializer.child
-
+ resource_serializer_class = serializer.child
fields = get_serializer_fields(resource_serializer_class)
- force_type_resolution = getattr(
- resource_serializer_class, "_poly_force_type_resolution", False
- )
json_resource_obj = self.build_json_resource_obj(
fields,
@@ -590,7 +563,6 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
resource_instance,
resource_name,
serializer,
- force_type_resolution,
)
json_api_data.append(json_resource_obj)
@@ -603,9 +575,6 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
)
else:
fields = get_serializer_fields(serializer)
- force_type_resolution = getattr(
- serializer, "_poly_force_type_resolution", False
- )
resource_instance = serializer.instance
json_api_data = self.build_json_resource_obj(
@@ -614,7 +583,6 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
resource_instance,
resource_name,
serializer,
- force_type_resolution,
)
self.extract_included(
diff --git a/rest_framework_json_api/serializers.py b/rest_framework_json_api/serializers.py
index 5583d3b9..ea786e31 100644
--- a/rest_framework_json_api/serializers.py
+++ b/rest_framework_json_api/serializers.py
@@ -1,8 +1,6 @@
-import warnings
from collections.abc import Mapping
from django.core.exceptions import ObjectDoesNotExist
-from django.db.models.query import QuerySet
from django.utils.module_loading import import_string as import_class_from_dotted_path
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ParseError
@@ -20,7 +18,6 @@
)
from rest_framework.settings import api_settings
-from rest_framework_json_api.exceptions import Conflict
from rest_framework_json_api.relations import ResourceRelatedField
from rest_framework_json_api.utils import (
get_included_resources,
@@ -326,175 +323,3 @@ def get_field_names(self, declared_fields, info):
}
fields = super().get_field_names(declared, info)
return list(fields) + list(getattr(self.Meta, "meta_fields", list()))
-
-
-class PolymorphicSerializerMetaclass(SerializerMetaclass):
- """
- This metaclass ensures that the `polymorphic_serializers` is correctly defined on a
- `PolymorphicSerializer` class and make a cache of model/serializer/type mappings.
- """
-
- def __new__(cls, name, bases, attrs):
- new_class = super().__new__(cls, name, bases, attrs)
-
- # Ensure initialization is only performed for subclasses of PolymorphicModelSerializer
- # (excluding PolymorphicModelSerializer class itself).
- parents = [b for b in bases if isinstance(b, PolymorphicSerializerMetaclass)]
- if not parents:
- return new_class
-
- warnings.warn(
- DeprecationWarning(
- "Support for polymorphic resources is deprecated."
- "There is currently no replacement. In case you are affected of this "
- "change please join discussion at "
- "https://github.com/django-json-api/django-rest-framework-json-api/discussions/1194."
- ),
- stacklevel=2,
- )
-
- polymorphic_serializers = getattr(new_class, "polymorphic_serializers", None)
- assert (
- polymorphic_serializers is not None
- ), "A PolymorphicModelSerializer must define a `polymorphic_serializers` attribute."
- serializer_to_model = {
- serializer: serializer.Meta.model for serializer in polymorphic_serializers
- }
- model_to_serializer = {
- serializer.Meta.model: serializer for serializer in polymorphic_serializers
- }
- type_to_serializer = {
- get_resource_type_from_serializer(serializer): serializer
- for serializer in polymorphic_serializers
- }
- new_class._poly_serializer_model_map = serializer_to_model
- new_class._poly_model_serializer_map = model_to_serializer
- new_class._poly_type_serializer_map = type_to_serializer
- new_class._poly_force_type_resolution = True
-
- # Flag each linked polymorphic serializer to force type resolution based on instance
- for serializer in polymorphic_serializers:
- serializer._poly_force_type_resolution = True
-
- return new_class
-
-
-class PolymorphicModelSerializer(
- ModelSerializer, metaclass=PolymorphicSerializerMetaclass
-):
- """
- A serializer for polymorphic models.
- Useful for "lazy" parent models. Leaves should be represented with a regular serializer.
- """
-
- def get_fields(self):
- """
- Return an exhaustive list of the polymorphic serializer fields.
- """
- if self.instance not in (None, []):
- if not isinstance(self.instance, QuerySet):
- serializer_class = self.get_polymorphic_serializer_for_instance(
- self.instance
- )
- return serializer_class(
- self.instance, context=self.context
- ).get_fields()
- else:
- raise Exception(
- "Cannot get fields from a polymorphic serializer given a queryset"
- )
- return super().get_fields()
-
- @classmethod
- def get_polymorphic_serializer_for_instance(cls, instance):
- """
- Return the polymorphic serializer associated with the given instance/model.
- Raise `NotImplementedError` if no serializer is found for the given model. This usually
- means that a serializer is missing in the class's `polymorphic_serializers` attribute.
- """
- try:
- return cls._poly_model_serializer_map[instance._meta.model]
- except KeyError:
- raise NotImplementedError(
- "No polymorphic serializer has been found for model {}".format(
- instance._meta.model.__name__
- )
- )
-
- @classmethod
- def get_polymorphic_model_for_serializer(cls, serializer):
- """
- Return the polymorphic model associated with the given serializer.
- Raise `NotImplementedError` if no model is found for the given serializer. This usually
- means that a serializer is missing in the class's `polymorphic_serializers` attribute.
- """
- try:
- return cls._poly_serializer_model_map[serializer]
- except KeyError:
- raise NotImplementedError(
- "No polymorphic model has been found for serializer {}".format(
- serializer.__name__
- )
- )
-
- @classmethod
- def get_polymorphic_serializer_for_type(cls, obj_type):
- """
- Return the polymorphic serializer associated with the given type.
- Raise `NotImplementedError` if no serializer is found for the given type. This usually
- means that a serializer is missing in the class's `polymorphic_serializers` attribute.
- """
- try:
- return cls._poly_type_serializer_map[obj_type]
- except KeyError:
- raise NotImplementedError(
- f"No polymorphic serializer has been found for type {obj_type}"
- )
-
- @classmethod
- def get_polymorphic_model_for_type(cls, obj_type):
- """
- Return the polymorphic model associated with the given type.
- Raise `NotImplementedError` if no model is found for the given type. This usually
- means that a serializer is missing in the class's `polymorphic_serializers` attribute.
- """
- return cls.get_polymorphic_model_for_serializer(
- cls.get_polymorphic_serializer_for_type(obj_type)
- )
-
- @classmethod
- def get_polymorphic_types(cls):
- """
- Return the list of accepted types.
- """
- return cls._poly_type_serializer_map.keys()
-
- def to_representation(self, instance):
- """
- Retrieve the appropriate polymorphic serializer and use this to handle representation.
- """
- serializer_class = self.get_polymorphic_serializer_for_instance(instance)
- return serializer_class(instance, context=self.context).to_representation(
- instance
- )
-
- def to_internal_value(self, data):
- """
- Ensure that the given type is one of the expected polymorphic types, then retrieve the
- appropriate polymorphic serializer and use this to handle internal value.
- """
- received_type = data.get("type")
- expected_types = self.get_polymorphic_types()
- if received_type not in expected_types:
- raise Conflict(
- "Incorrect relation type. Expected on of [{expected_types}], "
- "received {received_type}.".format(
- expected_types=", ".join(expected_types),
- received_type=received_type,
- )
- )
- serializer_class = self.get_polymorphic_serializer_for_type(received_type)
- self.__class__ = serializer_class
- return serializer_class(
- self.instance, data, context=self.context, partial=self.partial
- ).to_internal_value(data)
diff --git a/rest_framework_json_api/utils.py b/rest_framework_json_api/utils.py
index 2dd79677..ac57fdd1 100644
--- a/rest_framework_json_api/utils.py
+++ b/rest_framework_json_api/utils.py
@@ -25,11 +25,10 @@
from django.contrib.contenttypes.fields import ReverseGenericManyToOneDescriptor
-def get_resource_name(context, expand_polymorphic_types=False):
+def get_resource_name(context):
"""
Return the name of a resource.
"""
- from rest_framework_json_api.serializers import PolymorphicModelSerializer
view = context.get("view")
@@ -55,12 +54,7 @@ def get_resource_name(context, expand_polymorphic_types=False):
serializer = view.get_related_serializer_class()
else:
serializer = view.get_serializer_class()
- if expand_polymorphic_types and issubclass(
- serializer, PolymorphicModelSerializer
- ):
- return serializer.get_polymorphic_types()
- else:
- return get_resource_type_from_serializer(serializer)
+ return get_resource_type_from_serializer(serializer)
except AttributeError:
try:
resource_name = get_resource_type_from_model(view.model)
@@ -193,8 +187,6 @@ def format_resource_type(value, format_type=None, pluralize=None):
def get_related_resource_type(relation):
- from rest_framework_json_api.serializers import PolymorphicModelSerializer
-
try:
return get_resource_type_from_serializer(relation)
except AttributeError:
@@ -218,11 +210,7 @@ def get_related_resource_type(relation):
if not relation_model:
parent_serializer = relation.parent
parent_model = None
- if isinstance(parent_serializer, PolymorphicModelSerializer):
- parent_model = parent_serializer.get_polymorphic_serializer_for_instance(
- parent_serializer.instance
- ).Meta.model
- elif hasattr(parent_serializer, "Meta"):
+ if hasattr(parent_serializer, "Meta"):
parent_model = getattr(parent_serializer.Meta, "model", None)
elif hasattr(parent_serializer, "parent") and hasattr(
parent_serializer.parent, "Meta"
diff --git a/setup.cfg b/setup.cfg
index 05c3bd26..92606700 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -63,7 +63,6 @@ DJANGO_SETTINGS_MODULE=example.settings.test
filterwarnings =
error::DeprecationWarning
error::PendingDeprecationWarning
- ignore:Support for polymorphic resources is deprecated
testpaths =
example
tests
diff --git a/setup.py b/setup.py
index 68e91167..e12f2455 100755
--- a/setup.py
+++ b/setup.py
@@ -110,7 +110,6 @@ def get_package_data(package):
"django>=5.2",
],
extras_require={
- "django-polymorphic": ["django-polymorphic>=4.0.0"],
"django-filter": ["django-filter>=2.4"],
},
setup_requires=wheel,