Add box art to dashboard and add Game.box_art_base_url()

This commit is contained in:
Joakim Hellsén 2025-08-29 18:11:07 +02:00
commit b430ce7ae8
3 changed files with 75 additions and 27 deletions

View file

@ -5,35 +5,64 @@
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
<main> <main>
<h1>Twitch Drops Dashboard</h1>
<p>Track active campaigns and upcoming drops</p>
{% if campaigns_by_org_game %} {% if campaigns_by_org_game %}
{% for org_id, org_data in campaigns_by_org_game.items %} {% for org_id, org_data in campaigns_by_org_game.items %}
{% for game_id, game_data in org_data.games.items %} <section>
<section> <h2>
<header> {% if org_data.name %}
<h2> {{ org_data.name }}
<a href="{% url 'twitch:game_detail' game_id %}">{{ game_data.name }}</a> {% else %}
</h2> Organization {{ org_id }}
</header> {% endif %}
<div style="display: flex; flex-wrap: wrap; gap: 1.5rem;"> </h2>
{% for campaign in game_data.campaigns %} {% for game_id, game_data in org_data.games.items %}
<article style="flex: 0 1 300px;"> <article>
<a href="{% url 'twitch:campaign_detail' campaign.id %}"> <header style="display: flex; align-items: flex-start; margin-bottom: 1rem;">
<img src="{{ campaign.image_url }}" <div style="flex-shrink: 0; margin-right: 1rem;">
alt="Image for {{ campaign.name }}" <img src="{{ game_data.box_art }}"
width="160" alt="Box art for {{ game_data.name }}"
height="160" width="200"
style="border-radius: 0.5rem"> height="267"
</a> style="border-radius: 8px">
<h3>{{ campaign.clean_name }}</h3> </div>
<time datetime="{{ campaign.end_at|date:'c' }}" <div style="flex: 1;">
title="{{ campaign.end_at|date:'DATETIME_FORMAT' }}"> <h3 style="margin: 0 0 0.5rem 0;">
Ends in {{ campaign.end_at|timeuntil }} <a href="{% url 'twitch:game_detail' game_id %}">{{ game_data.name }}</a>
</time> </h3>
</article> </div>
{% endfor %} </header>
</div> <div>
</section> {% for campaign in game_data.campaigns %}
{% endfor %} <article style="display: flex;
align-items: center;
margin-bottom: 1rem;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 8px">
<div style="flex: 1;">
<a href="{% url 'twitch:campaign_detail' campaign.id %}">
<img src="{{ campaign.image_url }}"
alt="Image for {{ campaign.name }}"
width="120"
height="120"
style="border-radius: 4px">
<h4 style="margin: 0.5rem 0;">{{ campaign.clean_name }}</h4>
</a>
<time datetime="{{ campaign.end_at|date:'c' }}"
title="{{ campaign.end_at|date:'DATETIME_FORMAT' }}"
style="font-size: 0.9rem;
color: #666">
Ends in {{ campaign.end_at|timeuntil }}
</time>
</div>
</article>
{% endfor %}
</div>
</article>
{% endfor %}
</section>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p>No active campaigns at the moment.</p> <p>No active campaigns at the moment.</p>

View file

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import re
from typing import TYPE_CHECKING, ClassVar from typing import TYPE_CHECKING, ClassVar
from django.db import models from django.db import models
@ -47,6 +48,23 @@ class Game(models.Model):
"""Return all organizations that have drop campaigns for this game.""" """Return all organizations that have drop campaigns for this game."""
return Organization.objects.filter(drop_campaigns__game=self).distinct() return Organization.objects.filter(drop_campaigns__game=self).distinct()
@property
def box_art_base_url(self) -> str:
"""Return the base box art URL without size suffix.
Twitch box art URLs often include size suffixes like '-120x160.jpg'.
This property returns the base URL without the size suffix.
Examples:
'https://static-cdn.jtvnw.net/ttv-boxart/512710-120x160.jpg'
-> 'https://static-cdn.jtvnw.net/ttv-boxart/512710.jpg'
"""
if not self.box_art:
return ""
# Remove size suffix pattern like '-120x160' from the filename
return re.sub(r"-\d+x\d+(\.jpg|\.png|\.jpeg|\.gif|\.webp)$", r"\1", self.box_art)
class Organization(models.Model): class Organization(models.Model):
"""Represents an organization on Twitch that can own drop campaigns.""" """Represents an organization on Twitch that can own drop campaigns."""

View file

@ -337,6 +337,7 @@ def dashboard(request: HttpRequest) -> HttpResponse:
if game_id not in campaigns_by_org_game[org_id]["games"]: if game_id not in campaigns_by_org_game[org_id]["games"]:
campaigns_by_org_game[org_id]["games"][game_id] = { campaigns_by_org_game[org_id]["games"][game_id] = {
"name": game_name, "name": game_name,
"box_art": campaign.game.box_art_base_url,
"campaigns": [], "campaigns": [],
} }