Add box art to dashboard and add Game.box_art_base_url()
This commit is contained in:
parent
5d572d7b52
commit
b430ce7ae8
3 changed files with 75 additions and 27 deletions
|
|
@ -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>
|
||||||
<header>
|
|
||||||
<h2>
|
<h2>
|
||||||
<a href="{% url 'twitch:game_detail' game_id %}">{{ game_data.name }}</a>
|
{% if org_data.name %}
|
||||||
|
{{ org_data.name }}
|
||||||
|
{% else %}
|
||||||
|
Organization {{ org_id }}
|
||||||
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
|
{% for game_id, game_data in org_data.games.items %}
|
||||||
|
<article>
|
||||||
|
<header style="display: flex; align-items: flex-start; margin-bottom: 1rem;">
|
||||||
|
<div style="flex-shrink: 0; margin-right: 1rem;">
|
||||||
|
<img src="{{ game_data.box_art }}"
|
||||||
|
alt="Box art for {{ game_data.name }}"
|
||||||
|
width="200"
|
||||||
|
height="267"
|
||||||
|
style="border-radius: 8px">
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<h3 style="margin: 0 0 0.5rem 0;">
|
||||||
|
<a href="{% url 'twitch:game_detail' game_id %}">{{ game_data.name }}</a>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: 1.5rem;">
|
<div>
|
||||||
{% for campaign in game_data.campaigns %}
|
{% for campaign in game_data.campaigns %}
|
||||||
<article style="flex: 0 1 300px;">
|
<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 %}">
|
<a href="{% url 'twitch:campaign_detail' campaign.id %}">
|
||||||
<img src="{{ campaign.image_url }}"
|
<img src="{{ campaign.image_url }}"
|
||||||
alt="Image for {{ campaign.name }}"
|
alt="Image for {{ campaign.name }}"
|
||||||
width="160"
|
width="120"
|
||||||
height="160"
|
height="120"
|
||||||
style="border-radius: 0.5rem">
|
style="border-radius: 4px">
|
||||||
|
<h4 style="margin: 0.5rem 0;">{{ campaign.clean_name }}</h4>
|
||||||
</a>
|
</a>
|
||||||
<h3>{{ campaign.clean_name }}</h3>
|
|
||||||
<time datetime="{{ campaign.end_at|date:'c' }}"
|
<time datetime="{{ campaign.end_at|date:'c' }}"
|
||||||
title="{{ campaign.end_at|date:'DATETIME_FORMAT' }}">
|
title="{{ campaign.end_at|date:'DATETIME_FORMAT' }}"
|
||||||
|
style="font-size: 0.9rem;
|
||||||
|
color: #666">
|
||||||
Ends in {{ campaign.end_at|timeuntil }}
|
Ends in {{ campaign.end_at|timeuntil }}
|
||||||
</time>
|
</time>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</article>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</section>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No active campaigns at the moment.</p>
|
<p>No active campaigns at the moment.</p>
|
||||||
|
|
|
||||||
|
|
@ -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."""
|
||||||
|
|
|
||||||
|
|
@ -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": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue