From 4e67e7299de6f7ddbf3270b940514431d6c76a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Mon, 9 Feb 2026 20:13:47 +0100 Subject: [PATCH] Refactor models to use auto_prefetch for improved performance and update dependencies in pyproject.toml --- pyproject.toml | 3 +- ...ptions_alter_chatbadge_options_and_more.py | 137 ++++++++++++++++++ ...0009_alter_chatbadge_badge_set_and_more.py | 92 ++++++++++++ twitch/models.py | 59 ++++---- 4 files changed, 261 insertions(+), 30 deletions(-) create mode 100644 twitch/migrations/0008_alter_channel_options_alter_chatbadge_options_and_more.py create mode 100644 twitch/migrations/0009_alter_chatbadge_badge_set_and_more.py diff --git a/pyproject.toml b/pyproject.toml index c59928f..7eb2f2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,8 @@ dependencies = [ "tqdm", "colorama", "gunicorn", - "django-silk>=5.4.3", + "django-silk", + "django-auto-prefetch", ] [dependency-groups] diff --git a/twitch/migrations/0008_alter_channel_options_alter_chatbadge_options_and_more.py b/twitch/migrations/0008_alter_channel_options_alter_chatbadge_options_and_more.py new file mode 100644 index 0000000..ab678c6 --- /dev/null +++ b/twitch/migrations/0008_alter_channel_options_alter_chatbadge_options_and_more.py @@ -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()), + ], + ), + ] diff --git a/twitch/migrations/0009_alter_chatbadge_badge_set_and_more.py b/twitch/migrations/0009_alter_chatbadge_badge_set_and_more.py new file mode 100644 index 0000000..502578e --- /dev/null +++ b/twitch/migrations/0009_alter_chatbadge_badge_set_and_more.py @@ -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", + ), + ), + ] diff --git a/twitch/models.py b/twitch/models.py index 9373017..7a1fb94 100644 --- a/twitch/models.py +++ b/twitch/models.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING +import auto_prefetch from django.db import models from django.urls import reverse from django.utils import timezone @@ -16,7 +17,7 @@ logger: logging.Logger = logging.getLogger("ttvdrops") # MARK: Organization -class Organization(models.Model): +class Organization(auto_prefetch.Model): """Represents an organization on Twitch that can own drop campaigns.""" twitch_id = models.TextField( @@ -44,7 +45,7 @@ class Organization(models.Model): help_text="Timestamp when this organization record was last updated.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["name"] indexes = [ models.Index(fields=["name"]), @@ -71,7 +72,7 @@ class Organization(models.Model): # MARK: Game -class Game(models.Model): +class Game(auto_prefetch.Model): """Represents a game on Twitch.""" 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.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["display_name"] indexes = [ models.Index(fields=["display_name"]), @@ -189,7 +190,7 @@ class Game(models.Model): # MARK: TwitchGame -class TwitchGameData(models.Model): +class TwitchGameData(auto_prefetch.Model): """Represents game metadata returned from the Twitch API. 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, help_text="The Twitch ID for this game.", ) - game = models.ForeignKey( + game = auto_prefetch.ForeignKey( Game, on_delete=models.SET_NULL, related_name="twitch_game_data", @@ -237,7 +238,7 @@ class TwitchGameData(models.Model): help_text="Record last update time.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["name"] indexes = [ models.Index(fields=["name"]), @@ -253,7 +254,7 @@ class TwitchGameData(models.Model): # MARK: Channel -class Channel(models.Model): +class Channel(auto_prefetch.Model): """Represents a Twitch channel that can participate in drop campaigns.""" twitch_id = models.TextField( @@ -279,7 +280,7 @@ class Channel(models.Model): help_text="Timestamp when this channel record was last updated.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["display_name"] indexes = [ models.Index(fields=["display_name"]), @@ -295,7 +296,7 @@ class Channel(models.Model): # MARK: DropCampaign -class DropCampaign(models.Model): +class DropCampaign(auto_prefetch.Model): """Represents a Twitch drop campaign.""" twitch_id = models.TextField( @@ -355,7 +356,7 @@ class DropCampaign(models.Model): help_text="Channels that are allowed to participate in this campaign.", ) - game = models.ForeignKey( + game = auto_prefetch.ForeignKey( Game, on_delete=models.CASCADE, related_name="drop_campaigns", @@ -378,7 +379,7 @@ class DropCampaign(models.Model): help_text="Timestamp when this campaign record was last updated.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["-start_at"] indexes = [ models.Index(fields=["-start_at"]), @@ -460,7 +461,7 @@ class DropCampaign(models.Model): # MARK: DropBenefit -class DropBenefit(models.Model): +class DropBenefit(auto_prefetch.Model): """Represents a benefit that can be earned from a drop.""" twitch_id = models.TextField( @@ -515,7 +516,7 @@ class DropBenefit(models.Model): help_text="Timestamp when this benefit record was last updated.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["-created_at"] indexes = [ models.Index(fields=["-created_at"]), @@ -546,15 +547,15 @@ class DropBenefit(models.Model): # MARK: DropBenefitEdge -class DropBenefitEdge(models.Model): +class DropBenefitEdge(auto_prefetch.Model): """Link a TimeBasedDrop to a DropBenefit.""" - drop = models.ForeignKey( + drop = auto_prefetch.ForeignKey( to="twitch.TimeBasedDrop", on_delete=models.CASCADE, help_text="The time-based drop in this relationship.", ) - benefit = models.ForeignKey( + benefit = auto_prefetch.ForeignKey( DropBenefit, on_delete=models.CASCADE, 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.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): constraints = [ models.UniqueConstraint( fields=("drop", "benefit"), @@ -594,7 +595,7 @@ class DropBenefitEdge(models.Model): # MARK: TimeBasedDrop -class TimeBasedDrop(models.Model): +class TimeBasedDrop(auto_prefetch.Model): """Represents a time-based drop in a drop campaign.""" twitch_id = models.TextField( @@ -626,7 +627,7 @@ class TimeBasedDrop(models.Model): ) # Foreign keys - campaign = models.ForeignKey( + campaign = auto_prefetch.ForeignKey( DropCampaign, on_delete=models.CASCADE, 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."), ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["start_at"] indexes = [ models.Index(fields=["start_at"]), @@ -672,7 +673,7 @@ class TimeBasedDrop(models.Model): # MARK: RewardCampaign -class RewardCampaign(models.Model): +class RewardCampaign(auto_prefetch.Model): """Represents a Twitch reward campaign (Quest rewards).""" twitch_id = models.TextField( @@ -734,7 +735,7 @@ class RewardCampaign(models.Model): default=False, help_text="Whether the reward campaign is sitewide.", ) - game = models.ForeignKey( + game = auto_prefetch.ForeignKey( Game, on_delete=models.SET_NULL, null=True, @@ -752,7 +753,7 @@ class RewardCampaign(models.Model): help_text="Timestamp when this reward campaign record was last updated.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["-starts_at"] indexes = [ models.Index(fields=["-starts_at"]), @@ -784,7 +785,7 @@ class RewardCampaign(models.Model): # MARK: ChatBadgeSet -class ChatBadgeSet(models.Model): +class ChatBadgeSet(auto_prefetch.Model): """Represents a set of Twitch global chat badges (e.g., VIP, Subscriber, Bits).""" set_id = models.TextField( @@ -806,7 +807,7 @@ class ChatBadgeSet(models.Model): help_text="Timestamp when this badge set record was last updated.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["set_id"] indexes = [ models.Index(fields=["set_id"]), @@ -820,10 +821,10 @@ class ChatBadgeSet(models.Model): # MARK: ChatBadge -class ChatBadge(models.Model): +class ChatBadge(auto_prefetch.Model): """Represents a specific version of a Twitch global chat badge.""" - badge_set = models.ForeignKey( + badge_set = auto_prefetch.ForeignKey( ChatBadgeSet, on_delete=models.CASCADE, related_name="badges", @@ -884,7 +885,7 @@ class ChatBadge(models.Model): help_text="Timestamp when this badge record was last updated.", ) - class Meta: + class Meta(auto_prefetch.Model.Meta): ordering = ["badge_set", "badge_id"] constraints = [ models.UniqueConstraint(