+ {% for game in games %}
+
+
+
+
-
- {% if discord_settings %}
-
-
Available on iOS: {{ game_data.ios_available|yesno:"Yes,No" }}
-
- {% else %}
-
- {% endif %}
-
-
-
- {% for drop_benefit in game_data.drop_benefits %}
-
-

- {# 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 }}
+
+ {% 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()