This commit is contained in:
parent
17ef09465d
commit
4663a827e4
12 changed files with 434 additions and 405 deletions
|
|
@ -1,17 +1,19 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load image_tags %}
|
{% load image_tags %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Chat Badges - ttvdrops
|
Chat Badges
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{ badge_sets.count }} Twitch Chat Badges</h1>
|
<h1>{{ badge_sets.count }} Twitch Chat Badges</h1>
|
||||||
{% if badge_sets %}
|
{% if badge_sets %}
|
||||||
{% for data in badge_data %}
|
{% for data in badge_data %}
|
||||||
|
<!-- {{ data.set.set_id }} - {{ data.badges|length }} version{% if data.badges|length > 1 %}s{% endif %} -->
|
||||||
<h2>
|
<h2>
|
||||||
<a href="{% url 'twitch:badge_set_detail' set_id=data.set.set_id %}">{{ data.set.set_id }}</a>
|
<a href="{% url 'twitch:badge_set_detail' set_id=data.set.set_id %}">{{ data.set.set_id }}</a>
|
||||||
</h2>
|
</h2>
|
||||||
<table>
|
<table>
|
||||||
{% for badge in data.badges %}
|
{% for badge in data.badges %}
|
||||||
|
<!-- {{ badge.title }} {% if badge.description != badge.title %}- {{ badge.description }}{% else %}{% endif %} -->
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 40px;">
|
<td style="width: 40px;">
|
||||||
<a href="{% url 'twitch:badge_set_detail' set_id=data.set.set_id %}">
|
<a href="{% url 'twitch:badge_set_detail' set_id=data.set.set_id %}">
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for badge in badges %}
|
{% for badge in badges %}
|
||||||
|
<!-- {{ badge.title }} {% if badge.description != badge.title %}- {{ badge.description }}{% else %}{% endif %} -->
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<code>{{ badge.badge_id }}</code>
|
<code>{{ badge.badge_id }}</code>
|
||||||
|
|
@ -24,18 +25,16 @@
|
||||||
<a href="{{ badge.image_url_4x }}" rel="nofollow ugc">[72px]</a>
|
<a href="{{ badge.image_url_4x }}" rel="nofollow ugc">[72px]</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if badge.click_url %}
|
{% if badge.click_url %}<a href="{{ badge.click_url }}" rel="nofollow ugc">{{ badge.click_action }}</a>{% endif %}
|
||||||
<a href="{{ badge.click_url }}" rel="nofollow ugc">{{ badge.click_action }}</a>
|
|
||||||
{% else %}
|
|
||||||
-
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if badge.award_campaigns %}
|
{% if badge.award_campaigns %}
|
||||||
|
<!-- If there are campaigns awarding this badge, show them in a nested row -->
|
||||||
<div>
|
<div>
|
||||||
The following campaigns have the same name as this badge and may be awarding it:
|
The following campaigns have the same name as this badge and may be awarding it:
|
||||||
<ul>
|
<ul>
|
||||||
{% for campaign in badge.award_campaigns %}
|
{% for campaign in badge.award_campaigns %}
|
||||||
|
<!-- Note: We can't be sure if these campaigns are actually awarding this badge, but it's likely given the name match. -->
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,28 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock extra_head %}
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Campaign Title -->
|
<div style="display: flex; align-items: flex-start;">
|
||||||
<h1>
|
|
||||||
{% if campaign.game %}
|
|
||||||
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">{{ campaign.game.get_game_name }}</a> - {{ campaign.clean_name }}
|
|
||||||
{% else %}
|
|
||||||
{{ campaign.clean_name }}
|
|
||||||
{% endif %}
|
|
||||||
</h1>
|
|
||||||
<!-- Campaign Owners -->
|
|
||||||
{% for org in owners %}
|
|
||||||
<p>
|
|
||||||
<a href="{% url 'twitch:organization_detail' org.twitch_id %}">{{ org.name }}</a>
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
<!-- Campaign image -->
|
<!-- Campaign image -->
|
||||||
|
<div style="margin-right: 16px;">
|
||||||
{% if campaign.image_best_url %}
|
{% if campaign.image_best_url %}
|
||||||
{% picture campaign.image_best_url alt=campaign.name width=160 %}
|
{% picture campaign.image_best_url alt=campaign.name width=160 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- Campaign Title -->
|
||||||
|
<div style="display: flex; flex-direction: column;">
|
||||||
|
<h1 style="margin-top: 0; margin-bottom: 0px;">{{ campaign.clean_name }}</h1>
|
||||||
|
<div>
|
||||||
|
{% if campaign.game %}
|
||||||
|
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">{{ campaign.game.get_game_name }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if owners %}
|
||||||
|
-
|
||||||
|
<!-- Campaign Owners -->
|
||||||
|
{% for org in owners %}
|
||||||
|
<a href="{% url 'twitch:organization_detail' org.twitch_id %}">{{ org.name }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<!-- Campaign description -->
|
<!-- Campaign description -->
|
||||||
<p>{{ campaign.description|linebreaksbr }}</p>
|
<p>{{ campaign.description|linebreaksbr }}</p>
|
||||||
<!-- Campaign end times -->
|
<!-- Campaign end times -->
|
||||||
|
|
@ -72,6 +76,7 @@
|
||||||
<strong>Duration</strong> {{ campaign.end_at|timeuntil:campaign.start_at }}
|
<strong>Duration</strong> {{ campaign.end_at|timeuntil:campaign.start_at }}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Buttons -->
|
||||||
<div>
|
<div>
|
||||||
<!-- Campaign Detail URL -->
|
<!-- Campaign Detail URL -->
|
||||||
{% if campaign.details_url %}<a href="{{ campaign.details_url }}" rel="nofollow ugc">[details]</a>{% endif %}
|
{% if campaign.details_url %}<a href="{{ campaign.details_url }}" rel="nofollow ugc">[details]</a>{% endif %}
|
||||||
|
|
@ -87,40 +92,24 @@
|
||||||
title="Atom feed for {{ campaign.game.display_name }} campaigns">[atom]</a>
|
title="Atom feed for {{ campaign.game.display_name }} campaigns">[atom]</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if allowed_channels %}
|
|
||||||
<h5>Allowed Channels</h5>
|
|
||||||
<div>
|
|
||||||
{% for channel in allowed_channels %}
|
|
||||||
<a href="{% url 'twitch:channel_detail' channel.twitch_id %}">{{ channel.display_name }}</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
</div>
|
||||||
<a href="{{ campaign.game.twitch_directory_url }}"
|
|
||||||
rel="nofollow ugc"
|
|
||||||
title="Find streamers playing {{ campaign.game.display_name }} with drops enabled">
|
|
||||||
Go to a participating live channel
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<h5>Campaign Info</h5>
|
<h5>Campaign Info</h5>
|
||||||
{% if drops %}
|
{% if drops %}
|
||||||
<table id="drops-table" style="border-collapse: collapse; width: 100%;">
|
<!-- Drops table -->
|
||||||
<thead>
|
<table style="border-collapse: collapse; width: 100%;">
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>Drop Name</th>
|
|
||||||
<th>Requirements</th>
|
|
||||||
<th>Period</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for drop in drops %}
|
{% for drop in drops %}
|
||||||
<tr id="drop-{{ drop.drop.twitch_id }}">
|
<!-- {{ drop.drop.name }} - {{ drop.drop.benefits.all|join:", " }} -->
|
||||||
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% for benefit in drop.drop.benefits.all %}
|
{% for benefit in drop.drop.benefits.all %}
|
||||||
{% if benefit.image_asset_url %}
|
{% if benefit.image_asset_url %}
|
||||||
|
<!-- Show the benefit image if available -->
|
||||||
{% picture benefit.image_best_url|default:benefit.image_asset_url alt=benefit.name width=160 height=160 style="object-fit: cover; margin-right: 3px" %}
|
{% picture benefit.image_best_url|default:benefit.image_asset_url alt=benefit.name width=160 height=160 style="object-fit: cover; margin-right: 3px" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if benefit.distribution_type == "BADGE" and drop.awarded_badge %}
|
{% if benefit.distribution_type == "BADGE" and drop.awarded_badge %}
|
||||||
|
<!-- If the drop awards a badge, show the badge prominently with its description -->
|
||||||
<div style="margin-top: 6px; font-size: 0.9em;">
|
<div style="margin-top: 6px; font-size: 0.9em;">
|
||||||
<strong>Awards Badge:</strong>
|
<strong>Awards Badge:</strong>
|
||||||
<a href="{% url 'twitch:badge_set_detail' set_id=drop.awarded_badge.badge_set.set_id %}">
|
<a href="{% url 'twitch:badge_set_detail' set_id=drop.awarded_badge.badge_set.set_id %}">
|
||||||
|
|
@ -128,6 +117,7 @@
|
||||||
{{ drop.awarded_badge.title }}
|
{{ drop.awarded_badge.title }}
|
||||||
</a>
|
</a>
|
||||||
{% if drop.awarded_badge.description %}
|
{% if drop.awarded_badge.description %}
|
||||||
|
<!-- Show the badge description if available -->
|
||||||
<div style="margin-top: 4px; color: #a9a9a9; font-size: 0.9em;">{{ drop.awarded_badge.description|linebreaksbr }}</div>
|
<div style="margin-top: 4px; color: #a9a9a9; font-size: 0.9em;">{{ drop.awarded_badge.description|linebreaksbr }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -138,6 +128,7 @@
|
||||||
<div style="margin-bottom: 5px;">{{ drop.drop.name }}</div>
|
<div style="margin-bottom: 5px;">{{ drop.drop.name }}</div>
|
||||||
{% for benefit in drop.drop.benefits.all %}
|
{% for benefit in drop.drop.benefits.all %}
|
||||||
{% if benefit.name != drop.drop.name %}
|
{% if benefit.name != drop.drop.name %}
|
||||||
|
<!-- Show additional benefits if they have a different name than the drop itself -->
|
||||||
<div style="font-size: 0.9em; color: #a9a9a9; margin-bottom: 3px;">{{ benefit.name }}</div>
|
<div style="font-size: 0.9em; color: #a9a9a9; margin-bottom: 3px;">{{ benefit.name }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
@ -163,8 +154,27 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<!-- If there are no drops, show a message indicating that there are no drops available for this campaign -->
|
||||||
<p>No drops available for this campaign.</p>
|
<p>No drops available for this campaign.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!-- Allowed channels -->
|
||||||
|
{% if allowed_channels %}
|
||||||
|
<!-- If specific allowed channels are specified, list them -->
|
||||||
|
<h5>Allowed Channels</h5>
|
||||||
|
<div>
|
||||||
|
{% for channel in allowed_channels %}
|
||||||
|
<!-- {{ channel.display_name }} https://www.twitch.tv/{{ channel.display_name }} -->
|
||||||
|
<a href="{% url 'twitch:channel_detail' channel.twitch_id %}">{{ channel.display_name }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<!-- If no allowed channels are specified, link to the game's Twitch directory for channels with drops enabled -->
|
||||||
|
<a href="{{ campaign.game.twitch_directory_url }}"
|
||||||
|
rel="nofollow ugc"
|
||||||
|
title="Find streamers playing {{ campaign.game.display_name }} with drops enabled">
|
||||||
|
Go to a participating live channel
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
<!-- Campaign JSON -->
|
<!-- Campaign JSON -->
|
||||||
{{ campaign_data|safe }}
|
{{ campaign_data|safe }}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
{% load image_tags %}
|
{% load image_tags %}
|
||||||
{% load image_tags %}
|
{% load image_tags %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Drop Campaigns - Twitch Drops Tracker
|
Drop Campaigns
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link rel="alternate"
|
<link rel="alternate"
|
||||||
|
|
@ -41,6 +41,7 @@
|
||||||
<select name="game">
|
<select name="game">
|
||||||
<option value="">All Games</option>
|
<option value="">All Games</option>
|
||||||
{% for game in games %}
|
{% for game in games %}
|
||||||
|
<!-- Game option with Twitch ID {{ game.twitch_id }} and display name "{{ game.display_name }}" -->
|
||||||
<option value="{{ game.twitch_id }}"
|
<option value="{{ game.twitch_id }}"
|
||||||
{% if selected_game == game.twitch_id %}selected{% endif %}>
|
{% if selected_game == game.twitch_id %}selected{% endif %}>
|
||||||
{{ game.display_name|default:game.name|default:game.slug|default:game.twitch_id }}
|
{{ game.display_name|default:game.name|default:game.slug|default:game.twitch_id }}
|
||||||
|
|
@ -51,6 +52,7 @@
|
||||||
<select id="status" name="status">
|
<select id="status" name="status">
|
||||||
<option value="">All Statuses</option>
|
<option value="">All Statuses</option>
|
||||||
{% for status in status_options %}
|
{% for status in status_options %}
|
||||||
|
<!-- Status option "{{ status }}" -->
|
||||||
<option value="{{ status }}"
|
<option value="{{ status }}"
|
||||||
{% if selected_status == status %}selected{% endif %}>{{ status|title }}</option>
|
{% if selected_status == status %}selected{% endif %}>{{ status|title }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
@ -63,6 +65,7 @@
|
||||||
{% if campaigns %}
|
{% if campaigns %}
|
||||||
{% regroup campaigns by game as campaigns_by_game %}
|
{% regroup campaigns by game as campaigns_by_game %}
|
||||||
{% for game_group in campaigns_by_game %}
|
{% for game_group in campaigns_by_game %}
|
||||||
|
<!-- Game group for game "{{ game_group.grouper.display_name }}" with {{ game_group.list|length }} campaigns -->
|
||||||
<section>
|
<section>
|
||||||
<div style="display: flex; gap: 1rem;">
|
<div style="display: flex; gap: 1rem;">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -99,6 +102,9 @@
|
||||||
<div style="overflow-x: auto;">
|
<div style="overflow-x: auto;">
|
||||||
<div style="display: flex; gap: 1rem; min-width: max-content;">
|
<div style="display: flex; gap: 1rem; min-width: max-content;">
|
||||||
{% for campaign in game_group.list %}
|
{% for campaign in game_group.list %}
|
||||||
|
<!-- Campaign "{{ campaign.clean_name }}" with Twitch ID {{ campaign.twitch_id }} -->
|
||||||
|
<!-- https://ttvdrops.lovinator.space{% url 'twitch:campaign_detail' campaign.twitch_id %} -->
|
||||||
|
<!-- https://ttvdrops.lovinator.space{{ campaign.image_best_url }} -->
|
||||||
<article style="display: flex;
|
<article style="display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
@ -108,7 +114,7 @@
|
||||||
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}"
|
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}"
|
||||||
style="text-decoration: none">
|
style="text-decoration: none">
|
||||||
{% if campaign.image_best_url %}
|
{% if campaign.image_best_url %}
|
||||||
{% picture campaign.image_best_url alt="Campaign artwork for "|add:campaign.name width=120 height=120 style="border-radius: 4px" %}
|
{% picture campaign.image_best_url alt="Campaign artwork for "|add:campaign.name width=120 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h4 style="margin: 0.5rem 0; text-align: left">{{ campaign.clean_name }}</h4>
|
<h4 style="margin: 0.5rem 0; text-align: left">{{ campaign.clean_name }}</h4>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -153,61 +159,34 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if is_paginated %}
|
{% if is_paginated %}
|
||||||
<nav style="margin-top: 3rem; text-align: center;">
|
<!-- {{ page_obj.paginator.count }} total campaigns, showing {{ page_obj.start_index }} to {{ page_obj.end_index }} on page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} -->
|
||||||
<div style="display: flex;
|
<nav style="text-align: center;">
|
||||||
justify-content: center;
|
<div>
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
flex-wrap: wrap">
|
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page=1"
|
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page=1">[first]</a>
|
||||||
style="padding: 0.5rem 1rem;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none">[first]</a>
|
|
||||||
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ page_obj.previous_page_number }}"
|
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ page_obj.previous_page_number }}"
|
||||||
style="padding: 0.5rem 1rem;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none"
|
|
||||||
aria-label="Previous">[previous]</a>
|
aria-label="Previous">[previous]</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span style="padding: 0.5rem 1rem;">[first]</span>
|
<span>[first]</span>
|
||||||
<span style="padding: 0.5rem 1rem;">[previous]</span>
|
<span>[previous]</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for num in page_obj.paginator.page_range %}
|
{% for num in page_obj.paginator.page_range %}
|
||||||
{% if page_obj.number == num %}
|
{% if page_obj.number == num %}
|
||||||
<span style="padding: 0.5rem 1rem; border-radius: 4px; font-weight: 600">{{ num }}</span>
|
<span>{{ num }}</span>
|
||||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
{% elif num > page_obj.number|add:'-10' and num < page_obj.number|add:'10' %}
|
||||||
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ num }}"
|
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ num }}">{{ num }}</a>
|
||||||
style="padding: 0.5rem 1rem;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none">{{ num }}</a>
|
|
||||||
{% elif num == 1 or num == page_obj.paginator.num_pages %}
|
{% elif num == 1 or num == page_obj.paginator.num_pages %}
|
||||||
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ num }}"
|
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ num }}">{{ num }}</a>
|
||||||
style="padding: 0.5rem 1rem;
|
{% elif num == page_obj.number|add:'-10' or num == page_obj.number|add:'10' %}
|
||||||
border: 1px solid;
|
<span>...</span>
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none">{{ num }}</a>
|
|
||||||
{% elif num == page_obj.number|add:'-4' or num == page_obj.number|add:'4' %}
|
|
||||||
<span style="padding: 0.5rem 1rem;">...</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ page_obj.next_page_number }}"
|
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ page_obj.next_page_number }}">[next]</a>
|
||||||
style="padding: 0.5rem 1rem;
|
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ page_obj.paginator.num_pages }}">[last]</a>
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none">[next]</a>
|
|
||||||
<a href="?{% if selected_status %}status={{ selected_status }}&{% endif %}{% if selected_game %}game={{ selected_game }}&{% endif %}{% if selected_per_page != 100 %}per_page={{ selected_per_page }}&{% endif %}page={{ page_obj.paginator.num_pages }}"
|
|
||||||
style="padding: 0.5rem 1rem;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration: none">[last]</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<span style="padding: 0.5rem 1rem;">[next]</span>
|
<span>[next]</span>
|
||||||
<span style="padding: 0.5rem 1rem;">[last]</span>
|
<span>[last]</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<small style="display: block; margin-top: 1rem;">
|
<small style="display: block; margin-top: 1rem;">
|
||||||
|
|
|
||||||
|
|
@ -19,22 +19,26 @@
|
||||||
border: none">
|
border: none">
|
||||||
</iframe>
|
</iframe>
|
||||||
<!-- Channel Info -->
|
<!-- Channel Info -->
|
||||||
<p>
|
<p>Channel ID: {{ channel.twitch_id }}</p>
|
||||||
<strong>Channel ID:</strong> {{ channel.twitch_id }}
|
|
||||||
</p>
|
|
||||||
{% if active_campaigns %}
|
{% if active_campaigns %}
|
||||||
<h5 id="active-campaigns-header">Active Campaigns</h5>
|
<h5>Active Campaigns</h5>
|
||||||
<table id="active-campaigns-table">
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for campaign in active_campaigns %}
|
{% for campaign in active_campaigns %}
|
||||||
<tr id="campaign-row-{{ campaign.twitch_id }}">
|
<!-- Campaign {{ campaign.name }} ({{ campaign.twitch_id }}) -->
|
||||||
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
||||||
{% if campaign.time_based_drops.all %}
|
{% if campaign.time_based_drops.all %}
|
||||||
<div class="campaign-benefits">
|
<!-- If the campaign has time-based drops, show the benefits in a nested div -->
|
||||||
|
<div>
|
||||||
|
<!-- swag swag swag {{campaign.sorted_benefits}} -->
|
||||||
{% for benefit in campaign.sorted_benefits %}
|
{% for benefit in campaign.sorted_benefits %}
|
||||||
<span class="benefit-item" title="{{ benefit.name }}">
|
<!-- Benefit {{ benefit.name }} ({{ benefit.twitch_id }}) -->
|
||||||
|
<!-- {{ benefit.image_best_url }} -->
|
||||||
|
<span title="{{ benefit.name }}">
|
||||||
{% if benefit.image_best_url or benefit.image_asset_url %}
|
{% if benefit.image_best_url or benefit.image_asset_url %}
|
||||||
|
<!-- Show the benefit image if available -->
|
||||||
<img src="{{ benefit.image_best_url|default:benefit.image_asset_url }}"
|
<img src="{{ benefit.image_best_url|default:benefit.image_asset_url }}"
|
||||||
alt="{{ benefit.name }}"
|
alt="{{ benefit.name }}"
|
||||||
width="24"
|
width="24"
|
||||||
|
|
@ -51,6 +55,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if campaign.game %}
|
{% if campaign.game %}
|
||||||
|
<!-- If the campaign has an associated game, show the game name with a link to the game detail page -->
|
||||||
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">
|
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">
|
||||||
{{ campaign.game.display_name|default:campaign.game.name }}
|
{{ campaign.game.display_name|default:campaign.game.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -67,18 +72,23 @@
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if upcoming_campaigns %}
|
{% if upcoming_campaigns %}
|
||||||
<h5 id="upcoming-campaigns-header">Upcoming Campaigns</h5>
|
<!-- If there are upcoming campaigns, show them in a separate section -->
|
||||||
<table id="upcoming-campaigns-table">
|
<h5>Upcoming Campaigns</h5>
|
||||||
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for campaign in upcoming_campaigns %}
|
{% for campaign in upcoming_campaigns %}
|
||||||
<tr id="campaign-row-{{ campaign.twitch_id }}">
|
<!-- Campaign {{ campaign.name }} ({{ campaign.twitch_id }}) -->
|
||||||
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
||||||
{% if campaign.time_based_drops.all %}
|
{% if campaign.time_based_drops.all %}
|
||||||
<div class="campaign-benefits">
|
<!-- If the campaign has time-based drops, show the benefits in a nested div -->
|
||||||
|
<div>
|
||||||
{% for benefit in campaign.sorted_benefits %}
|
{% for benefit in campaign.sorted_benefits %}
|
||||||
<span class="benefit-item" title="{{ benefit.name }}">
|
<!-- Benefit {{ benefit.name }} ({{ benefit.twitch_id }}) -->
|
||||||
|
<span title="{{ benefit.name }}">
|
||||||
{% if benefit.image_best_url or benefit.image_asset_url %}
|
{% if benefit.image_best_url or benefit.image_asset_url %}
|
||||||
|
<!-- Show the benefit image if available -->
|
||||||
<img src="{{ benefit.image_best_url|default:benefit.image_asset_url }}"
|
<img src="{{ benefit.image_best_url|default:benefit.image_asset_url }}"
|
||||||
alt="{{ benefit.name }}"
|
alt="{{ benefit.name }}"
|
||||||
width="24"
|
width="24"
|
||||||
|
|
@ -95,6 +105,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if campaign.game %}
|
{% if campaign.game %}
|
||||||
|
<!-- If the campaign has an associated game, show the game name with a link to the game detail page -->
|
||||||
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">
|
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">
|
||||||
{{ campaign.game.display_name|default:campaign.game.name }}
|
{{ campaign.game.display_name|default:campaign.game.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -111,17 +122,21 @@
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if expired_campaigns %}
|
{% if expired_campaigns %}
|
||||||
<h5 id="expired-campaigns-header">Past Campaigns</h5>
|
<!-- If there are expired campaigns, show them in a separate section -->
|
||||||
<table id="expired-campaigns-table">
|
<h5>Past Campaigns</h5>
|
||||||
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for campaign in expired_campaigns %}
|
{% for campaign in expired_campaigns %}
|
||||||
<tr id="campaign-row-{{ campaign.twitch_id }}">
|
<!-- Campaign {{ campaign.name }} ({{ campaign.twitch_id }}) -->
|
||||||
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
|
||||||
{% if campaign.time_based_drops.all %}
|
{% if campaign.time_based_drops.all %}
|
||||||
<div class="campaign-benefits">
|
<!-- If the campaign has time-based drops, show the benefits in a nested div -->
|
||||||
|
<div>
|
||||||
{% for benefit in campaign.sorted_benefits %}
|
{% for benefit in campaign.sorted_benefits %}
|
||||||
<span class="benefit-item" title="{{ benefit.name }}">
|
<!-- Benefit {{ benefit.name }} ({{ benefit.twitch_id }}) -->
|
||||||
|
<span title="{{ benefit.name }}">
|
||||||
{% if benefit.image_best_url or benefit.image_asset_url %}
|
{% if benefit.image_best_url or benefit.image_asset_url %}
|
||||||
<img src="{{ benefit.image_best_url|default:benefit.image_asset_url }}"
|
<img src="{{ benefit.image_best_url|default:benefit.image_asset_url }}"
|
||||||
alt="{{ benefit.name }}"
|
alt="{{ benefit.name }}"
|
||||||
|
|
@ -139,6 +154,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if campaign.game %}
|
{% if campaign.game %}
|
||||||
|
<!-- If the campaign has an associated game, show the game name with a link to the game detail page -->
|
||||||
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">
|
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">
|
||||||
{{ campaign.game.display_name|default:campaign.game.name }}
|
{{ campaign.game.display_name|default:campaign.game.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -155,7 +171,7 @@
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not active_campaigns and not upcoming_campaigns and not expired_campaigns %}
|
{% if not active_campaigns and not upcoming_campaigns and not expired_campaigns %}
|
||||||
<p id="no-campaigns-message">No campaigns found for this channel.</p>
|
<p>No campaigns found for this channel.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ channel_data|safe }}
|
{{ channel_data|safe }}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,24 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
<h1>Active Twitch Drops Campaigns</h1>
|
<h1>Active Twitch Drops Campaigns</h1>
|
||||||
|
<p>
|
||||||
|
This page lists all currently active Twitch Drops campaigns
|
||||||
|
organized by game.
|
||||||
|
<br />
|
||||||
|
Click on a campaign for more details about it and how to
|
||||||
|
earn drops.
|
||||||
|
<br />
|
||||||
|
Individual RSS feeds are available under each game, and
|
||||||
|
there is also a global feed for all campaigns:
|
||||||
|
</p>
|
||||||
<!-- RSS Feeds -->
|
<!-- RSS Feeds -->
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'twitch:campaign_feed' %}"
|
<a href="{% url 'twitch:campaign_feed' %}"
|
||||||
title="RSS feed for all campaigns">[rss - all campaigns]</a>
|
title="RSS feed for all campaigns">[rss]</a>
|
||||||
<a href="{% url 'twitch:campaign_feed_atom' %}"
|
<a href="{% url 'twitch:campaign_feed_atom' %}"
|
||||||
title="Atom feed for campaigns">[atom - all campaigns]</a>
|
title="Atom feed for campaigns">[atom]</a>
|
||||||
</div>
|
</div>
|
||||||
|
<hr />
|
||||||
{% if campaigns_by_game %}
|
{% if campaigns_by_game %}
|
||||||
{% for game_id, game_data in campaigns_by_game.items %}
|
{% for game_id, game_data in campaigns_by_game.items %}
|
||||||
<article id="game-article-{{ game_id }}" style="margin-bottom: 2rem;">
|
<article id="game-article-{{ game_id }}" style="margin-bottom: 2rem;">
|
||||||
|
|
@ -42,23 +53,22 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
<div style="display: flex; gap: 1rem;">
|
<div style="display: flex; gap: 1rem;">
|
||||||
<div style="flex-shrink: 0;">
|
<div style="flex-shrink: 0;">{% picture game_data.box_art alt="Box art for "|add:game_data.name width=200 %}</div>
|
||||||
{% picture game_data.box_art alt="Box art for "|add:game_data.name width=200 height=267 style="border-radius: 8px" %}
|
|
||||||
</div>
|
|
||||||
<div style="flex: 1; overflow-x: auto;">
|
<div style="flex: 1; overflow-x: auto;">
|
||||||
<div style="display: flex; gap: 1rem; min-width: max-content;">
|
<div style="display: flex; gap: 1rem; min-width: max-content;">
|
||||||
{% for campaign_data in game_data.campaigns %}
|
{% for campaign_data in game_data.campaigns %}
|
||||||
<article id="campaign-article-{{ campaign_data.campaign.twitch_id }}"
|
<!-- {{ campaign_data.campaign.name }} -->
|
||||||
style="display: flex;
|
<article style="display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
flex-shrink: 0">
|
flex-shrink: 0">
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'twitch:campaign_detail' campaign_data.campaign.twitch_id %}">
|
<a href="{% url 'twitch:campaign_detail' campaign_data.campaign.twitch_id %}">
|
||||||
{% picture campaign_data.campaign.image_best_url|default:campaign_data.campaign.image_url alt="Image for "|add:campaign_data.campaign.name width=120 height=120 style="border-radius: 4px" %}
|
{% picture campaign_data.campaign.image_best_url|default:campaign_data.campaign.image_url alt="Image for "|add:campaign_data.campaign.name width=120 %}
|
||||||
<h4 style="margin: 0.5rem 0; text-align: left;">{{ campaign_data.campaign.clean_name }}</h4>
|
<h4 style="margin: 0.5rem 0; text-align: left;">{{ campaign_data.campaign.clean_name }}</h4>
|
||||||
</a>
|
</a>
|
||||||
|
<!-- End time -->
|
||||||
<time datetime="{{ campaign_data.campaign.end_at|date:'c' }}"
|
<time datetime="{{ campaign_data.campaign.end_at|date:'c' }}"
|
||||||
title="{{ campaign_data.campaign.end_at|date:'DATETIME_FORMAT' }}"
|
title="{{ campaign_data.campaign.end_at|date:'DATETIME_FORMAT' }}"
|
||||||
style="font-size: 0.9rem;
|
style="font-size: 0.9rem;
|
||||||
|
|
@ -66,6 +76,7 @@
|
||||||
text-align: left">
|
text-align: left">
|
||||||
Ends in {{ campaign_data.campaign.end_at|timeuntil }}
|
Ends in {{ campaign_data.campaign.end_at|timeuntil }}
|
||||||
</time>
|
</time>
|
||||||
|
<!-- Start time -->
|
||||||
<time datetime="{{ campaign_data.campaign.start_at|date:'c' }}"
|
<time datetime="{{ campaign_data.campaign.start_at|date:'c' }}"
|
||||||
title="{{ campaign_data.campaign.start_at|date:'DATETIME_FORMAT' }}"
|
title="{{ campaign_data.campaign.start_at|date:'DATETIME_FORMAT' }}"
|
||||||
style="font-size: 0.9rem;
|
style="font-size: 0.9rem;
|
||||||
|
|
@ -73,6 +84,7 @@
|
||||||
text-align: left">
|
text-align: left">
|
||||||
Started {{ campaign_data.campaign.start_at|timesince }} ago
|
Started {{ campaign_data.campaign.start_at|timesince }} ago
|
||||||
</time>
|
</time>
|
||||||
|
<!-- Duration -->
|
||||||
<time datetime="{{ campaign_data.campaign.duration_iso }}"
|
<time datetime="{{ campaign_data.campaign.duration_iso }}"
|
||||||
title="{{ campaign_data.campaign.start_at|date:'DATETIME_FORMAT' }} to {{ campaign_data.campaign.end_at|date:'DATETIME_FORMAT' }}"
|
title="{{ campaign_data.campaign.start_at|date:'DATETIME_FORMAT' }} to {{ campaign_data.campaign.end_at|date:'DATETIME_FORMAT' }}"
|
||||||
style="font-size: 0.9rem;
|
style="font-size: 0.9rem;
|
||||||
|
|
@ -87,27 +99,20 @@
|
||||||
list-style-type: none">
|
list-style-type: none">
|
||||||
{% if campaign_data.campaign.allow_is_enabled %}
|
{% if campaign_data.campaign.allow_is_enabled %}
|
||||||
{% if campaign_data.allowed_channels %}
|
{% if campaign_data.allowed_channels %}
|
||||||
{% for channel in campaign_data.allowed_channels %}
|
{% for channel in campaign_data.allowed_channels|slice:":5" %}
|
||||||
{% if forloop.counter <= 5 %}
|
<!-- {{ channel.name }} -->
|
||||||
<li style="margin-bottom: 0.1rem;">
|
<li style="margin-bottom: 0.1rem;">
|
||||||
<a href="https://twitch.tv/{{ channel.name }}"
|
<a href="https://twitch.tv/{{ channel.name }}"
|
||||||
rel="nofollow ugc"
|
rel="nofollow ugc"
|
||||||
title="Watch {{ channel.display_name }} on Twitch">
|
title="Watch {{ channel.display_name }} on Twitch">
|
||||||
{{ channel.display_name }}
|
{{ channel.display_name }}</a><a href="{% url 'twitch:channel_detail' channel.twitch_id %}"
|
||||||
</a>
|
|
||||||
<a href="{% url 'twitch:channel_detail' channel.twitch_id %}"
|
|
||||||
title="View {{ channel.display_name }} details"
|
title="View {{ channel.display_name }} details"
|
||||||
style="font-family: monospace;
|
style="font-family: monospace;
|
||||||
text-decoration: none">[i]</a>
|
text-decoration: none">[i]</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if campaign_data.allowed_channels|length > 5 %}
|
|
||||||
<li style="margin-bottom: 0.1rem; color: #666; font-style: italic;">
|
|
||||||
... and {{ campaign_data.allowed_channels|length|add:"-5" }} more
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<!-- No allowed channels means drops are available in any stream of the game's category -->
|
||||||
{% if campaign.game.twitch_directory_url %}
|
{% if campaign.game.twitch_directory_url %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ campaign.game.twitch_directory_url }}"
|
<a href="{{ campaign.game.twitch_directory_url }}"
|
||||||
|
|
@ -120,8 +125,15 @@
|
||||||
<li>Failed to get Twitch category URL :(</li>
|
<li>Failed to get Twitch category URL :(</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if campaign_data.allowed_channels|length > 5 %}
|
||||||
|
<!-- {{ campaign_data.allowed_channels|length }} allowed channels -->
|
||||||
|
<li style="margin-bottom: 0.1rem; color: #666; font-style: italic;">
|
||||||
|
... and {{ campaign_data.allowed_channels|length|add:"-5" }} more
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if campaign_data.campaign.game.twitch_directory_url %}
|
{% if campaign_data.campaign.game.twitch_directory_url %}
|
||||||
|
<!--{{ campaign_data.campaign.game.display_name }} Twitch directory URL: {{ campaign_data.campaign.game.twitch_directory_url }} -->
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ campaign_data.campaign.game.twitch_directory_url }}"
|
<a href="{{ campaign_data.campaign.game.twitch_directory_url }}"
|
||||||
rel="nofollow ugc"
|
rel="nofollow ugc"
|
||||||
|
|
@ -130,6 +142,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<!-- {{ campaign_data.campaign.game.display_name }} Twitch directory URL not available -->
|
||||||
<li>Failed to get Twitch directory URL :(</li>
|
<li>Failed to get Twitch directory URL :(</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Emotes</h1>
|
<h1>Emotes</h1>
|
||||||
{% for emote in emotes %}
|
{% for emote in emotes %}
|
||||||
|
<!-- Emote from campaign {{ emote.campaign.name }} -->
|
||||||
|
<!-- https://ttvdrops.lovinator.space{{ emote.image_url }} -->
|
||||||
<a href="{% url 'twitch:campaign_detail' emote.campaign.twitch_id %}"
|
<a href="{% url 'twitch:campaign_detail' emote.campaign.twitch_id %}"
|
||||||
title="{{ emote.campaign.name }}"
|
title="{{ emote.campaign.name }}"
|
||||||
style="display: inline-block">
|
style="display: inline-block">
|
||||||
|
|
|
||||||
|
|
@ -16,31 +16,41 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock extra_head %}
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Game Title -->
|
<div style="display: flex; align-items: flex-start;">
|
||||||
<h1>
|
|
||||||
{{ game.display_name }}
|
|
||||||
{% if game.display_name != game.name and game.name %}<small>({{ game.name }})</small>{% endif %}
|
|
||||||
</h1>
|
|
||||||
<!-- Game image -->
|
<!-- Game image -->
|
||||||
|
<div style="margin-right: 16px;">
|
||||||
{% if game.box_art_best_url %}
|
{% if game.box_art_best_url %}
|
||||||
{% picture game.box_art_best_url alt=game.name width=160 %}
|
{% picture game.box_art_best_url alt=game.name width=160 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- Game Title and Details -->
|
||||||
|
<div style="display: flex; flex-direction: column;">
|
||||||
|
<h1 style="margin-top: 0; margin-bottom: 0px;">
|
||||||
|
{{ game.display_name }}
|
||||||
|
{% if game.display_name != game.name and game.name %}<small>({{ game.name }})</small>{% endif %}
|
||||||
|
</h1>
|
||||||
<!-- Game owner -->
|
<!-- Game owner -->
|
||||||
{% if owners %}
|
{% if owners %}
|
||||||
<small>
|
<small>
|
||||||
|
Owned by
|
||||||
{% for owner in owners %}
|
{% for owner in owners %}
|
||||||
<a id="owner-link-{{ owner.twitch_id }}"
|
<a href="{% url 'twitch:organization_detail' owner.twitch_id %}">{{ owner.name }}</a>
|
||||||
href="{% url 'twitch:organization_detail' owner.twitch_id %}">{{ owner.name }}</a>
|
|
||||||
{% if not forloop.last %},{% endif %}
|
{% if not forloop.last %},{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</small>
|
</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
Twitch ID: <a href="https://www.twitch.tv/directory/category/{{ game.slug|urlencode }}">{{ game.twitch_id }}</a>
|
||||||
|
</div>
|
||||||
|
<div>Twitch slug: {{ game.slug }}</div>
|
||||||
<!-- RSS Feeds -->
|
<!-- RSS Feeds -->
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'twitch:game_campaign_feed' game.twitch_id %}"
|
<a href="{% url 'twitch:game_campaign_feed' game.twitch_id %}"
|
||||||
title="RSS feed for {{ game.display_name }} campaigns">[rss - {{ game.display_name|default:game.name|lower }}]</a>
|
title="RSS feed for {{ game.display_name }} campaigns">[rss]</a>
|
||||||
<a href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}"
|
<a href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}"
|
||||||
title="Atom feed for {{ game.display_name }} campaigns">[atom - {{ game.display_name|default:game.name|lower }}]</a>
|
title="Atom feed for {{ game.display_name }} campaigns">[atom]</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if active_campaigns %}
|
{% if active_campaigns %}
|
||||||
<h5 id="active-campaigns-header">Active Campaigns</h5>
|
<h5 id="active-campaigns-header">Active Campaigns</h5>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Reward Campaigns - Twitch Drops Tracker
|
Reward Campaigns
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link rel="alternate"
|
<link rel="alternate"
|
||||||
|
|
@ -14,47 +14,27 @@
|
||||||
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
||||||
{% endblock extra_head %}
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 id="page-title">Reward Campaigns (Quest Rewards)</h1>
|
<h1>Reward Campaigns</h1>
|
||||||
<p>Browse all available quest reward campaigns</p>
|
|
||||||
<!-- RSS Feeds -->
|
<!-- RSS Feeds -->
|
||||||
<div style="margin-bottom: 1rem;">
|
<div>
|
||||||
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
||||||
style="margin-right: 1rem"
|
title="RSS feed for all reward campaigns">[rss]</a>
|
||||||
title="RSS feed for all reward campaigns">RSS feed for all reward campaigns</a>
|
|
||||||
<a href="{% url 'twitch:reward_campaign_feed_atom' %}"
|
<a href="{% url 'twitch:reward_campaign_feed_atom' %}"
|
||||||
title="Atom feed for all reward campaigns">[atom]</a>
|
title="Atom feed for all reward campaigns">[atom]</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Filter Form -->
|
<p>This is an archive of old Twitch reward campaigns because we do not monitor them.</p>
|
||||||
<form id="filter-form"
|
<p>
|
||||||
method="get"
|
Feel free to submit a pull request on <a href="https://github.com/TheLovinator1/ttvdrops">GitHub</a>
|
||||||
action="{% url 'twitch:reward_campaign_list' %}">
|
with a working implementation :-).
|
||||||
<label for="game">Game:</label>
|
</p>
|
||||||
<select id="game" name="game">
|
|
||||||
<option value="">All Games</option>
|
|
||||||
{% for game in games %}
|
|
||||||
<option value="{{ game.twitch_id }}"
|
|
||||||
{% if selected_game == game.twitch_id %}selected{% endif %}>
|
|
||||||
{{ game.display_name|default:game.name|default:game.slug|default:game.twitch_id }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<label for="status">Status:</label>
|
|
||||||
<select id="status" name="status">
|
|
||||||
<option value="">All Statuses</option>
|
|
||||||
{% for status in status_options %}
|
|
||||||
<option value="{{ status }}"
|
|
||||||
{% if selected_status == status %}selected{% endif %}>{{ status|title }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<button id="apply-filters-button" type="submit">Apply Filters</button>
|
|
||||||
</form>
|
|
||||||
{% if reward_campaigns %}
|
{% if reward_campaigns %}
|
||||||
|
{% comment %}
|
||||||
<h5>Active Reward Campaigns</h5>
|
<h5>Active Reward Campaigns</h5>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for campaign in reward_campaigns %}
|
{% for campaign in reward_campaigns %}
|
||||||
{% if campaign.starts_at <= now and campaign.ends_at >= now %}
|
{% if campaign.starts_at <= now and campaign.ends_at >= now %}
|
||||||
<tr id="reward-campaign-{{ campaign.twitch_id }}">
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'twitch:reward_campaign_detail' campaign.twitch_id %}">
|
<a href="{% url 'twitch:reward_campaign_detail' campaign.twitch_id %}">
|
||||||
{% if campaign.brand %}
|
{% if campaign.brand %}
|
||||||
|
|
@ -83,12 +63,15 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% endcomment %}
|
||||||
|
{% comment %}
|
||||||
<h5>Upcoming Reward Campaigns</h5>
|
<h5>Upcoming Reward Campaigns</h5>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for campaign in reward_campaigns %}
|
{% for campaign in reward_campaigns %}
|
||||||
|
|
||||||
{% if campaign.starts_at > now %}
|
{% if campaign.starts_at > now %}
|
||||||
<tr id="reward-campaign-{{ campaign.twitch_id }}">
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'twitch:reward_campaign_detail' campaign.twitch_id %}">
|
<a href="{% url 'twitch:reward_campaign_detail' campaign.twitch_id %}">
|
||||||
{% if campaign.brand %}
|
{% if campaign.brand %}
|
||||||
|
|
@ -117,12 +100,13 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% endcomment %}
|
||||||
<h5>Past Reward Campaigns</h5>
|
<h5>Past Reward Campaigns</h5>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for campaign in reward_campaigns %}
|
{% for campaign in reward_campaigns %}
|
||||||
{% if campaign.ends_at < now %}
|
{% if campaign.ends_at < now %}
|
||||||
<tr id="reward-campaign-{{ campaign.twitch_id }}">
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'twitch:reward_campaign_detail' campaign.twitch_id %}">
|
<a href="{% url 'twitch:reward_campaign_detail' campaign.twitch_id %}">
|
||||||
{% if campaign.brand %}
|
{% if campaign.brand %}
|
||||||
|
|
|
||||||
|
|
@ -599,6 +599,14 @@ class DropCampaign(auto_prefetch.Model):
|
||||||
"""Return the campaign image URL for RSS enclosures."""
|
"""Return the campaign image URL for RSS enclosures."""
|
||||||
return self.image_best_url
|
return self.image_best_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sorted_benefits(self) -> list[DropBenefit]:
|
||||||
|
"""Return a sorted list of benefits for the campaign."""
|
||||||
|
benefits: list[DropBenefit] = []
|
||||||
|
for drop in self.time_based_drops.all(): # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
benefits.extend(drop.benefits.all()) # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
return sorted(benefits, key=lambda benefit: benefit.name)
|
||||||
|
|
||||||
|
|
||||||
# MARK: DropBenefit
|
# MARK: DropBenefit
|
||||||
class DropBenefit(auto_prefetch.Model):
|
class DropBenefit(auto_prefetch.Model):
|
||||||
|
|
|
||||||
|
|
@ -503,7 +503,7 @@ class TestChannelListView:
|
||||||
)
|
)
|
||||||
game.owners.add(org1, org2)
|
game.owners.add(org1, org2)
|
||||||
|
|
||||||
campaign: DropCampaign = DropCampaign.objects.create(
|
_campaign: DropCampaign = DropCampaign.objects.create(
|
||||||
twitch_id="camp1",
|
twitch_id="camp1",
|
||||||
name="Campaign",
|
name="Campaign",
|
||||||
game=game,
|
game=game,
|
||||||
|
|
@ -519,14 +519,11 @@ class TestChannelListView:
|
||||||
if isinstance(context, list):
|
if isinstance(context, list):
|
||||||
context = context[-1]
|
context = context[-1]
|
||||||
|
|
||||||
|
# campaigns_by_game should include one deduplicated campaign entry for the game.
|
||||||
assert "campaigns_by_game" in context
|
assert "campaigns_by_game" in context
|
||||||
assert game.twitch_id in context["campaigns_by_game"]
|
assert game.twitch_id in context["campaigns_by_game"]
|
||||||
assert len(context["campaigns_by_game"][game.twitch_id]["campaigns"]) == 1
|
assert len(context["campaigns_by_game"][game.twitch_id]["campaigns"]) == 1
|
||||||
|
|
||||||
# Template renders each campaign with a stable id, so we can assert it appears once.
|
|
||||||
html = response.content.decode("utf-8")
|
|
||||||
assert html.count(f"campaign-article-{campaign.twitch_id}") == 1
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_debug_view(self, client: Client) -> None:
|
def test_debug_view(self, client: Client) -> None:
|
||||||
"""Test debug view returns 200 and has games_without_owner in context."""
|
"""Test debug view returns 200 and has games_without_owner in context."""
|
||||||
|
|
|
||||||
|
|
@ -290,23 +290,29 @@ def search_view(request: HttpRequest) -> HttpResponse:
|
||||||
results["games"] = Game.objects.filter(
|
results["games"] = Game.objects.filter(
|
||||||
Q(name__istartswith=query) | Q(display_name__istartswith=query),
|
Q(name__istartswith=query) | Q(display_name__istartswith=query),
|
||||||
)
|
)
|
||||||
|
|
||||||
results["campaigns"] = DropCampaign.objects.filter(
|
results["campaigns"] = DropCampaign.objects.filter(
|
||||||
Q(name__istartswith=query) | Q(description__icontains=query),
|
Q(name__istartswith=query) | Q(description__icontains=query),
|
||||||
).select_related("game")
|
).select_related("game")
|
||||||
|
|
||||||
results["drops"] = TimeBasedDrop.objects.filter(
|
results["drops"] = TimeBasedDrop.objects.filter(
|
||||||
name__istartswith=query,
|
name__istartswith=query,
|
||||||
).select_related("campaign")
|
).select_related("campaign")
|
||||||
|
|
||||||
results["benefits"] = DropBenefit.objects.filter(
|
results["benefits"] = DropBenefit.objects.filter(
|
||||||
name__istartswith=query,
|
name__istartswith=query,
|
||||||
).prefetch_related("drops__campaign")
|
).prefetch_related("drops__campaign")
|
||||||
|
|
||||||
results["reward_campaigns"] = RewardCampaign.objects.filter(
|
results["reward_campaigns"] = RewardCampaign.objects.filter(
|
||||||
Q(name__istartswith=query)
|
Q(name__istartswith=query)
|
||||||
| Q(brand__istartswith=query)
|
| Q(brand__istartswith=query)
|
||||||
| Q(summary__icontains=query),
|
| Q(summary__icontains=query),
|
||||||
).select_related("game")
|
).select_related("game")
|
||||||
|
|
||||||
results["badge_sets"] = ChatBadgeSet.objects.filter(
|
results["badge_sets"] = ChatBadgeSet.objects.filter(
|
||||||
set_id__istartswith=query,
|
set_id__istartswith=query,
|
||||||
)
|
)
|
||||||
|
|
||||||
results["badges"] = ChatBadge.objects.filter(
|
results["badges"] = ChatBadge.objects.filter(
|
||||||
Q(title__istartswith=query) | Q(description__icontains=query),
|
Q(title__istartswith=query) | Q(description__icontains=query),
|
||||||
).select_related("badge_set")
|
).select_related("badge_set")
|
||||||
|
|
@ -317,20 +323,25 @@ def search_view(request: HttpRequest) -> HttpResponse:
|
||||||
results["games"] = Game.objects.filter(
|
results["games"] = Game.objects.filter(
|
||||||
Q(name__icontains=query) | Q(display_name__icontains=query),
|
Q(name__icontains=query) | Q(display_name__icontains=query),
|
||||||
)
|
)
|
||||||
|
|
||||||
results["campaigns"] = DropCampaign.objects.filter(
|
results["campaigns"] = DropCampaign.objects.filter(
|
||||||
Q(name__icontains=query) | Q(description__icontains=query),
|
Q(name__icontains=query) | Q(description__icontains=query),
|
||||||
).select_related("game")
|
).select_related("game")
|
||||||
|
|
||||||
results["drops"] = TimeBasedDrop.objects.filter(
|
results["drops"] = TimeBasedDrop.objects.filter(
|
||||||
name__icontains=query,
|
name__icontains=query,
|
||||||
).select_related("campaign")
|
).select_related("campaign")
|
||||||
|
|
||||||
results["benefits"] = DropBenefit.objects.filter(
|
results["benefits"] = DropBenefit.objects.filter(
|
||||||
name__icontains=query,
|
name__icontains=query,
|
||||||
).prefetch_related("drops__campaign")
|
).prefetch_related("drops__campaign")
|
||||||
|
|
||||||
results["reward_campaigns"] = RewardCampaign.objects.filter(
|
results["reward_campaigns"] = RewardCampaign.objects.filter(
|
||||||
Q(name__icontains=query)
|
Q(name__icontains=query)
|
||||||
| Q(brand__icontains=query)
|
| Q(brand__icontains=query)
|
||||||
| Q(summary__icontains=query),
|
| Q(summary__icontains=query),
|
||||||
).select_related("game")
|
).select_related("game")
|
||||||
|
|
||||||
results["badge_sets"] = ChatBadgeSet.objects.filter(set_id__icontains=query)
|
results["badge_sets"] = ChatBadgeSet.objects.filter(set_id__icontains=query)
|
||||||
results["badges"] = ChatBadge.objects.filter(
|
results["badges"] = ChatBadge.objects.filter(
|
||||||
Q(title__icontains=query) | Q(description__icontains=query),
|
Q(title__icontains=query) | Q(description__icontains=query),
|
||||||
|
|
@ -1127,42 +1138,13 @@ class GameDetailView(DetailView):
|
||||||
either end date or status.
|
either end date or status.
|
||||||
"""
|
"""
|
||||||
context: dict[str, Any] = super().get_context_data(**kwargs)
|
context: dict[str, Any] = super().get_context_data(**kwargs)
|
||||||
game: Game = self.get_object() # pyright: ignore[reportAssignmentType]
|
game: Game = self.object # pyright: ignore[reportAssignmentType]
|
||||||
|
|
||||||
now: datetime.datetime = timezone.now()
|
now: datetime.datetime = timezone.now()
|
||||||
# For each drop, find awarded badge (distribution_type BADGE)
|
|
||||||
drop_awarded_badges: dict[str, ChatBadge] = {}
|
|
||||||
drops: QuerySet[TimeBasedDrop, TimeBasedDrop] = TimeBasedDrop.objects.filter(
|
|
||||||
campaign__game=game,
|
|
||||||
).prefetch_related("benefits")
|
|
||||||
|
|
||||||
# Materialize drops so we can iterate multiple times without extra DB hits
|
|
||||||
drops_list: list[TimeBasedDrop] = list(drops)
|
|
||||||
|
|
||||||
# Collect all benefit names that award badges
|
|
||||||
benefit_badge_titles: set[str] = set()
|
|
||||||
for drop in drops_list:
|
|
||||||
for benefit in drop.benefits.all():
|
|
||||||
if benefit.distribution_type == "BADGE" and benefit.name:
|
|
||||||
benefit_badge_titles.add(benefit.name)
|
|
||||||
|
|
||||||
# Bulk-load all matching ChatBadge instances to avoid N+1 queries
|
|
||||||
badges_by_title: dict[str, ChatBadge] = {
|
|
||||||
badge.title: badge
|
|
||||||
for badge in ChatBadge.objects.filter(title__in=benefit_badge_titles)
|
|
||||||
}
|
|
||||||
|
|
||||||
for drop in drops_list:
|
|
||||||
for benefit in drop.benefits.all():
|
|
||||||
if benefit.distribution_type == "BADGE":
|
|
||||||
badge: ChatBadge | None = badges_by_title.get(benefit.name)
|
|
||||||
if badge:
|
|
||||||
drop_awarded_badges[drop.twitch_id] = badge
|
|
||||||
|
|
||||||
all_campaigns: QuerySet[DropCampaign] = (
|
all_campaigns: QuerySet[DropCampaign] = (
|
||||||
DropCampaign.objects
|
DropCampaign.objects
|
||||||
.filter(game=game)
|
.filter(game=game)
|
||||||
.prefetch_related("game__owners")
|
.select_related("game")
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
"time_based_drops",
|
"time_based_drops",
|
||||||
|
|
@ -1177,9 +1159,34 @@ class GameDetailView(DetailView):
|
||||||
.order_by("-end_at")
|
.order_by("-end_at")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
campaigns_list: list[DropCampaign] = list(all_campaigns)
|
||||||
|
|
||||||
|
# For each drop, find awarded badge (distribution_type BADGE)
|
||||||
|
drop_awarded_badges: dict[str, ChatBadge] = {}
|
||||||
|
benefit_badge_titles: set[str] = set()
|
||||||
|
for campaign in campaigns_list:
|
||||||
|
for drop in campaign.time_based_drops.all(): # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
for benefit in drop.benefits.all():
|
||||||
|
if benefit.distribution_type == "BADGE" and benefit.name:
|
||||||
|
benefit_badge_titles.add(benefit.name)
|
||||||
|
|
||||||
|
# Bulk-load all matching ChatBadge instances to avoid N+1 queries
|
||||||
|
badges_by_title: dict[str, ChatBadge] = {
|
||||||
|
badge.title: badge
|
||||||
|
for badge in ChatBadge.objects.filter(title__in=benefit_badge_titles)
|
||||||
|
}
|
||||||
|
|
||||||
|
for campaign in campaigns_list:
|
||||||
|
for drop in campaign.time_based_drops.all(): # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
for benefit in drop.benefits.all():
|
||||||
|
if benefit.distribution_type == "BADGE":
|
||||||
|
badge: ChatBadge | None = badges_by_title.get(benefit.name)
|
||||||
|
if badge:
|
||||||
|
drop_awarded_badges[drop.twitch_id] = badge
|
||||||
|
|
||||||
active_campaigns: list[DropCampaign] = [
|
active_campaigns: list[DropCampaign] = [
|
||||||
campaign
|
campaign
|
||||||
for campaign in all_campaigns
|
for campaign in campaigns_list
|
||||||
if campaign.start_at is not None
|
if campaign.start_at is not None
|
||||||
and campaign.start_at <= now
|
and campaign.start_at <= now
|
||||||
and campaign.end_at is not None
|
and campaign.end_at is not None
|
||||||
|
|
@ -1195,7 +1202,7 @@ class GameDetailView(DetailView):
|
||||||
|
|
||||||
upcoming_campaigns: list[DropCampaign] = [
|
upcoming_campaigns: list[DropCampaign] = [
|
||||||
campaign
|
campaign
|
||||||
for campaign in all_campaigns
|
for campaign in campaigns_list
|
||||||
if campaign.start_at is not None and campaign.start_at > now
|
if campaign.start_at is not None and campaign.start_at > now
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1209,7 +1216,7 @@ class GameDetailView(DetailView):
|
||||||
|
|
||||||
expired_campaigns: list[DropCampaign] = [
|
expired_campaigns: list[DropCampaign] = [
|
||||||
campaign
|
campaign
|
||||||
for campaign in all_campaigns
|
for campaign in campaigns_list
|
||||||
if campaign.end_at is not None and campaign.end_at < now
|
if campaign.end_at is not None and campaign.end_at < now
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1229,10 +1236,10 @@ class GameDetailView(DetailView):
|
||||||
)
|
)
|
||||||
game_data: list[dict[str, Any]] = json.loads(serialized_game)
|
game_data: list[dict[str, Any]] = json.loads(serialized_game)
|
||||||
|
|
||||||
if all_campaigns.exists():
|
if campaigns_list:
|
||||||
serialized_campaigns = serialize(
|
serialized_campaigns = serialize(
|
||||||
"json",
|
"json",
|
||||||
all_campaigns,
|
campaigns_list,
|
||||||
fields=(
|
fields=(
|
||||||
"twitch_id",
|
"twitch_id",
|
||||||
"name",
|
"name",
|
||||||
|
|
@ -2028,13 +2035,13 @@ class ChannelDetailView(DetailView):
|
||||||
dict: Context data with active, upcoming, and expired campaigns.
|
dict: Context data with active, upcoming, and expired campaigns.
|
||||||
"""
|
"""
|
||||||
context: dict[str, Any] = super().get_context_data(**kwargs)
|
context: dict[str, Any] = super().get_context_data(**kwargs)
|
||||||
channel: Channel = self.get_object() # pyright: ignore[reportAssignmentType]
|
channel: Channel = self.object # pyright: ignore[reportAssignmentType]
|
||||||
|
|
||||||
now: datetime.datetime = timezone.now()
|
now: datetime.datetime = timezone.now()
|
||||||
all_campaigns: QuerySet[DropCampaign] = (
|
all_campaigns: QuerySet[DropCampaign] = (
|
||||||
DropCampaign.objects
|
DropCampaign.objects
|
||||||
.filter(allow_channels=channel)
|
.filter(allow_channels=channel)
|
||||||
.prefetch_related("game__owners")
|
.select_related("game")
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
"time_based_drops",
|
"time_based_drops",
|
||||||
|
|
@ -2049,9 +2056,11 @@ class ChannelDetailView(DetailView):
|
||||||
.order_by("-start_at")
|
.order_by("-start_at")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
campaigns_list: list[DropCampaign] = list(all_campaigns)
|
||||||
|
|
||||||
active_campaigns: list[DropCampaign] = [
|
active_campaigns: list[DropCampaign] = [
|
||||||
campaign
|
campaign
|
||||||
for campaign in all_campaigns
|
for campaign in campaigns_list
|
||||||
if campaign.start_at is not None
|
if campaign.start_at is not None
|
||||||
and campaign.start_at <= now
|
and campaign.start_at <= now
|
||||||
and campaign.end_at is not None
|
and campaign.end_at is not None
|
||||||
|
|
@ -2067,7 +2076,7 @@ class ChannelDetailView(DetailView):
|
||||||
|
|
||||||
upcoming_campaigns: list[DropCampaign] = [
|
upcoming_campaigns: list[DropCampaign] = [
|
||||||
campaign
|
campaign
|
||||||
for campaign in all_campaigns
|
for campaign in campaigns_list
|
||||||
if campaign.start_at is not None and campaign.start_at > now
|
if campaign.start_at is not None and campaign.start_at > now
|
||||||
]
|
]
|
||||||
upcoming_campaigns.sort(
|
upcoming_campaigns.sort(
|
||||||
|
|
@ -2080,7 +2089,7 @@ class ChannelDetailView(DetailView):
|
||||||
|
|
||||||
expired_campaigns: list[DropCampaign] = [
|
expired_campaigns: list[DropCampaign] = [
|
||||||
campaign
|
campaign
|
||||||
for campaign in all_campaigns
|
for campaign in campaigns_list
|
||||||
if campaign.end_at is not None and campaign.end_at < now
|
if campaign.end_at is not None and campaign.end_at < now
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2091,10 +2100,10 @@ class ChannelDetailView(DetailView):
|
||||||
)
|
)
|
||||||
channel_data: list[dict[str, Any]] = json.loads(serialized_channel)
|
channel_data: list[dict[str, Any]] = json.loads(serialized_channel)
|
||||||
|
|
||||||
if all_campaigns.exists():
|
if campaigns_list:
|
||||||
serialized_campaigns: str = serialize(
|
serialized_campaigns: str = serialize(
|
||||||
"json",
|
"json",
|
||||||
all_campaigns,
|
campaigns_list,
|
||||||
fields=(
|
fields=(
|
||||||
"twitch_id",
|
"twitch_id",
|
||||||
"name",
|
"name",
|
||||||
|
|
@ -2112,7 +2121,7 @@ class ChannelDetailView(DetailView):
|
||||||
channel_data[0]["fields"]["campaigns"] = campaigns_data
|
channel_data[0]["fields"]["campaigns"] = campaigns_data
|
||||||
|
|
||||||
name: str = channel.display_name or channel.name or channel.twitch_id
|
name: str = channel.display_name or channel.name or channel.twitch_id
|
||||||
total_campaigns: int = len(all_campaigns)
|
total_campaigns: int = len(campaigns_list)
|
||||||
description: str = f"{name} participates in {total_campaigns} drop campaign"
|
description: str = f"{name} participates in {total_campaigns} drop campaign"
|
||||||
if total_campaigns > 1:
|
if total_campaigns > 1:
|
||||||
description += "s"
|
description += "s"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue