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:
django-stubs (required)
django-stubs-ext (optional)
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:
PolymorphicModel->Model(with a custom through model)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