Move organization detail queryset optimizations to model; add tests for organization detail queryset
All checks were successful
Deploy to Server / deploy (push) Successful in 30s
All checks were successful
Deploy to Server / deploy (push) Successful in 30s
This commit is contained in:
parent
3c18b26e9c
commit
1790bac2e0
3 changed files with 96 additions and 11 deletions
|
|
@ -75,6 +75,27 @@ class Organization(auto_prefetch.Model):
|
||||||
"""Return organizations with only fields needed by the org list page."""
|
"""Return organizations with only fields needed by the org list page."""
|
||||||
return cls.objects.only("twitch_id", "name").order_by("name")
|
return cls.objects.only("twitch_id", "name").order_by("name")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_detail_view(cls) -> models.QuerySet[Organization]:
|
||||||
|
"""Return organizations with only fields and relations needed by detail page."""
|
||||||
|
return cls.objects.only(
|
||||||
|
"twitch_id",
|
||||||
|
"name",
|
||||||
|
"added_at",
|
||||||
|
"updated_at",
|
||||||
|
).prefetch_related(
|
||||||
|
models.Prefetch(
|
||||||
|
"games",
|
||||||
|
queryset=Game.objects.only(
|
||||||
|
"twitch_id",
|
||||||
|
"name",
|
||||||
|
"display_name",
|
||||||
|
"slug",
|
||||||
|
).order_by("display_name"),
|
||||||
|
to_attr="games_for_detail",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def feed_description(self: Organization) -> str:
|
def feed_description(self: Organization) -> str:
|
||||||
"""Return a description of the organization for RSS feeds."""
|
"""Return a description of the organization for RSS feeds."""
|
||||||
name: str = self.name or "Unknown Organization"
|
name: str = self.name or "Unknown Organization"
|
||||||
|
|
|
||||||
|
|
@ -2397,6 +2397,73 @@ class TestChannelListView:
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert "organization" in response.context
|
assert "organization" in response.context
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_organization_detail_queryset_only_selects_rendered_fields(self) -> None:
|
||||||
|
"""Organization detail queryset should defer non-rendered organization fields."""
|
||||||
|
org: Organization = Organization.objects.create(
|
||||||
|
twitch_id="org_detail_fields",
|
||||||
|
name="Org Detail Fields",
|
||||||
|
)
|
||||||
|
Game.objects.create(
|
||||||
|
twitch_id="org_detail_game_fields",
|
||||||
|
name="Org Detail Game Fields",
|
||||||
|
display_name="Org Detail Game Fields",
|
||||||
|
).owners.add(org)
|
||||||
|
|
||||||
|
fetched_org: Organization | None = (
|
||||||
|
Organization.for_detail_view().filter(twitch_id=org.twitch_id).first()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert fetched_org is not None
|
||||||
|
deferred_fields: set[str] = fetched_org.get_deferred_fields()
|
||||||
|
assert "twitch_id" not in deferred_fields
|
||||||
|
assert "name" not in deferred_fields
|
||||||
|
assert "added_at" not in deferred_fields
|
||||||
|
assert "updated_at" not in deferred_fields
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_organization_detail_prefetched_games_do_not_trigger_extra_queries(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
"""Organization detail should prefetch games used by the template."""
|
||||||
|
org: Organization = Organization.objects.create(
|
||||||
|
twitch_id="org_detail_prefetch",
|
||||||
|
name="Org Detail Prefetch",
|
||||||
|
)
|
||||||
|
game: Game = Game.objects.create(
|
||||||
|
twitch_id="org_detail_prefetch_game",
|
||||||
|
name="Org Detail Prefetch Game",
|
||||||
|
display_name="Org Detail Prefetch Game",
|
||||||
|
slug="org-detail-prefetch-game",
|
||||||
|
)
|
||||||
|
game.owners.add(org)
|
||||||
|
|
||||||
|
fetched_org: Organization = Organization.for_detail_view().get(
|
||||||
|
twitch_id=org.twitch_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
games_for_detail: list[Game] = list(
|
||||||
|
getattr(fetched_org, "games_for_detail", []),
|
||||||
|
)
|
||||||
|
assert len(games_for_detail) == 1
|
||||||
|
|
||||||
|
with CaptureQueriesContext(connection) as queries:
|
||||||
|
game_row: Game = games_for_detail[0]
|
||||||
|
_ = game_row.twitch_id
|
||||||
|
_ = game_row.display_name
|
||||||
|
_ = game_row.name
|
||||||
|
_ = str(game_row)
|
||||||
|
|
||||||
|
select_queries: list[str] = [
|
||||||
|
query_info["sql"]
|
||||||
|
for query_info in queries.captured_queries
|
||||||
|
if query_info["sql"].lstrip().upper().startswith("SELECT")
|
||||||
|
]
|
||||||
|
assert not select_queries, (
|
||||||
|
"Organization detail prefetched games triggered unexpected SELECT queries. "
|
||||||
|
f"Queries: {select_queries}"
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_channel_detail_view(self, client: Client, db: None) -> None:
|
def test_channel_detail_view(self, client: Client, db: None) -> None:
|
||||||
"""Test channel detail view returns 200 and has channel in context."""
|
"""Test channel detail view returns 200 and has channel in context."""
|
||||||
|
|
|
||||||
|
|
@ -316,21 +316,18 @@ def organization_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespon
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered organization detail page.
|
HttpResponse: The rendered organization detail page.
|
||||||
|
|
||||||
Raises:
|
|
||||||
Http404: If the organization is not found.
|
|
||||||
"""
|
"""
|
||||||
try:
|
organization: Organization = get_object_or_404(
|
||||||
organization: Organization = Organization.objects.get(twitch_id=twitch_id)
|
Organization.for_detail_view(),
|
||||||
except Organization.DoesNotExist as exc:
|
twitch_id=twitch_id,
|
||||||
msg = "No organization found matching the query"
|
)
|
||||||
raise Http404(msg) from exc
|
|
||||||
|
|
||||||
games: QuerySet[Game] = organization.games.all() # pyright: ignore[reportAttributeAccessIssue]
|
games: list[Game] = list(getattr(organization, "games_for_detail", []))
|
||||||
|
|
||||||
org_name: str = organization.name or organization.twitch_id
|
org_name: str = organization.name or organization.twitch_id
|
||||||
games_count: int = games.count()
|
games_count: int = len(games)
|
||||||
s: Literal["", "s"] = "" if games_count == 1 else "s"
|
noun: str = "game" if games_count == 1 else "games"
|
||||||
org_description: str = f"{org_name} has {games_count} game{s}."
|
org_description: str = f"{org_name} has {games_count} {noun}."
|
||||||
|
|
||||||
url: str = build_absolute_uri(
|
url: str = build_absolute_uri(
|
||||||
reverse("twitch:organization_detail", args=[organization.twitch_id]),
|
reverse("twitch:organization_detail", args=[organization.twitch_id]),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue