Skip to content
This repository has been archived by the owner on Jul 8, 2023. It is now read-only.

Commit

Permalink
feat: use a type's get_queryset for Relay connections if it defines o…
Browse files Browse the repository at this point in the history
…ne (#215)

* Use a type's get_queryset for Relay connections

Why:

- Allow nested queries to be filtered by user and permissions

This change addresses the need by:

- Resolve reverse relationship type in Relay connections
- Use get_queryset filter of the resolved type

* Update strawberry_django_plus/type.py

Co-authored-by: Thiago Bellini Ribeiro <hackedbellini@gmail.com>

* Clean up conn_resolver

* Preventive rename to avoid name collision

* Try fix for Python < 3.10

* Simplify implementation

* Add test for Relay Connection resolving

Why:

- Ensure no regressions when querying related models
  - Caught a case where prefetches were ignored
- Allow tests to use a type's custom get_queryset method

This change addresses the need by:

- Adding a Favorite model and type
- Adding a test to check the new use case
- Documenting how to access custom QuerySets in types
- Warm up lazy user in test client to avoid sync/async collision

* Lint clean up

* Fix typing checks

* Add user warmup middleware

Why:

- get_queryset in types only supports sync context
- Avoids error being called in async context

This change addresses the need by:

- Adding a user warmup middleware to cache user object in async context

* Clean up typing

* Actually warmup user with method call

---------

Co-authored-by: Thiago Bellini Ribeiro <hackedbellini@gmail.com>
  • Loading branch information
moritz89 and bellini666 authored Jun 1, 2023
1 parent 4d640e7 commit bb3af76
Show file tree
Hide file tree
Showing 12 changed files with 407 additions and 60 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ exclude_lines =
@abstractmethod
@abc.abstractmethod
assert_never
omit =
*/tests/*
38 changes: 38 additions & 0 deletions demo/migrations/0006_favorite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 4.2.1 on 2023-05-30 05:00

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("demo", "0005_remove_issue_milestone_not_null"),
]

operations = [
migrations.CreateModel(
name="Favorite",
fields=[
("id", models.BigAutoField(primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=32)),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="favorite_set",
to="demo.issue",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="favorite_set",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
27 changes: 27 additions & 0 deletions demo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

from django.contrib.auth import get_user_model
from django.db import models
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from django_choices_field.fields import TextChoicesField

from strawberry_django_plus.descriptors import model_property
from strawberry_django_plus.utils.typing import UserType

if TYPE_CHECKING:
from django.db.models.manager import RelatedManager
Expand Down Expand Up @@ -73,6 +75,31 @@ class Milestone(models.Model):
)


class FavoriteQuerySet(QuerySet):
def by_user(self, user: UserType):
if user.is_anonymous:
return self.none()
return self.filter(user__pk=user.pk)


class Favorite(models.Model):
"""A user's favorite issues."""

class Meta:
# Needed to allow type's get_queryset() to access a model's custom QuerySet
base_manager_name = "objects"

id = models.BigAutoField( # noqa: A003
verbose_name="ID",
primary_key=True,
)
name = models.CharField(max_length=32)
user = models.ForeignKey(User, related_name="favorite_set", on_delete=models.CASCADE)
issue = models.ForeignKey("Issue", related_name="favorite_set", on_delete=models.CASCADE)

objects = FavoriteQuerySet.as_manager()


class Issue(models.Model):
comments: "RelatedManager[Issue]"
issue_assignees: "RelatedManager[Assignee]"
Expand Down
16 changes: 15 additions & 1 deletion demo/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
IsSuperuser,
)

from .models import Assignee, Issue, Milestone, Project, Quiz, Tag
from .models import Assignee, Favorite, FavoriteQuerySet, Issue, Milestone, Project, Quiz, Tag

UserModel = cast(Type[AbstractUser], get_user_model())

Expand Down Expand Up @@ -122,6 +122,17 @@ async def async_field(self, value: str) -> str:
return f"value: {value}"


@gql.django.type(Favorite)
class FavoriteType(relay.Node):
name: gql.auto
user: UserType
issue: "IssueType"

@classmethod
def get_queryset(cls, queryset: FavoriteQuerySet, info: Info) -> QuerySet:
return queryset.by_user(info.context.request.user)


@gql.django.type(Issue)
class IssueType(relay.Node):
name: gql.auto
Expand All @@ -132,6 +143,7 @@ class IssueType(relay.Node):
name_with_kind: str = gql.django.field(only=["kind", "name"])
tags: List["TagType"]
issue_assignees: List["AssigneeType"]
favorite_set: relay.Connection["FavoriteType"]


@gql.django.type(Tag)
Expand Down Expand Up @@ -218,6 +230,7 @@ class Query:

node: Optional[gql.Node] = gql.django.node()

favorite: Optional[FavoriteType] = gql.django.node()
issue: Optional[IssueType] = gql.django.node(description="Foobar")
milestone: Optional[Annotated["MilestoneType", gql.lazy("demo.schema")]] = gql.django.node()
milestone_mandatory: MilestoneType = gql.django.node()
Expand All @@ -237,6 +250,7 @@ class Query:
project_list: List[ProjectType] = gql.django.field()
tag_list: List[TagType] = gql.django.field()

favorite_conn: relay.Connection[FavoriteType] = gql.django.connection()
issue_conn: relay.Connection[
gql.LazyType["IssueType", "demo.schema"] # type: ignore # noqa: F821
] = gql.django.connection()
Expand Down
1 change: 1 addition & 0 deletions demo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"strawberry_django_plus.middlewares.user_warmup.user_warmup_middleware",
"strawberry_django_plus.middlewares.debug_toolbar.DebugToolbarMiddleware",
]

Expand Down
Loading

0 comments on commit bb3af76

Please sign in to comment.