Type Hints

Added in version 4.11.

django-polymorphic is now fully typed, and ships with type hints for all public APIs. Typing is checked with mypy and pyright in the CI pipeline.

The utility and power of the Django ORM derives from its dynamism but this makes static typing more difficult. There are no additional runtime dependencies but to use the packaged type hint classes and descriptors, you must install the following in your type checking context:

Tip

The most useful type hints to add to your own code will make it so your type checking system knows which model types your PolymorphicManager and PolymorphicQuerySet objects might return.

Correct type hints for polymorphic managers and querysets cannot be automatically inferred - you will have to add them explicitly if you want them:

Managers

You can type your managers like this. It might not always be the case that you can add hints for all child model types, especially if they are included in dependent apps. You can alleviate some of this complexity with forward type references but strict typing may not always be appropriate.

 1from __future__ import annotations
 2import typing as t
 3from typing_extensions import Self
 4from polymorphic.models import PolymorphicModel
 5from polymorphic.managers import PolymorphicManager
 6
 7
 8class ParentModel(PolymorphicModel):
 9    # fmt: off
10    # If you want a polymorphic manager with type hint support you can
11    # override the default one like this:
12    objects: t.ClassVar[
13        PolymorphicManager[
14            Self | Child1 | Child2,  # union of all polymorphic types
15            Self,                    # the base type (for non_polymorphic)
16        ]
17    ] = PolymorphicManager()
18    # fmt: on
19
20
21class Child1(ParentModel):
22    # you may also override the type hints for the child default
23    # managers to narrow the filter returns at this level
24    objects: t.ClassVar[PolymorphicManager[Self | Child2, Self]]
25
26
27class Child2(Child1):
28    objects: t.ClassVar[PolymorphicManager[Self, Self]]
ParentModel.objects.all()  # type: PolymorphicQuerySet[ParentModel | Child1 | Child2]
ParentModel.objects.instance_of(Child1)  # type: PolymorphicQuerySet[Child1]
Child1.objects.non_polymorphic()  # type: QuerySet[Child1]

Foreign Key

django-polymorphic includes several type hint descriptors. You can use them to type your forward and reverse relationship fields. For foreign key relationships we provide PolymorphicForwardManyToOneDescriptor and PolymorphicReverseManyToOneDescriptor:

 1from django.db import models
 2from polymorphic.models import PolymorphicModel
 3from polymorphic.managers import (
 4    PolymorphicForwardManyToOneDescriptor,
 5    PolymorphicReverseManyToOneDescriptor,
 6    Nullable,  # Alias for typing.Literal[True]
 7)
 8
 9
10class ParentModel(PolymorphicModel):
11    related_forward = models.ForeignKey(
12        "RelatedModel", on_delete=models.CASCADE, related_name="parents_reverse"
13    )
14
15
16class Child1(ParentModel):
17    pass
18
19
20class Child2(Child1):
21    pass
22
23
24class RelatedModel(models.Model):
25    # fmt: off
26    # Foreign Key Descriptor Type Hint:
27    #   1. Class Attribute: ForwardManyToOneDescriptor
28    #   2. Instance attribute: Union of all listed model types or None
29    parent: PolymorphicForwardManyToOneDescriptor[
30        ParentModel | Child1 | Child2,  # all possible polymorphic types
31        ParentModel,                    # the base type (for non_polymorphic)
32        Nullable,                       # when null=True
33    ] = models.ForeignKey(              # type: ignore[assignment]
34        ParentModel,
35        on_delete=models.CASCADE,
36        null=True,
37    )
38
39    # Reverse FK Relation Descriptor Type Hint:
40    #   1. Class Attribute: ReverseManyToOneDescriptor
41    #   2. Instance attribute: Union of all listed model types
42    parents_reverse: PolymorphicReverseManyToOneDescriptor[
43        ParentModel | Child1 | Child2,  # all possible polymorphic types
44        ParentModel,                    # the base type (for non_polymorphic)
45                                        # nullable defaults to False
46    ]
47    # fmt: on
RelatedModel().parent: Optional[ParentModel | Child1 | Child2]
RelatedModel().children.all(): PolymorphicQuerySet[ParentModel | Child1 | Child2]

One to One

For foreign key relationships we provide PolymorphicForwardOneToOneDescriptor and PolymorphicReverseOneToOneDescriptor:

 1from django.db import models
 2from polymorphic.models import PolymorphicModel
 3from polymorphic.managers import (
 4    PolymorphicForwardOneToOneDescriptor,
 5    PolymorphicReverseOneToOneDescriptor,
 6    Nullable,  # Alias for typing.Literal[True]
 7)
 8
 9
10class ParentModel(PolymorphicModel):
11    related_forward = models.OneToOneField(
12        "RelatedModel", on_delete=models.CASCADE, related_name="parent_reverse"
13    )
14
15
16class Child1(ParentModel):
17    pass
18
19
20class Child2(Child1):
21    pass
22
23
24class RelatedModel(models.Model):
25    # fmt: off
26    # Forward Relation Descriptor Type Hint:
27    #   1. Class Attribute: ForwardOneToOneDescriptor
28    #   2. Instance attribute: Union of all listed model types or None
29    parent_forward: PolymorphicForwardOneToOneDescriptor[
30        ParentModel | Child1 | Child2,  # all possible polymorphic types
31        ParentModel,                    # the base type (for non_polymorphic)
32        Nullable,                       # when null=True
33    ] = models.OneToOneField(           # type: ignore[assignment]
34        "ParentModel",
35        on_delete=models.CASCADE,
36        null=True
37    )
38
39    # Reverse Relation Descriptor Type Hint:
40    #   1. Class Attribute: ReverseOneToOneDescriptor
41    #   2. Instance attribute: Union of all listed model types
42    parent_reverse: PolymorphicReverseOneToOneDescriptor[
43        ParentModel | Child1 | Child2,  # possible polymorphic types
44        ParentModel,                    # the base type (for non_polymorphic)
45                                        # nullable defaults to False
46    ]
47    # fmt: on
RelatedModel().parent_forward: ParentModel | Child1 | Child2 | None
RelatedModel().parent_reverse: ParentModel | Child1 | Child2

Many to Many

You can use the same PolymorphicManyToManyDescriptor for both forward and reverse ManyToManyField relationships.

The following example shows two ManyToManyField relationships:

  1. PolymorphicModel -> Model (with a custom through model)

  2. Model -> PolymorphicModel (with the default through model)

For the custom through model you will need to annotate using the foreign key descriptors as well.

 1from __future__ import annotations
 2from typing import ClassVar
 3from django.db import models
 4from polymorphic.models import PolymorphicModel
 5from polymorphic.managers import (
 6    PolymorphicManager,
 7    PolymorphicManyToManyDescriptor,
 8    PolymorphicForwardManyToOneDescriptor,
 9    PolymorphicReverseManyToOneDescriptor,
10)
11
12
13class ParentModel(PolymorphicModel):
14    to_related = models.ManyToManyField(  # type: ignore[var-annotated]
15        "RelatedModel", related_name="to_related_reverse", through="PolyThrough"
16    )
17
18    parents: PolymorphicReverseManyToOneDescriptor[
19        ParentModel | Child1 | Child2, ParentModel
20    ]
21
22    objects: ClassVar[
23        PolymorphicManager[ParentModel | Child1 | Child2, ParentModel]
24    ]
25
26
27class Child1(ParentModel):
28    pass
29
30
31class Child2(Child1):
32    pass
33
34
35class PolyThrough(PolymorphicModel):
36    parent: PolymorphicForwardManyToOneDescriptor[
37        ParentModel | Child1 | Child2, ParentModel
38    ] = models.ForeignKey(  # type: ignore[assignment]
39        ParentModel, on_delete=models.CASCADE, related_name="parents"
40    )
41
42    related = models.ForeignKey("RelatedModel", on_delete=models.CASCADE)
43
44    objects: ClassVar[
45        PolymorphicManager[PolyThrough | ThroughChild, PolyThrough]
46    ]
47
48
49class ThroughChild(PolyThrough):
50    pass
51
52
53class RelatedModel(models.Model):
54    # fmt: off
55    # ManyToMany Descriptor Type Hint:
56    #   1. Class Attribute: ManyToManyDescriptor
57    #   2. Instance attribute: PolymorphicManager for related type(s)
58    to_parents: PolymorphicManyToManyDescriptor[
59        ParentModel | Child1 | Child2,  # all possible polymorphic types
60        ParentModel,                    # the base type (for non_polymorphic)
61                                        # no custom through model
62    ] = models.ManyToManyField(         # type: ignore[assignment]
63        "ParentModel",
64        related_name="to_parents_reverse"
65    )
66
67    # ManyToMany Descriptor for the reverse relation
68    #   1. Class Attribute: ManyToManyDescriptor
69    #   2. Instance attribute: PolymorphicManager for related type(s)
70    to_related_reverse: PolymorphicManyToManyDescriptor[
71        ParentModel | Child1 | Child2,
72        ParentModel,
73        PolyThrough  # custom through model (may be polymorphic!)
74    ]
75    # fmt: on