Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
82db051
Improve Polymorphic handling in optimizer:
diesieben07 Mar 18, 2025
6c53419
Add tests for custom polymorphism with the optimizer
diesieben07 Mar 18, 2025
4a1918d
Apply format and lint fixes
diesieben07 Mar 18, 2025
de7eba6
Fix custom polymorphism tests
diesieben07 Mar 18, 2025
a0f9d3e
Refactor get_possible_concrete_types into utils/inspect.py
diesieben07 Mar 24, 2025
4e85093
Improve readability in get_possible_concrete_types
diesieben07 Mar 24, 2025
139e630
Fix wrong import from refactoring
diesieben07 Mar 24, 2025
13c5706
Implement polymorphic optimization on model relations
diesieben07 Mar 25, 2025
a1aa9f8
Add test for optimized queries in polymorphic context
diesieben07 Mar 25, 2025
d270163
Add test for optimized queries in custom polymorphic context
diesieben07 Mar 25, 2025
3956238
Run formatter
diesieben07 Mar 25, 2025
47147b7
Add support for polymorphic foreign key optimization
diesieben07 Mar 25, 2025
7e0aa2c
Add tests for polymorphic foreign key optimization
diesieben07 Mar 25, 2025
e911379
Run formatter and linter
diesieben07 Mar 25, 2025
383be70
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 25, 2025
692a49b
Fix typing for old Python
diesieben07 Mar 25, 2025
fd47974
Fix Polymorphic optimizer tests for SQL queries
diesieben07 Mar 26, 2025
7574cf1
Implement basic support for Django Model Utils InheritanceManager in …
diesieben07 Mar 25, 2025
4e1d8ab
Fix InheritanceManager support when some subclasses are not in the sc…
diesieben07 Mar 26, 2025
920bb6e
Replace custom code for "path to parent" with Django built-in solution
diesieben07 Mar 26, 2025
b100c25
Add tests for abstract models in polymorphic inheritance tree
diesieben07 Mar 27, 2025
be3cffd
Add tests for abstract models in django-polymorphic
diesieben07 Mar 27, 2025
899b82c
Simplify the custom polymorphism test
diesieben07 Mar 27, 2025
07785f0
Add documentation for polymorphic queries in the optimizer
diesieben07 Mar 27, 2025
d07dd38
Fix subclass collection for InheritanceManager for paginated and conn…
diesieben07 Mar 27, 2025
ffa9fb0
Add multiple inheritance levels test for Django Polymorphic
diesieben07 Mar 27, 2025
6d64676
Fix test_polymorphic_query_multiple_inheritance_levels test
diesieben07 Mar 27, 2025
d21d05d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 27, 2025
1e244bd
Improve documentation wording
diesieben07 Mar 27, 2025
f51c0b8
Merge remote-tracking branch 'origin/optimizer-polymorphic' into opti…
diesieben07 Mar 27, 2025
a4f5d4b
Fix typing
diesieben07 Mar 27, 2025
62d83c4
Rename "Project" model in custom polymorophism test
diesieben07 Mar 27, 2025
bb5a3cb
Update wording to include InheritanceManager
diesieben07 Mar 27, 2025
ebe3a07
Simplify InheritanceManager prefix building
diesieben07 Mar 27, 2025
963dd6a
Use TypeIs instead of TypeGuard
diesieben07 Mar 30, 2025
be91823
Only attempt conditional imports from model-utils and django-polymorp…
diesieben07 Mar 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions docs/guide/optimizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,193 @@ class OrderItem:

`total` now will be properly optimized since it points to a `@model_property`
decorated attribute, which contains the required information for optimizing it.

## Optimizing polymorphic queries

The optimizer has dedicated support for polymorphic queries, that is, fields which return an interface.
The optimizer will handle optimizing any subtypes of the interface as necessary. This is supported on top level queries
as well as relations between models.
See the following sections for how this interacts with your models.

### Using Django Polymorphic

If you are already using the [Django Polymorphic](https://django-polymorphic.readthedocs.io/en/stable/) library,
polymorphic queries work out of the box.

```python title="models.py"
from django.db import models
from polymorphic.models import PolymorphicModel

class Project(PolymorphicModel):
topic = models.CharField(max_length=255)

class ResearchProject(Project):
supervisor = models.CharField(max_length=30)

class ArtProject(Project):
artist = models.CharField(max_length=30)
```

```python title="types.py"
import strawberry
import strawberry_django
from . import models


@strawberry_django.interface(models.Project)
class ProjectType:
topic: strawberry.auto


@strawberry_django.type(models.ResearchProject)
class ResearchProjectType(ProjectType):
supervisor: strawberry.auto


@strawberry_django.type(models.ArtProject)
class ArtProjectType(ProjectType):
artist: strawberry.auto


@strawberry.type
class Query:
projects: list[ProjectType] = strawberry_django.field()
```

The `projects` field will return either ResearchProjectType or ArtProjectType, matching on whether it is a
ResearchProject or ArtProject. The optimizer will make sure to only select those fields from subclasses which are
requested in the GraphQL query in the same way that it does normally.

> [!WARNING]
> The optimizer does not filter your QuerySet and Django will return
> all instances of your model, regardless of whether their type exists in your GraphQL schema or not.
> Make sure you have a corresponding type for every model subclass or add a `get_queryset` method to your
> GraphQL interface type to filter out unwanted subtypes.
> Otherwise you might receive an error like
> `Abstract type 'ProjectType' must resolve to an Object type at runtime for field 'Query.projects'.`

### Using Model-Utils InheritanceManager

Models using `InheritanceManager` from [django-model-utils](https://django-model-utils.readthedocs.io/en/latest/)
are also supported.

```python title="models.py"
from django.db import models
from model_utils.managers import InheritanceManager

class Project(models.Model):
topic = models.CharField(max_length=255)

objects = InheritanceManager()

class ResearchProject(Project):
supervisor = models.CharField(max_length=30)

class ArtProject(Project):
artist = models.CharField(max_length=30)
```

```python title="types.py"
import strawberry
import strawberry_django
from . import models


@strawberry_django.interface(models.Project)
class ProjectType:
topic: strawberry.auto


@strawberry_django.type(models.ResearchProject)
class ResearchProjectType(ProjectType):
supervisor: strawberry.auto


@strawberry_django.type(models.ArtProject)
class ArtProjectType(ProjectType):
artist: strawberry.auto


@strawberry.type
class Query:
projects: list[ProjectType] = strawberry_django.field()
```

The `projects` field will return either ResearchProjectType or ArtProjectType, matching on whether it is a
ResearchProject or ArtProject. The optimizer automatically calls `select_subclasses`, passing in any subtypes present
in your schema.

> [!WARNING]
> The optimizer does not filter your QuerySet and Django will return
> all instances of your model, regardless of whether their type exists in your GraphQL schema or not.
> Make sure you have a corresponding type for every model subclass or add a `get_queryset` method to your
> GraphQL interface type to filter out unwanted subtypes.
> Otherwise you might receive an error like
> `Abstract type 'ProjectType' must resolve to an Object type at runtime for field 'Query.projects'.`

> [!NOTE]
> If you have polymorphic relations (as in: a field that points to a model with subclasses), you need to make sure
> the manager being used to look up the related model is an `InheritanceManager`.
> Strawberry Django uses the model's [base manager](https://docs.djangoproject.com/en/5.1/topics/db/managers/#base-managers)
> by default, which is different from the standard `objects`.
> Either change your base manager to also be an `InheritanceManager` or set Strawberry Django to use the default
> manager: `DjangoOptimizerExtension(prefetch_custom_queryset=True)`.

### Custom polymorphic solution

The optimizer also supports polymorphism even if your models are not polymorphic.
`resolve_type` in the GraphQL interface type is used to tell GraphQL the actual type that should be used.

```python title="models.py"
from django.db import models

class Project(models.Model):
topic = models.CharField(max_length=255)
supervisor = models.CharField(max_length=30)
artist = models.CharField(max_length=30)

```

```python title="types.py"
import strawberry
import strawberry_django
from . import models


@strawberry_django.interface(models.Project)
class ProjectType:
topic: strawberry.auto

@classmethod
def resolve_type(cls, value, info, parent_type) -> str:
if not isinstance(value, models.Project):
raise TypeError()
if value.artist:
return 'ArtProjectType'
if value.supervisor:
return 'ResearchProjectType'
raise TypeError()

@classmethod
def get_queryset(cls, qs, info):
return qs


@strawberry_django.type(models.ResearchProject)
class ResearchProjectType(ProjectType):
supervisor: strawberry.auto


@strawberry_django.type(models.ArtProject)
class ArtProjectType(ProjectType):
artist: strawberry.auto


@strawberry.type
class Query:
projects: list[ProjectType] = strawberry_django.field()
```

> [!WARNING]
> Make sure to add `get_queryset` to your interface type, to force the optimizer to use
> `prefetch_related`, otherwise this technique will not work for relation fields.
17 changes: 16 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ setuptools = "^77.0.3"
psycopg2 = "^2.9.9"
psycopg2-binary = "^2.9.9"
django-tree-queries = "^0.19.0"
django-model-utils = "^5.0.0"

[tool.poetry.extras]
debug-toolbar = ["django-debug-toolbar"]
Expand Down
Loading
Loading