Rewrite index view
This commit is contained in:
@ -1,8 +1,7 @@
|
|||||||
{% 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, game_data in org_data.games.items %}
|
{% for game in games %}
|
||||||
<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">
|
||||||
@ -16,46 +15,27 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title h5">
|
<h2 class="card-title h5">
|
||||||
<a href="https://www.twitch.tv/directory/category/{{ game.slug }}"
|
<a href="{{ game.twitch_url }}" class="text-decoration-none">{{ game.display_name }}</a>
|
||||||
class="text-decoration-none">{{ game.display_name }}</a>
|
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% for campaign in game.campaigns %}
|
||||||
<div class="card-body card-bottom">
|
<div class="card-body card-bottom">
|
||||||
{% if discord_settings %}
|
{% for drop in campaign.drops %}
|
||||||
<div class="card-body">
|
|
||||||
<div>Available on iOS: {{ game_data.ios_available|yesno:"Yes,No" }}</div>
|
|
||||||
</div>
|
|
||||||
{% 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">
|
<div class="col-6 col-md-4 d-flex align-items-center mb-2 position-relative">
|
||||||
<img src="{{ drop_benefit.image_asset_url }}"
|
<img src="{{ drop.image_url|default:'https://static-cdn.jtvnw.net/twitch-quests-assets/CAMPAIGN/default.png' }}"
|
||||||
alt="{{ drop_benefit.name }}"
|
alt="{{ drop.name }} drop image"
|
||||||
class="img-fluid rounded me-3"
|
class="img-fluid rounded me-3"
|
||||||
height="50"
|
height="50"
|
||||||
width="50"
|
width="50"
|
||||||
loading="lazy">
|
loading="lazy">
|
||||||
{# Only show the entitlement limit if it's not None or above 1 #}
|
{{ drop.name }}
|
||||||
{% 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>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
129
core/views.py
129
core/views.py
@ -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,
|
||||||
for benefit in drop_benefits:
|
name=drop.name,
|
||||||
orgs_data[org]["games"][benefit.game]["drop_benefits"].append(benefit)
|
limit=None,
|
||||||
|
required_minutes_watched=drop.required_minutes_watched,
|
||||||
time_based_drops: BaseManager[TimeBasedDrop] = TimeBasedDrop.objects.filter(
|
required_subs=drop.required_subs,
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
drop_campaigns: BaseManager[DropCampaign] = DropCampaign.objects.filter(
|
if not drops:
|
||||||
owner=org,
|
logger.info("No drops found for %s", campaign.name)
|
||||||
)
|
continue
|
||||||
for campaign in drop_campaigns:
|
|
||||||
orgs_data[org]["drop_campaigns"].append(campaign)
|
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
campaigns.append(
|
||||||
discord_settings: BaseManager[DiscordSetting] = DiscordSetting.objects.filter(
|
CampaignContext(
|
||||||
user=request.user,
|
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,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
context = {
|
if not campaigns:
|
||||||
"orgs_data": orgs_data,
|
logger.info("No campaigns found for %s", game.display_name)
|
||||||
"discord_settings": discord_settings if request.user.is_authenticated else None,
|
continue
|
||||||
}
|
|
||||||
|
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: dict[str, list[GameContext]] = {"games": list_of_games}
|
||||||
|
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
|
@ -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={
|
||||||
|
@ -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"),
|
||||||
|
),
|
||||||
|
]
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -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()
|
||||||
|
Reference in New Issue
Block a user