Refactor models to use auto_prefetch for improved performance and update dependencies in pyproject.toml

This commit is contained in:
Joakim Hellsén 2026-02-09 20:13:47 +01:00
commit 4e67e7299d
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
4 changed files with 261 additions and 30 deletions

View file

@ -16,7 +16,8 @@ dependencies = [
"tqdm", "tqdm",
"colorama", "colorama",
"gunicorn", "gunicorn",
"django-silk>=5.4.3", "django-silk",
"django-auto-prefetch",
] ]
[dependency-groups] [dependency-groups]

View file

@ -0,0 +1,137 @@
# Generated by Django 6.0.2 on 2026-02-09 19:04
from __future__ import annotations
import django.db.models.manager
from django.db import migrations
class Migration(migrations.Migration):
"Alter model options to use prefetch_manager as the base manager and set default ordering for better performance and consistent query results." # noqa: E501
dependencies = [
("twitch", "0007_rename_operation_name_to_operation_names"),
]
operations = [
migrations.AlterModelOptions(
name="channel",
options={"base_manager_name": "prefetch_manager", "ordering": ["display_name"]},
),
migrations.AlterModelOptions(
name="chatbadge",
options={"base_manager_name": "prefetch_manager", "ordering": ["badge_set", "badge_id"]},
),
migrations.AlterModelOptions(
name="chatbadgeset",
options={"base_manager_name": "prefetch_manager", "ordering": ["set_id"]},
),
migrations.AlterModelOptions(
name="dropbenefit",
options={"base_manager_name": "prefetch_manager", "ordering": ["-created_at"]},
),
migrations.AlterModelOptions(
name="dropbenefitedge",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="dropcampaign",
options={"base_manager_name": "prefetch_manager", "ordering": ["-start_at"]},
),
migrations.AlterModelOptions(
name="game",
options={"base_manager_name": "prefetch_manager", "ordering": ["display_name"]},
),
migrations.AlterModelOptions(
name="organization",
options={"base_manager_name": "prefetch_manager", "ordering": ["name"]},
),
migrations.AlterModelOptions(
name="rewardcampaign",
options={"base_manager_name": "prefetch_manager", "ordering": ["-starts_at"]},
),
migrations.AlterModelOptions(
name="timebaseddrop",
options={"base_manager_name": "prefetch_manager", "ordering": ["start_at"]},
),
migrations.AlterModelOptions(
name="twitchgamedata",
options={"base_manager_name": "prefetch_manager", "ordering": ["name"]},
),
migrations.AlterModelManagers(
name="channel",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="chatbadge",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="chatbadgeset",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="dropbenefit",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="dropbenefitedge",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="dropcampaign",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="game",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="organization",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="rewardcampaign",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="timebaseddrop",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="twitchgamedata",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
]

View file

@ -0,0 +1,92 @@
# Generated by Django 6.0.2 on 2026-02-09 19:05
from __future__ import annotations
import auto_prefetch
import django.db.models.deletion
from django.db import migrations
class Migration(migrations.Migration):
"Alter ChatBadge.badge_set to use auto_prefetch.ForeignKey and update related fields to use auto_prefetch.ForeignKey as well for better performance." # noqa: E501
dependencies = [
("twitch", "0008_alter_channel_options_alter_chatbadge_options_and_more"),
]
operations = [
migrations.AlterField(
model_name="chatbadge",
name="badge_set",
field=auto_prefetch.ForeignKey(
help_text="The badge set this badge belongs to.",
on_delete=django.db.models.deletion.CASCADE,
related_name="badges",
to="twitch.chatbadgeset",
verbose_name="Badge Set",
),
),
migrations.AlterField(
model_name="dropbenefitedge",
name="benefit",
field=auto_prefetch.ForeignKey(
help_text="The benefit in this relationship.",
on_delete=django.db.models.deletion.CASCADE,
to="twitch.dropbenefit",
),
),
migrations.AlterField(
model_name="dropbenefitedge",
name="drop",
field=auto_prefetch.ForeignKey(
help_text="The time-based drop in this relationship.",
on_delete=django.db.models.deletion.CASCADE,
to="twitch.timebaseddrop",
),
),
migrations.AlterField(
model_name="dropcampaign",
name="game",
field=auto_prefetch.ForeignKey(
help_text="Game associated with this campaign.",
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch.game",
verbose_name="Game",
),
),
migrations.AlterField(
model_name="rewardcampaign",
name="game",
field=auto_prefetch.ForeignKey(
blank=True,
help_text="Game associated with this reward campaign (if any).",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="reward_campaigns",
to="twitch.game",
),
),
migrations.AlterField(
model_name="timebaseddrop",
name="campaign",
field=auto_prefetch.ForeignKey(
help_text="The campaign this drop belongs to.",
on_delete=django.db.models.deletion.CASCADE,
related_name="time_based_drops",
to="twitch.dropcampaign",
),
),
migrations.AlterField(
model_name="twitchgamedata",
name="game",
field=auto_prefetch.ForeignKey(
blank=True,
help_text="Optional link to the local Game record for this Twitch game.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="twitch_game_data",
to="twitch.game",
verbose_name="Game",
),
),
]

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import auto_prefetch
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
@ -16,7 +17,7 @@ logger: logging.Logger = logging.getLogger("ttvdrops")
# MARK: Organization # MARK: Organization
class Organization(models.Model): class Organization(auto_prefetch.Model):
"""Represents an organization on Twitch that can own drop campaigns.""" """Represents an organization on Twitch that can own drop campaigns."""
twitch_id = models.TextField( twitch_id = models.TextField(
@ -44,7 +45,7 @@ class Organization(models.Model):
help_text="Timestamp when this organization record was last updated.", help_text="Timestamp when this organization record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["name"] ordering = ["name"]
indexes = [ indexes = [
models.Index(fields=["name"]), models.Index(fields=["name"]),
@ -71,7 +72,7 @@ class Organization(models.Model):
# MARK: Game # MARK: Game
class Game(models.Model): class Game(auto_prefetch.Model):
"""Represents a game on Twitch.""" """Represents a game on Twitch."""
twitch_id = models.TextField(verbose_name="Twitch game ID", unique=True) twitch_id = models.TextField(verbose_name="Twitch game ID", unique=True)
@ -124,7 +125,7 @@ class Game(models.Model):
help_text="Timestamp when this game record was last updated.", help_text="Timestamp when this game record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["display_name"] ordering = ["display_name"]
indexes = [ indexes = [
models.Index(fields=["display_name"]), models.Index(fields=["display_name"]),
@ -189,7 +190,7 @@ class Game(models.Model):
# MARK: TwitchGame # MARK: TwitchGame
class TwitchGameData(models.Model): class TwitchGameData(auto_prefetch.Model):
"""Represents game metadata returned from the Twitch API. """Represents game metadata returned from the Twitch API.
This mirrors the public Twitch API fields for a game and is tied to the This mirrors the public Twitch API fields for a game and is tied to the
@ -208,7 +209,7 @@ class TwitchGameData(models.Model):
unique=True, unique=True,
help_text="The Twitch ID for this game.", help_text="The Twitch ID for this game.",
) )
game = models.ForeignKey( game = auto_prefetch.ForeignKey(
Game, Game,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name="twitch_game_data", related_name="twitch_game_data",
@ -237,7 +238,7 @@ class TwitchGameData(models.Model):
help_text="Record last update time.", help_text="Record last update time.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["name"] ordering = ["name"]
indexes = [ indexes = [
models.Index(fields=["name"]), models.Index(fields=["name"]),
@ -253,7 +254,7 @@ class TwitchGameData(models.Model):
# MARK: Channel # MARK: Channel
class Channel(models.Model): class Channel(auto_prefetch.Model):
"""Represents a Twitch channel that can participate in drop campaigns.""" """Represents a Twitch channel that can participate in drop campaigns."""
twitch_id = models.TextField( twitch_id = models.TextField(
@ -279,7 +280,7 @@ class Channel(models.Model):
help_text="Timestamp when this channel record was last updated.", help_text="Timestamp when this channel record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["display_name"] ordering = ["display_name"]
indexes = [ indexes = [
models.Index(fields=["display_name"]), models.Index(fields=["display_name"]),
@ -295,7 +296,7 @@ class Channel(models.Model):
# MARK: DropCampaign # MARK: DropCampaign
class DropCampaign(models.Model): class DropCampaign(auto_prefetch.Model):
"""Represents a Twitch drop campaign.""" """Represents a Twitch drop campaign."""
twitch_id = models.TextField( twitch_id = models.TextField(
@ -355,7 +356,7 @@ class DropCampaign(models.Model):
help_text="Channels that are allowed to participate in this campaign.", help_text="Channels that are allowed to participate in this campaign.",
) )
game = models.ForeignKey( game = auto_prefetch.ForeignKey(
Game, Game,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="drop_campaigns", related_name="drop_campaigns",
@ -378,7 +379,7 @@ class DropCampaign(models.Model):
help_text="Timestamp when this campaign record was last updated.", help_text="Timestamp when this campaign record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["-start_at"] ordering = ["-start_at"]
indexes = [ indexes = [
models.Index(fields=["-start_at"]), models.Index(fields=["-start_at"]),
@ -460,7 +461,7 @@ class DropCampaign(models.Model):
# MARK: DropBenefit # MARK: DropBenefit
class DropBenefit(models.Model): class DropBenefit(auto_prefetch.Model):
"""Represents a benefit that can be earned from a drop.""" """Represents a benefit that can be earned from a drop."""
twitch_id = models.TextField( twitch_id = models.TextField(
@ -515,7 +516,7 @@ class DropBenefit(models.Model):
help_text="Timestamp when this benefit record was last updated.", help_text="Timestamp when this benefit record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["-created_at"] ordering = ["-created_at"]
indexes = [ indexes = [
models.Index(fields=["-created_at"]), models.Index(fields=["-created_at"]),
@ -546,15 +547,15 @@ class DropBenefit(models.Model):
# MARK: DropBenefitEdge # MARK: DropBenefitEdge
class DropBenefitEdge(models.Model): class DropBenefitEdge(auto_prefetch.Model):
"""Link a TimeBasedDrop to a DropBenefit.""" """Link a TimeBasedDrop to a DropBenefit."""
drop = models.ForeignKey( drop = auto_prefetch.ForeignKey(
to="twitch.TimeBasedDrop", to="twitch.TimeBasedDrop",
on_delete=models.CASCADE, on_delete=models.CASCADE,
help_text="The time-based drop in this relationship.", help_text="The time-based drop in this relationship.",
) )
benefit = models.ForeignKey( benefit = auto_prefetch.ForeignKey(
DropBenefit, DropBenefit,
on_delete=models.CASCADE, on_delete=models.CASCADE,
help_text="The benefit in this relationship.", help_text="The benefit in this relationship.",
@ -573,7 +574,7 @@ class DropBenefitEdge(models.Model):
help_text="Timestamp when this drop-benefit edge was last updated.", help_text="Timestamp when this drop-benefit edge was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
constraints = [ constraints = [
models.UniqueConstraint( models.UniqueConstraint(
fields=("drop", "benefit"), fields=("drop", "benefit"),
@ -594,7 +595,7 @@ class DropBenefitEdge(models.Model):
# MARK: TimeBasedDrop # MARK: TimeBasedDrop
class TimeBasedDrop(models.Model): class TimeBasedDrop(auto_prefetch.Model):
"""Represents a time-based drop in a drop campaign.""" """Represents a time-based drop in a drop campaign."""
twitch_id = models.TextField( twitch_id = models.TextField(
@ -626,7 +627,7 @@ class TimeBasedDrop(models.Model):
) )
# Foreign keys # Foreign keys
campaign = models.ForeignKey( campaign = auto_prefetch.ForeignKey(
DropCampaign, DropCampaign,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="time_based_drops", related_name="time_based_drops",
@ -648,7 +649,7 @@ class TimeBasedDrop(models.Model):
help_text=("Timestamp when this time-based drop record was last updated."), help_text=("Timestamp when this time-based drop record was last updated."),
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["start_at"] ordering = ["start_at"]
indexes = [ indexes = [
models.Index(fields=["start_at"]), models.Index(fields=["start_at"]),
@ -672,7 +673,7 @@ class TimeBasedDrop(models.Model):
# MARK: RewardCampaign # MARK: RewardCampaign
class RewardCampaign(models.Model): class RewardCampaign(auto_prefetch.Model):
"""Represents a Twitch reward campaign (Quest rewards).""" """Represents a Twitch reward campaign (Quest rewards)."""
twitch_id = models.TextField( twitch_id = models.TextField(
@ -734,7 +735,7 @@ class RewardCampaign(models.Model):
default=False, default=False,
help_text="Whether the reward campaign is sitewide.", help_text="Whether the reward campaign is sitewide.",
) )
game = models.ForeignKey( game = auto_prefetch.ForeignKey(
Game, Game,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
@ -752,7 +753,7 @@ class RewardCampaign(models.Model):
help_text="Timestamp when this reward campaign record was last updated.", help_text="Timestamp when this reward campaign record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["-starts_at"] ordering = ["-starts_at"]
indexes = [ indexes = [
models.Index(fields=["-starts_at"]), models.Index(fields=["-starts_at"]),
@ -784,7 +785,7 @@ class RewardCampaign(models.Model):
# MARK: ChatBadgeSet # MARK: ChatBadgeSet
class ChatBadgeSet(models.Model): class ChatBadgeSet(auto_prefetch.Model):
"""Represents a set of Twitch global chat badges (e.g., VIP, Subscriber, Bits).""" """Represents a set of Twitch global chat badges (e.g., VIP, Subscriber, Bits)."""
set_id = models.TextField( set_id = models.TextField(
@ -806,7 +807,7 @@ class ChatBadgeSet(models.Model):
help_text="Timestamp when this badge set record was last updated.", help_text="Timestamp when this badge set record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["set_id"] ordering = ["set_id"]
indexes = [ indexes = [
models.Index(fields=["set_id"]), models.Index(fields=["set_id"]),
@ -820,10 +821,10 @@ class ChatBadgeSet(models.Model):
# MARK: ChatBadge # MARK: ChatBadge
class ChatBadge(models.Model): class ChatBadge(auto_prefetch.Model):
"""Represents a specific version of a Twitch global chat badge.""" """Represents a specific version of a Twitch global chat badge."""
badge_set = models.ForeignKey( badge_set = auto_prefetch.ForeignKey(
ChatBadgeSet, ChatBadgeSet,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="badges", related_name="badges",
@ -884,7 +885,7 @@ class ChatBadge(models.Model):
help_text="Timestamp when this badge record was last updated.", help_text="Timestamp when this badge record was last updated.",
) )
class Meta: class Meta(auto_prefetch.Model.Meta):
ordering = ["badge_set", "badge_id"] ordering = ["badge_set", "badge_id"]
constraints = [ constraints = [
models.UniqueConstraint( models.UniqueConstraint(