Enhance campaign detail template
This commit is contained in:
parent
e709009b99
commit
ef6c2b84ab
3 changed files with 201 additions and 46 deletions
|
|
@ -2,6 +2,7 @@ from datetime import timedelta
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.client import _MonkeyPatchedWSGIResponse
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
@ -60,3 +61,46 @@ class ChzzkDashboardViewTests(TestCase):
|
||||||
|
|
||||||
assert included in campaigns
|
assert included in campaigns
|
||||||
assert all(c.state != "TESTING" for c in campaigns)
|
assert all(c.state != "TESTING" for c in campaigns)
|
||||||
|
|
||||||
|
def test_campaign_detail_view_renders_chzzk_campaign_and_rewards(self) -> None:
|
||||||
|
"""Test that the campaign detail view correctly renders the details of a chzzk campaign and its rewards."""
|
||||||
|
now: datetime = timezone.now()
|
||||||
|
|
||||||
|
campaign: ChzzkCampaign = ChzzkCampaign.objects.create(
|
||||||
|
campaign_no=2001,
|
||||||
|
title="Campaign Detail Test",
|
||||||
|
description="Detailed campaign description",
|
||||||
|
category_type="game",
|
||||||
|
category_id="1",
|
||||||
|
category_value="TestGame",
|
||||||
|
service_id="chzzk",
|
||||||
|
state="ACTIVE",
|
||||||
|
start_date=now - timedelta(days=1),
|
||||||
|
end_date=now + timedelta(days=1),
|
||||||
|
has_ios_based_reward=False,
|
||||||
|
drops_campaign_not_started=False,
|
||||||
|
source_api="unit-test",
|
||||||
|
)
|
||||||
|
|
||||||
|
campaign.rewards.create( # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
reward_no=10,
|
||||||
|
title="Reward A",
|
||||||
|
reward_type="ITEM",
|
||||||
|
campaign_reward_type="Standard",
|
||||||
|
condition_type="watch",
|
||||||
|
condition_for_minutes=15,
|
||||||
|
ios_based_reward=False,
|
||||||
|
code_remaining_count=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
response: _MonkeyPatchedWSGIResponse = self.client.get(
|
||||||
|
reverse("chzzk:campaign_detail", args=[campaign.campaign_no]),
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
content: str = response.content.decode()
|
||||||
|
assert campaign.title in content
|
||||||
|
assert "Reward A" in content
|
||||||
|
assert "watch" in content
|
||||||
|
assert "100" in content
|
||||||
|
assert "No" in content # ios_based_reward=False
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
@ -62,7 +63,8 @@ def campaign_detail_view(request: HttpRequest, campaign_no: int) -> HttpResponse
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The HTTP response containing the rendered campaign detail page.
|
HttpResponse: The HTTP response containing the rendered campaign detail page.
|
||||||
"""
|
"""
|
||||||
campaign: models.ChzzkCampaign = models.ChzzkCampaign.objects.get(
|
campaign: models.ChzzkCampaign = get_object_or_404(
|
||||||
|
models.ChzzkCampaign,
|
||||||
campaign_no=campaign_no,
|
campaign_no=campaign_no,
|
||||||
)
|
)
|
||||||
rewards: QuerySet[models.ChzzkReward, models.ChzzkReward] = campaign.rewards.all() # pyright: ignore[reportAttributeAccessIssue]
|
rewards: QuerySet[models.ChzzkReward, models.ChzzkReward] = campaign.rewards.all() # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,161 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load image_tags %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ campaign.title }}
|
{{ campaign.title }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="All chzzk campaigns (RSS)"
|
||||||
|
href="{% url 'chzzk:campaign_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="All chzzk campaigns (Atom)"
|
||||||
|
href="{% url 'chzzk:campaign_feed_atom' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="All chzzk campaigns (Discord)"
|
||||||
|
href="{% url 'chzzk:campaign_feed_discord' %}" />
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<div style="display: flex; align-items: flex-start;">
|
||||||
<h1>{{ campaign.title }}</h1>
|
<!-- Campaign image -->
|
||||||
<nav>
|
<div style="margin-right: 16px;">
|
||||||
<a href="{% url 'chzzk:dashboard' %}">chzzk</a> > <a href="{% url 'chzzk:campaign_list' %}">Campaigns</a> > {{ campaign.title }}
|
|
||||||
</nav>
|
|
||||||
{% if campaign.image_url %}
|
{% if campaign.image_url %}
|
||||||
<img src="{{ campaign.image_url }}"
|
<img src="{{ campaign.image_url }}"
|
||||||
alt="{{ campaign.title }}"
|
alt="{{ campaign.title }}"
|
||||||
height="auto"
|
width="160"
|
||||||
width="auto"
|
height="auto" />
|
||||||
style="max-width:200px;
|
|
||||||
height:auto;
|
|
||||||
border-radius:8px" />
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if campaign.description %}
|
|
||||||
<section class="description">
|
|
||||||
{{ campaign.description|linebreaksbr }}
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
<section class="times">
|
|
||||||
{% if campaign.starts_at %}
|
|
||||||
<div>
|
|
||||||
Starts: <time datetime="{{ campaign.starts_at|date:'c' }}">{{ campaign.starts_at|date:'M d, Y H:i' }}</time>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<!-- Campaign Title -->
|
||||||
{% if campaign.ends_at %}
|
<div style="display: flex; flex-direction: column;">
|
||||||
|
<h1 style="margin-top: 0; margin-bottom: 0px;">{{ campaign.title }}</h1>
|
||||||
|
<!-- Add breadcrumbs -->
|
||||||
<div>
|
<div>
|
||||||
Ends: <time datetime="{{ campaign.ends_at|date:'c' }}">{{ campaign.ends_at|date:'M d, Y H:i' }}</time>
|
<a href="{% url 'chzzk:dashboard' %}">chzzk</a> > <a href="{% url 'chzzk:campaign_list' %}">Campaigns</a> > {{ campaign.title }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<!-- Campaign description -->
|
||||||
</section>
|
<p>{{ campaign.description|linebreaksbr }}</p>
|
||||||
<hr />
|
<div>
|
||||||
<h2>Rewards</h2>
|
Published
|
||||||
{% if rewards %}
|
{% if campaign.scraped_at %}
|
||||||
<ul>
|
<time datetime="{{ campaign.scraped_at|date:'c' }}"
|
||||||
{% for r in rewards %}<li>{{ r.title }} — {{ r.condition_for_minutes }} minutes of watch time</li>{% endfor %}
|
title="{{ campaign.scraped_at|date:'DATETIME_FORMAT' }}">{{ campaign.scraped_at|date:"M d, Y H:i" }}</time> ({{ campaign.scraped_at|timesince }} ago)
|
||||||
</ul>
|
{% elif campaign.start_date %}
|
||||||
|
<time datetime="{{ campaign.start_date|date:'c' }}"
|
||||||
|
title="{{ campaign.start_date|date:'DATETIME_FORMAT' }}">{{ campaign.start_date|date:"M d, Y H:i" }}</time> ({{ campaign.start_date|timesince }} ago)
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No rewards available.</p>
|
unknown
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if campaign.external_url %}
|
</div>
|
||||||
<p>
|
<div>
|
||||||
<a class="btn" href="{{ campaign.external_url }}">View on chzzk</a>
|
Last updated
|
||||||
</p>
|
{% if campaign.scraped_at %}
|
||||||
|
<time datetime="{{ campaign.scraped_at|date:'c' }}"
|
||||||
|
title="{{ campaign.scraped_at|date:'DATETIME_FORMAT' }}">{{ campaign.scraped_at|date:"M d, Y H:i" }}</time> ({{ campaign.scraped_at|timesince }} ago)
|
||||||
|
{% elif campaign.updated_at %}
|
||||||
|
<time datetime="{{ campaign.updated_at|date:'c' }}"
|
||||||
|
title="{{ campaign.updated_at|date:'DATETIME_FORMAT' }}">{{ campaign.updated_at|date:"M d, Y H:i" }}</time> ({{ campaign.updated_at|timesince }} ago)
|
||||||
|
{% else %}
|
||||||
|
unknown
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- Campaign end times -->
|
||||||
|
<div>
|
||||||
|
{% if campaign.end_date %}
|
||||||
|
{% if campaign.end_date < now %}
|
||||||
|
Ended
|
||||||
|
<time datetime="{{ campaign.end_date|date:'c' }}"
|
||||||
|
title="{{ campaign.end_date|date:'DATETIME_FORMAT' }}">{{ campaign.end_date|date:"M d, Y H:i" }}</time> ({{ campaign.end_date|timesince }} ago)
|
||||||
|
{% else %}
|
||||||
|
Ends in
|
||||||
|
<time datetime="{{ campaign.end_date|date:'c' }}"
|
||||||
|
title="{{ campaign.end_date|date:'DATETIME_FORMAT' }}">{{ campaign.end_date|date:"M d, Y H:i" }}</time> (in {{ campaign.end_date|timeuntil }})
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
Ends unknown
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- Campaign start times -->
|
||||||
|
<div>
|
||||||
|
{% if campaign.start_date %}
|
||||||
|
{% if campaign.start_date > now %}
|
||||||
|
Starts in
|
||||||
|
<time datetime="{{ campaign.start_date|date:'c' }}"
|
||||||
|
title="{{ campaign.start_date|date:'DATETIME_FORMAT' }}">{{ campaign.start_date|date:"M d, Y H:i" }}</time> (in {{ campaign.start_date|timeuntil }})
|
||||||
|
{% else %}
|
||||||
|
Started
|
||||||
|
<time datetime="{{ campaign.start_date|date:'c' }}"
|
||||||
|
title="{{ campaign.start_date|date:'DATETIME_FORMAT' }}">{{ campaign.start_date|date:"M d, Y H:i" }}</time> ({{ campaign.start_date|timesince }} ago)
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
Starts unknown
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- Campaign duration -->
|
||||||
|
<div>
|
||||||
|
Duration is
|
||||||
|
{% if campaign.start_date and campaign.end_date %}
|
||||||
|
<time datetime="P{{ campaign.end_date|date:'Y' }}-{{ campaign.end_date|date:'m' }}-{{ campaign.end_date|date:'d' }}T{{ campaign.end_date|date:'H' }}:00:00"
|
||||||
|
title="{{ campaign.start_date|date:'DATETIME_FORMAT' }} to {{ campaign.end_date|date:'DATETIME_FORMAT' }}">{{ campaign.end_date|timeuntil:campaign.start_date }}</time>
|
||||||
|
{% else %}
|
||||||
|
unknown
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div>
|
||||||
|
{% if campaign.pc_link_url %}<a href="{{ campaign.pc_link_url }}" target="_blank">[details]</a>{% endif %}
|
||||||
|
{% if campaign.account_link_url %}<a href="{{ campaign.account_link_url }}" target="_blank">[connect]</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5>Campaign Info</h5>
|
||||||
|
{% if rewards %}
|
||||||
|
<table style="border-collapse: collapse; width: 100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="text-align: left; padding: 4px;">Image</th>
|
||||||
|
<th style="text-align: left; padding: 4px;">Reward</th>
|
||||||
|
<th style="text-align: left; padding: 4px;">Type</th>
|
||||||
|
<th style="text-align: left; padding: 4px;">Condition</th>
|
||||||
|
<th style="text-align: left; padding: 4px;">iOS Reward</th>
|
||||||
|
<th style="text-align: left; padding: 4px;">Codes Left</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for reward in rewards %}
|
||||||
|
<tr>
|
||||||
|
<td style="vertical-align: top; padding: 4px;">
|
||||||
|
{% if reward.image_url %}
|
||||||
|
<img src="{{ reward.image_url }}"
|
||||||
|
alt="{{ reward.title }}"
|
||||||
|
width="160"
|
||||||
|
height="auto"
|
||||||
|
style="object-fit: cover;
|
||||||
|
margin-right: 3px" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding: 4px;">
|
||||||
|
{{ reward.title }}
|
||||||
|
{% if reward.condition_for_minutes %}
|
||||||
|
<div style="font-size: 0.9em; color: #a9a9a9;">{{ reward.condition_for_minutes }} minutes watched</div>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td style="vertical-align: top; padding: 4px;">{{ reward.reward_type }}</td>
|
||||||
|
<td style="vertical-align: top; padding: 4px;">{{ reward.condition_type }}</td>
|
||||||
|
<td style="vertical-align: top; padding: 4px;">{{ reward.ios_based_reward|yesno:"Yes,No" }}</td>
|
||||||
|
{% if reward.reward_type == "CODE_FIRST_COME" %}
|
||||||
|
<td style="vertical-align: top; padding: 4px;">{{ reward.code_remaining_count }}</td>
|
||||||
|
{% else %}
|
||||||
|
<td style="vertical-align: top; padding: 4px;">Unlimited</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>No rewards available for this campaign.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</main>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue