Rewrite index view

This commit is contained in:
2024-07-03 23:50:08 +02:00
parent fb7d9ea4a7
commit 344a656056
6 changed files with 206 additions and 96 deletions

View File

@ -1,61 +1,41 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
{% for organization, org_data in orgs_data.items %} <div class="container mt-4">
<div class="container mt-4"> {% for game in games %}
{% for game, game_data in org_data.games.items %} <div class="card mb-4">
<div class="card mb-4"> <div class="row g-0">
<div class="row g-0"> <div class="col-md-2">
<div class="col-md-2"> <img src="{{ game.image_url }}"
<img src="{{ game.image_url }}" alt="{{ game.display_name }}"
alt="{{ game.display_name }}" class="img-fluid rounded-start"
class="img-fluid rounded-start" height="150"
height="150" width="150"
width="150" loading="lazy">
loading="lazy">
</div>
<div class="col-md-4">
<div class="card-body">
<h2 class="card-title h5">
<a href="https://www.twitch.tv/directory/category/{{ game.slug }}"
class="text-decoration-none">{{ game.display_name }}</a>
</h2>
</div>
</div>
</div> </div>
<div class="card-body card-bottom"> <div class="col-md-4">
{% if discord_settings %} <div class="card-body">
<div class="card-body"> <h2 class="card-title h5">
<div>Available on iOS: {{ game_data.ios_available|yesno:"Yes,No" }}</div> <a href="{{ game.twitch_url }}" class="text-decoration-none">{{ game.display_name }}</a>
</div> </h2>
{% else %}
<div class="card-body text-end">
<a href="{% url 'core:add_discord_webhook' %}" class="card-link">Add Discord settings</a>
</div>
{% endif %}
</div>
<div class="card-body card-bottom">
<div class="row">
{% for drop_benefit in game_data.drop_benefits %}
<div class="col-6 col-md-4 d-flex align-items-center mb-2 position-relative">
<img src="{{ drop_benefit.image_asset_url }}"
alt="{{ drop_benefit.name }}"
class="img-fluid rounded me-3"
height="50"
width="50"
loading="lazy">
{# Only show the entitlement limit if it's not None or above 1 #}
{% if drop_benefit.entitlement_limit > 1 %}
<span class="badge bg-primary position-absolute top-100 start-20 translate-middle">{{ drop_benefit.entitlement_limit }}
<span class="visually-hidden">limit per account</span>
</span>
{% endif %}
{{ drop_benefit.name }}
</div>
{% endfor %}
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% for campaign in game.campaigns %}
</div> <div class="card-body card-bottom">
{% endfor %} {% for drop in campaign.drops %}
<div class="col-6 col-md-4 d-flex align-items-center mb-2 position-relative">
<img src="{{ drop.image_url|default:'https://static-cdn.jtvnw.net/twitch-quests-assets/CAMPAIGN/default.png' }}"
alt="{{ drop.name }} drop image"
class="img-fluid rounded me-3"
height="50"
width="50"
loading="lazy">
{{ drop.name }}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock content %} {% endblock content %}

View File

@ -1,4 +1,6 @@
import datetime
import logging import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from django.contrib import messages from django.contrib import messages
@ -17,7 +19,6 @@ from twitch_app.models import (
DropBenefit, DropBenefit,
DropCampaign, DropCampaign,
Game, Game,
Organization,
TimeBasedDrop, TimeBasedDrop,
) )
@ -32,6 +33,47 @@ from django.views.decorators.http import require_POST
logger: logging.Logger = logging.getLogger(__name__) 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: def index(request: HttpRequest) -> HttpResponse:
"""/ index page. """/ index page.
@ -41,50 +83,61 @@ def index(request: HttpRequest) -> HttpResponse:
Returns: Returns:
HttpResponse: Returns the index page. 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 game in Game.objects.all():
for org in organizations: campaigns: list[CampaignContext] = []
drop_benefits: BaseManager[DropBenefit] = DropBenefit.objects.filter( for campaign in DropCampaign.objects.filter(game=game, status="ACTIVE"):
owner_organization=org, drops: list[DropContext] = []
)
games: set[Game] = {benefit.game for benefit in drop_benefits}
for game in games: drop: TimeBasedDrop
if game not in orgs_data[org]["games"]: for drop in campaign.time_based_drops.all():
orgs_data[org]["games"][game] = { benefit: DropBenefit | None = drop.benefits.first()
"drop_benefits": [], drops.append(
"time_based_drops": [], 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: if not drops:
orgs_data[org]["games"][benefit.game]["drop_benefits"].append(benefit) logger.info("No drops found for %s", campaign.name)
continue
time_based_drops: BaseManager[TimeBasedDrop] = TimeBasedDrop.objects.filter( campaigns.append(
benefits__in=drop_benefits, CampaignContext(
).distinct() drop_id=campaign.id,
for drop in time_based_drops: name=campaign.name,
for benefit in drop.benefits.all(): image_url=campaign.image_url,
if benefit.game in orgs_data[org]["games"]: status=campaign.status,
orgs_data[org]["games"][benefit.game]["time_based_drops"].append( account_link_url=campaign.account_link_url,
drop, 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( if not campaigns:
owner=org, logger.info("No campaigns found for %s", game.display_name)
) continue
for campaign in drop_campaigns:
orgs_data[org]["drop_campaigns"].append(campaign)
if request.user.is_authenticated: list_of_games.append(
discord_settings: BaseManager[DiscordSetting] = DiscordSetting.objects.filter( GameContext(
user=request.user, game_id=game.id,
campaigns=campaigns,
image_url=game.image_url,
display_name=game.display_name,
twitch_url=game.twitch_url,
),
) )
context = { context: dict[str, list[GameContext]] = {"games": list_of_games}
"orgs_data": orgs_data,
"discord_settings": discord_settings if request.user.is_authenticated else None,
}
return TemplateResponse( return TemplateResponse(
request=request, request=request,

View File

@ -4,14 +4,15 @@ import auto_prefetch
import django.db.models.deletion import django.db.models.deletion
import django.db.models.manager import django.db.models.manager
from django.db import migrations from django.db import migrations
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies: list[tuple[str, str]] = [
("twitch_app", "0005_alter_dropbenefit_options_alter_dropcampaign_options_and_more"), ("twitch_app", "0005_alter_dropbenefit_options_alter_dropcampaign_options_and_more"),
] ]
operations = [ operations: list[Operation] = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name="dropbenefit", name="dropbenefit",
options={ options={

View File

@ -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"),
),
]

View File

@ -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",
),
),
]

View File

@ -10,6 +10,11 @@ from simple_history.models import HistoricalRecords
class Organization(auto_prefetch.Model): class Organization(auto_prefetch.Model):
"""The company that owns the game.
For example, 2K games.
"""
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
name = models.TextField(blank=True, null=True) name = models.TextField(blank=True, null=True)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=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): class Game(auto_prefetch.Model):
"""The game that the drop campaign is for.
For example, MultiVersus.
"""
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
slug = models.TextField(blank=True, null=True) slug = models.TextField(blank=True, null=True)
twitch_url = models.GeneratedField( # type: ignore # noqa: PGH003 twitch_url = models.GeneratedField( # type: ignore # noqa: PGH003
@ -56,6 +66,8 @@ class Game(auto_prefetch.Model):
class DropBenefit(auto_prefetch.Model): class DropBenefit(auto_prefetch.Model):
"""Information about the drop."""
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
created_at = models.DateTimeField(blank=True, null=True) created_at = models.DateTimeField(blank=True, null=True)
entitlement_limit = models.IntegerField(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( owner_organization = auto_prefetch.ForeignKey(
Organization, Organization,
on_delete=models.CASCADE, 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) added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords() history = HistoricalRecords()
@ -81,13 +94,15 @@ class DropBenefit(auto_prefetch.Model):
class TimeBasedDrop(auto_prefetch.Model): class TimeBasedDrop(auto_prefetch.Model):
"""The actual drop that is being given out."""
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
required_subs = models.IntegerField(blank=True, null=True) required_subs = models.IntegerField(blank=True, null=True)
end_at = models.DateTimeField(blank=True, null=True) end_at = models.DateTimeField(blank=True, null=True)
name = models.TextField(blank=True, null=True) name = models.TextField(blank=True, null=True)
required_minutes_watched = models.IntegerField(blank=True, null=True) required_minutes_watched = models.IntegerField(blank=True, null=True)
start_at = models.DateTimeField(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) added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords() history = HistoricalRecords()
@ -106,6 +121,11 @@ class TimeBasedDrop(auto_prefetch.Model):
class DropCampaign(auto_prefetch.Model): class DropCampaign(auto_prefetch.Model):
"""Drops are grouped into campaigns.
For example, MultiVersus S1 Drops
"""
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
account_link_url = models.URLField(blank=True, null=True) account_link_url = models.URLField(blank=True, null=True)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
@ -125,7 +145,7 @@ class DropCampaign(auto_prefetch.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="drop_campaigns", 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) added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords() history = HistoricalRecords()