diff --git a/core/templates/index.html b/core/templates/index.html index 1668f98..ce37d90 100644 --- a/core/templates/index.html +++ b/core/templates/index.html @@ -1,61 +1,41 @@ {% extends "base.html" %} {% block content %} - {% for organization, org_data in orgs_data.items %} -
- {% for game, game_data in org_data.games.items %} -
-
-
- {{ game.display_name }} -
- +
+ {% for game in games %} +
+
+
+ {{ game.display_name }}
-
- {% if discord_settings %} -
-
Available on iOS: {{ game_data.ios_available|yesno:"Yes,No" }}
-
- {% else %} - - {% endif %} -
-
-
- {% for drop_benefit in game_data.drop_benefits %} -
- {{ drop_benefit.name }} - {# Only show the entitlement limit if it's not None or above 1 #} - {% if drop_benefit.entitlement_limit > 1 %} - {{ drop_benefit.entitlement_limit }} - limit per account - - {% endif %} - {{ drop_benefit.name }} -
- {% endfor %} +
- {% endfor %} -
- {% endfor %} + {% for campaign in game.campaigns %} +
+ {% for drop in campaign.drops %} +
+ {{ drop.name }} drop image + {{ drop.name }} +
+ {% endfor %} +
+ {% endfor %} +
+ {% endfor %} +
{% endblock content %} diff --git a/core/views.py b/core/views.py index 63dae4a..1b1fbfd 100644 --- a/core/views.py +++ b/core/views.py @@ -1,4 +1,6 @@ +import datetime import logging +from dataclasses import dataclass from typing import TYPE_CHECKING from django.contrib import messages @@ -17,7 +19,6 @@ from twitch_app.models import ( DropBenefit, DropCampaign, Game, - Organization, TimeBasedDrop, ) @@ -32,6 +33,47 @@ from django.views.decorators.http import require_POST logger: logging.Logger = logging.getLogger(__name__) +@dataclass +class DropContext: + """The drop.""" + + drops_id: str | None = None + image_url: str | None = None + name: str | None = None + limit: int | None = None + required_minutes_watched: int | None = None + required_subs: int | None = None + + +@dataclass +class CampaignContext: + """Drops are grouped into campaigns.""" + + drop_id: str | None = None + name: str | None = None + image_url: str | None = None + status: str | None = None + account_link_url: str | None = None + description: str | None = None + details_url: str | None = None + ios_available: bool | None = None + start_at: datetime.datetime | None = None + end_at: datetime.datetime | None = None + drops: list[DropContext] | None = None + + +@dataclass +class GameContext: + """Campaigns are under a game.""" + + game_id: str | None = None + campaigns: list[CampaignContext] | None = None + image_url: str | None = None + display_name: str | None = None + twitch_url: str | None = None + slug: str | None = None + + def index(request: HttpRequest) -> HttpResponse: """/ index page. @@ -41,50 +83,61 @@ def index(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: Returns the index page. """ - organizations: BaseManager[Organization] = Organization.objects.all() + list_of_games: list[GameContext] = [] - orgs_data = {org: {"games": {}, "drop_campaigns": []} for org in organizations} - for org in organizations: - drop_benefits: BaseManager[DropBenefit] = DropBenefit.objects.filter( - owner_organization=org, - ) - games: set[Game] = {benefit.game for benefit in drop_benefits} + for game in Game.objects.all(): + campaigns: list[CampaignContext] = [] + for campaign in DropCampaign.objects.filter(game=game, status="ACTIVE"): + drops: list[DropContext] = [] - for game in games: - if game not in orgs_data[org]["games"]: - orgs_data[org]["games"][game] = { - "drop_benefits": [], - "time_based_drops": [], - } + drop: TimeBasedDrop + for drop in campaign.time_based_drops.all(): + benefit: DropBenefit | None = drop.benefits.first() + drops.append( + DropContext( + drops_id=drop.id, + image_url=benefit.image_asset_url if benefit else None, + name=drop.name, + limit=None, + required_minutes_watched=drop.required_minutes_watched, + required_subs=drop.required_subs, + ), + ) - for benefit in drop_benefits: - orgs_data[org]["games"][benefit.game]["drop_benefits"].append(benefit) + if not drops: + logger.info("No drops found for %s", campaign.name) + continue - time_based_drops: BaseManager[TimeBasedDrop] = TimeBasedDrop.objects.filter( - benefits__in=drop_benefits, - ).distinct() - for drop in time_based_drops: - for benefit in drop.benefits.all(): - if benefit.game in orgs_data[org]["games"]: - orgs_data[org]["games"][benefit.game]["time_based_drops"].append( - drop, - ) + campaigns.append( + CampaignContext( + drop_id=campaign.id, + name=campaign.name, + image_url=campaign.image_url, + status=campaign.status, + account_link_url=campaign.account_link_url, + description=campaign.description, + details_url=campaign.details_url, + start_at=campaign.start_at, + end_at=campaign.end_at, + drops=drops, + ), + ) - drop_campaigns: BaseManager[DropCampaign] = DropCampaign.objects.filter( - owner=org, - ) - for campaign in drop_campaigns: - orgs_data[org]["drop_campaigns"].append(campaign) + if not campaigns: + logger.info("No campaigns found for %s", game.display_name) + continue - if request.user.is_authenticated: - discord_settings: BaseManager[DiscordSetting] = DiscordSetting.objects.filter( - user=request.user, + list_of_games.append( + GameContext( + game_id=game.id, + campaigns=campaigns, + image_url=game.image_url, + display_name=game.display_name, + twitch_url=game.twitch_url, + ), ) - context = { - "orgs_data": orgs_data, - "discord_settings": discord_settings if request.user.is_authenticated else None, - } + context: dict[str, list[GameContext]] = {"games": list_of_games} return TemplateResponse( request=request, diff --git a/twitch_app/migrations/0006_alter_dropbenefit_options_alter_dropcampaign_options_and_more.py b/twitch_app/migrations/0006_alter_dropbenefit_options_alter_dropcampaign_options_and_more.py index 1544800..63bd598 100644 --- a/twitch_app/migrations/0006_alter_dropbenefit_options_alter_dropcampaign_options_and_more.py +++ b/twitch_app/migrations/0006_alter_dropbenefit_options_alter_dropcampaign_options_and_more.py @@ -4,14 +4,15 @@ import auto_prefetch import django.db.models.deletion import django.db.models.manager from django.db import migrations +from django.db.migrations.operations.base import Operation class Migration(migrations.Migration): - dependencies = [ + dependencies: list[tuple[str, str]] = [ ("twitch_app", "0005_alter_dropbenefit_options_alter_dropcampaign_options_and_more"), ] - operations = [ + operations: list[Operation] = [ migrations.AlterModelOptions( name="dropbenefit", options={ diff --git a/twitch_app/migrations/0007_alter_dropcampaign_time_based_drops_and_more.py b/twitch_app/migrations/0007_alter_dropcampaign_time_based_drops_and_more.py new file mode 100644 index 0000000..365c558 --- /dev/null +++ b/twitch_app/migrations/0007_alter_dropcampaign_time_based_drops_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-07-02 17:11 + +from django.db import migrations, models +from django.db.migrations.operations.base import Operation + + +class Migration(migrations.Migration): + dependencies: list[tuple[str, str]] = [ + ("twitch_app", "0006_alter_dropbenefit_options_alter_dropcampaign_options_and_more"), + ] + + operations: list[Operation] = [ + migrations.AlterField( + model_name="dropcampaign", + name="time_based_drops", + field=models.ManyToManyField(related_name="drop_campaigns", to="twitch_app.timebaseddrop"), + ), + migrations.AlterField( + model_name="timebaseddrop", + name="benefits", + field=models.ManyToManyField(related_name="time_based_drops", to="twitch_app.dropbenefit"), + ), + ] diff --git a/twitch_app/migrations/0008_alter_dropbenefit_game_and_more.py b/twitch_app/migrations/0008_alter_dropbenefit_game_and_more.py new file mode 100644 index 0000000..4d04bba --- /dev/null +++ b/twitch_app/migrations/0008_alter_dropbenefit_game_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.6 on 2024-07-03 21:19 + +import auto_prefetch +import django.db.models.deletion +from django.db import migrations +from django.db.migrations.operations.base import Operation + + +class Migration(migrations.Migration): + dependencies: list[tuple[str, str]] = [ + ("twitch_app", "0007_alter_dropcampaign_time_based_drops_and_more"), + ] + + operations: list[Operation] = [ + migrations.AlterField( + model_name="dropbenefit", + name="game", + field=auto_prefetch.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="drop_benefits", + to="twitch_app.game", + ), + ), + migrations.AlterField( + model_name="dropbenefit", + name="owner_organization", + field=auto_prefetch.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="drop_benefits", + to="twitch_app.organization", + ), + ), + ] diff --git a/twitch_app/models.py b/twitch_app/models.py index 4e6064e..87ae7bf 100644 --- a/twitch_app/models.py +++ b/twitch_app/models.py @@ -10,6 +10,11 @@ from simple_history.models import HistoricalRecords class Organization(auto_prefetch.Model): + """The company that owns the game. + + For example, 2K games. + """ + id = models.TextField(primary_key=True) name = models.TextField(blank=True, null=True) added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True) @@ -25,6 +30,11 @@ class Organization(auto_prefetch.Model): class Game(auto_prefetch.Model): + """The game that the drop campaign is for. + + For example, MultiVersus. + """ + id = models.TextField(primary_key=True) slug = models.TextField(blank=True, null=True) twitch_url = models.GeneratedField( # type: ignore # noqa: PGH003 @@ -56,6 +66,8 @@ class Game(auto_prefetch.Model): class DropBenefit(auto_prefetch.Model): + """Information about the drop.""" + id = models.TextField(primary_key=True) created_at = models.DateTimeField(blank=True, null=True) entitlement_limit = models.IntegerField(blank=True, null=True) @@ -65,8 +77,9 @@ class DropBenefit(auto_prefetch.Model): owner_organization = auto_prefetch.ForeignKey( Organization, on_delete=models.CASCADE, + related_name="drop_benefits", ) - game = auto_prefetch.ForeignKey(Game, on_delete=models.CASCADE) + game = auto_prefetch.ForeignKey(Game, on_delete=models.CASCADE, related_name="drop_benefits") added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) history = HistoricalRecords() @@ -81,13 +94,15 @@ class DropBenefit(auto_prefetch.Model): class TimeBasedDrop(auto_prefetch.Model): + """The actual drop that is being given out.""" + id = models.TextField(primary_key=True) required_subs = models.IntegerField(blank=True, null=True) end_at = models.DateTimeField(blank=True, null=True) name = models.TextField(blank=True, null=True) required_minutes_watched = models.IntegerField(blank=True, null=True) start_at = models.DateTimeField(blank=True, null=True) - benefits = models.ManyToManyField(DropBenefit) + benefits = models.ManyToManyField(DropBenefit, related_name="time_based_drops") added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) history = HistoricalRecords() @@ -106,6 +121,11 @@ class TimeBasedDrop(auto_prefetch.Model): class DropCampaign(auto_prefetch.Model): + """Drops are grouped into campaigns. + + For example, MultiVersus S1 Drops + """ + id = models.TextField(primary_key=True) account_link_url = models.URLField(blank=True, null=True) description = models.TextField(blank=True, null=True) @@ -125,7 +145,7 @@ class DropCampaign(auto_prefetch.Model): on_delete=models.CASCADE, related_name="drop_campaigns", ) - time_based_drops = models.ManyToManyField(TimeBasedDrop) + time_based_drops = models.ManyToManyField(TimeBasedDrop, related_name="drop_campaigns") added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) history = HistoricalRecords()