Use template tags instead of partial HTML

This commit is contained in:
2024-08-18 05:41:50 +02:00
parent 3ff3fe157a
commit 534de60f9f
9 changed files with 277 additions and 155 deletions

View File

@ -76,11 +76,9 @@ async def add_reward_campaign(campaign: dict | None) -> None:
"""
if not campaign:
return
logger.info("Adding reward campaign to database %s", campaign["id"])
if "data" in campaign and "rewardCampaignsAvailableToUser" in campaign["data"]:
for reward_campaign in campaign["data"]["rewardCampaignsAvailableToUser"]:
our_reward_campaign = await handle_reward_campaign(reward_campaign)
our_reward_campaign: RewardCampaign = await handle_reward_campaign(reward_campaign)
if "rewards" in reward_campaign:
for reward in reward_campaign["rewards"]:
@ -126,6 +124,7 @@ async def handle_reward_campaign(reward_campaign: dict) -> RewardCampaign:
RewardCampaign: The reward campaign that was added or updated.
"""
mappings: dict[str, str] = {
"name": "name",
"brand": "brand",
"createdAt": "created_at",
"startsAt": "starts_at",
@ -209,8 +208,7 @@ async def add_or_update_owner(owner_json: dict | None) -> Owner | None:
if not owner_json:
return None
defaults = {}
defaults: dict[str, str] = {}
if owner_json.get("name"):
defaults["name"] = owner_json["name"]
@ -235,7 +233,7 @@ async def add_or_update_channels(channels_json: list[dict]) -> list[Channel] | N
channels: list[Channel] = []
for channel_json in channels_json:
defaults = {}
defaults: dict[str, str] = {}
if channel_json.get("displayName"):
defaults["display_name"] = channel_json["displayName"]
@ -266,7 +264,7 @@ async def add_benefit(benefit: dict, time_based_drop: TimeBasedDrop) -> None:
"isIosAvailable": "is_ios_available",
"name": "name",
}
defaults = {new_key: benefit[key] for key, new_key in mappings.items() if benefit.get(key)}
defaults: dict[str, str] = {new_key: benefit[key] for key, new_key in mappings.items() if benefit.get(key)}
our_benefit, created = await Benefit.objects.aupdate_or_create(id=benefit["id"], defaults=defaults)
if created:
logger.info("Added benefit %s", our_benefit.id)
@ -284,10 +282,11 @@ async def add_drop_campaign(drop_campaign: dict | None) -> None:
if not drop_campaign:
return
defaults = {}
defaults: dict[str, str | Game] = {}
if drop_campaign.get("game"):
game: Game | None = await add_or_update_game(drop_campaign["game"])
defaults["game"] = game
if game:
defaults["game"] = game
mappings: dict[str, str] = {
"accountLinkURL": "account_link_url",
@ -327,19 +326,22 @@ async def add_time_based_drops(drop_campaign: dict, our_drop_campaign: DropCampa
our_drop_campaign (DropCampaign): The drop campaign object in the database.
"""
for time_based_drop in drop_campaign.get("timeBasedDrops", []):
time_based_drop: dict[str, typing.Any]
if time_based_drop.get("preconditionDrops"):
# TODO(TheLovinator): Add precondition drops to time-based drop # noqa: TD003
# TODO(TheLovinator): Send JSON to Discord # noqa: TD003
logger.error("Not implemented: Add precondition drops to time-based drop, JSON: %s", time_based_drop)
mappings = {
mappings: dict[str, str] = {
"requiredSubs": "required_subs",
"endAt": "ends_at",
"name": "name",
"requiredMinutesWatched": "required_minutes_watched",
"startAt": "starts_at",
}
defaults = {new_key: time_based_drop[key] for key, new_key in mappings.items() if time_based_drop.get(key)}
defaults: dict[str, str | DropCampaign] = {
new_key: time_based_drop[key] for key, new_key in mappings.items() if time_based_drop.get(key)
}
if our_drop_campaign:
defaults["drop_campaign"] = our_drop_campaign
@ -373,18 +375,18 @@ async def process_json_data(num: int, campaign: dict | None) -> None:
# This is a Reward Campaign
if "rewardCampaignsAvailableToUser" in campaign.get("data", {}):
save_json(campaign, "reward_campaigns")
await add_reward_campaign(campaign)
save_json(campaign=campaign, dir_name="reward_campaigns")
await add_reward_campaign(campaign=campaign)
if "dropCampaign" in campaign.get("data", {}).get("user", {}):
save_json(campaign, "drop_campaign")
save_json(campaign=campaign, dir_name="drop_campaign")
if campaign.get("data", {}).get("user", {}).get("dropCampaign"):
await add_drop_campaign(campaign["data"]["user"]["dropCampaign"])
await add_drop_campaign(drop_campaign=campaign["data"]["user"]["dropCampaign"])
if "dropCampaigns" in campaign.get("data", {}).get("currentUser", {}):
for drop_campaign in campaign["data"]["currentUser"]["dropCampaigns"]:
save_json(campaign, "drop_campaigns")
await add_drop_campaign(drop_campaign)
save_json(campaign=campaign, dir_name="drop_campaigns")
await add_drop_campaign(drop_campaign=drop_campaign)
class Command(BaseCommand):
@ -423,7 +425,7 @@ class Command(BaseCommand):
while not logged_in:
try:
await page.wait_for_selector(
'div[data-a-target="top-nav-avatar"]',
selector='div[data-a-target="top-nav-avatar"]',
timeout=300000,
)
logged_in = True
@ -449,7 +451,7 @@ class Command(BaseCommand):
async def run_with_playwright(self) -> None:
async with async_playwright() as playwright:
await self.run(playwright)
await self.run(playwright=playwright)
if __name__ == "__main__":

View File

@ -171,13 +171,13 @@ MESSAGE_TAGS: dict[int, str] = {
messages.ERROR: "alert-danger",
}
CACHE_MIDDLEWARE_SECONDS = 60 * 60 * 24 # 1 day
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
"LOCATION": DATA_DIR / "django_cache",
},
}
# CACHE_MIDDLEWARE_SECONDS = 60 * 60 * 24 # 1 day
# CACHES = {
# "default": {
# "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
# "LOCATION": DATA_DIR / "django_cache",
# },
# }
SITE_ID = 1

View File

@ -1,29 +1,30 @@
{% extends "base.html" %}
{% load static %}
{% load static campaign_tags game_tags %}
{% block content %}
<div class="container mt-4">
<div class="row">
<div class="col-lg-3">{{ toc|safe }}</div>
<div class="col-lg-9">
{% include "partials/info_box.html" %}
{% include "partials/news.html" %}
<h2>
Reward campaigns -
<div class="d-inline text-muted">{{ reward_campaigns.count }} campaigns</div>
</h2>
{% for campaign in reward_campaigns %}
{% include "partials/reward_campaign_card.html" %}
{% endfor %}
<h2>
Drop campaigns -
<div class="d-inline text-muted ">{{ games.count }} games</div>
</h2>
{% for game in games %}
{% if game.drop_campaigns.count > 0 %}
{% include "partials/game_card.html" %}
{% endif %}
{% endfor %}
</div>
<div class="container mt-4">
<div class="row">
<div class="col-lg-3">{{ toc|safe }}</div>
<div class="col-lg-9">
{% include "partials/info_box.html" %}
{% include "partials/news.html" %}
<h2>
Reward campaign -
<div class="d-inline text-muted">
{{ reward_campaigns.count }}
campaign{{ reward_campaigns.count|pluralize }}
</div>
</h2>
{% for campaign in reward_campaigns %}
{% render_campaign campaign %}
{% endfor %}
<h2>
Drop campaigns -
<div class="d-inline text-muted ">{{ games.count }} game{{ games.count|pluralize }}</div>
</h2>
{% for game in games %}
{% render_game_card game %}
{% endfor %}
</div>
</div>
</div>
{% endblock content %}

View File

@ -1,52 +0,0 @@
<div class="card mb-4 shadow-sm" id="#{{ game.twitch_id }}">
<div class="row g-0">
<div class="col-md-2">
<img src="{{ game.box_art_url }}"
alt="{{ game.name }} box art"
class="img-fluid rounded-start"
height="283"
width="212"
loading="lazy">
</div>
<div class="col-md-10">
<div class="card-body">
<h2 class="card-title h5">
<a href="https://www.twitch.tv/directory/category/{{ game.slug }}"
class="text-decoration-none">{{ game.name }}</a>
</h2>
<div class="mt-auto">
<!-- Insert nice buttons -->
</div>
{% for campaign in game.drop_campaigns.all %}
<div class="mt-3">
{% if campaign.details_url == campaign.account_link_url %}
<a href="{{ campaign.details_url }}" class="text-decoration-none">Details</a>
{% else %}
<a href="{{ campaign.details_url }}" class="text-decoration-none">Details</a>
|
<a href="{{ campaign.account_link_url }}" class="text-decoration-none">Link Account</a>
{% endif %}
<p class="mb-2 text-muted">
Ends in: <abbr title="{{ campaign.starts_at|date:'l d F H:i' }} - {{ campaign.ends_at|date:'l d F H:i e' }}">{{ campaign.ends_at|timeuntil }}</abbr>
</p>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3">
{% for drop in campaign.drops.all %}
{% for benefit in drop.benefits.all %}
<div class="col d-flex align-items-center position-relative">
<img src="{{ benefit.image_url }}"
alt="{{ benefit.name }} drop image"
class="img-fluid rounded me-3"
height="50"
width="50"
loading="lazy">
{{ benefit.name }}
</div>
{% endfor %}
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>

View File

@ -1,52 +0,0 @@
<div class="card mb-4 shadow-sm" id="campaign-{{ campaign.id }}">
<div class="row g-0">
<div class="col-md-2">
<img src="{{ campaign.image_url }}"
alt="{{ campaign.name }}"
class="img-fluid rounded-start"
height="283"
width="212"
loading="lazy">
</div>
<div class="col-md-10">
<div class="card-body">
<h2 class="card-title h5" id="#reward-{{ campaign.id }}">
<a href="#campaign-{{ campaign.id }}" class="plain-text-item">{{ campaign.name }}</a>
</h2>
<p class="card-text text-muted">{{ campaign.summary }}</p>
<p class="mb-2 text-muted">
Ends in: <abbr title="{{ campaign.starts_at|date:'l d F H:i' }} - {{ campaign.ends_at|date:'l d F H:i e' }}">{{ campaign.ends_at|timeuntil }}</abbr>
</p>
<a href="{{ campaign.external_url }}"
class="btn btn-primary"
target="_blank">Learn More</a>
{% if campaign.instructions %}
<div class="mt-3">
<h3 class="h6">Instructions</h3>
<p>{{ campaign.instructions }}</p>
</div>
{% endif %}
{% if campaign.rewards.exists %}
<div class="mt-3">
<h3 class="h6">Rewards</h3>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
{% for reward in campaign.rewards.all %}
<div class="col d-flex align-items-center position-relative">
<img src="{{ reward.thumbnail_image_url }}"
alt="{{ reward.name }} reward image"
class="img-fluid rounded me-3"
height="50"
width="50"
loading="lazy">
<div>
<strong>{{ reward.name }}</strong>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>

View File

View File

@ -0,0 +1,93 @@
from django import template
from django.utils.html import format_html
from django.utils.safestring import SafeText
from django.utils.timesince import timesince
from django.utils.timezone import now
from core.models import Reward, RewardCampaign
register = template.Library()
@register.simple_tag
def render_campaign(campaign: RewardCampaign) -> SafeText:
"""Render the campaign HTML.
Args:
campaign: The campaign object.
Returns:
The rendered HTML string.
"""
time_remaining = timesince(now(), campaign.ends_at)
ends_in: str = f'{campaign.ends_at.strftime("%A %d %B %H:%M %Z")}' if campaign.ends_at else ""
starts_in: str = f'{campaign.starts_at.strftime("%A %d %B %H:%M %Z")}' if campaign.starts_at else ""
# Start building the HTML
html: str = f"""
<div class="card mb-4 shadow-sm" id="campaign-{campaign.id}">
<div class="row g-0">
<div class="col-md-2">
<img src="{campaign.image_url}"
alt="{campaign.name}"
class="img-fluid rounded-start"
height="283"
width="212"
loading="lazy">
</div>
<div class="col-md-10">
<div class="card-body">
<h2 class="card-title h5" id="#reward-{campaign.id}">
<a href="#campaign-{campaign.id}" class="plain-text-item">{campaign.name}</a>
</h2>
<p class="card-text text-muted">{campaign.summary}</p>
<p class="mb-2 text-muted">
Ends in: <abbr title="{starts_in} - {ends_in}">{time_remaining}</abbr>
</p>
<a href="{campaign.external_url}"
class="btn btn-primary"
target="_blank">Learn More</a>
"""
# Add instructions if present
if campaign.instructions:
html += f"""
<div class="mt-3">
<h3 class="h6">Instructions</h3>
<p>{campaign.instructions}</p>
</div>
"""
# Add rewards if present
if campaign.rewards.exists(): # type: ignore # noqa: PGH003
html += """
<div class="mt-3">
<h3 class="h6">Rewards</h3>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
"""
for reward in campaign.rewards.all(): # type: ignore # noqa: PGH003
reward: Reward
html += f"""
<div class="col d-flex align-items-center position-relative">
<img src="{reward.thumbnail_image_url}"
alt="{reward.name} reward image"
class="img-fluid rounded me-3"
height="50"
width="50"
loading="lazy">
<div>
<strong>{reward.name}</strong>
</div>
</div>
"""
html += "</div></div>"
# Close the main divs
html += """
</div>
</div>
</div>
</div>
"""
return format_html(html)

View File

@ -0,0 +1,130 @@
from django import template
from django.utils.html import format_html
from django.utils.safestring import SafeText
from django.utils.timesince import timesince
from django.utils.timezone import now
from core.models import Benefit, DropCampaign, Game, TimeBasedDrop
register = template.Library()
@register.simple_tag
def render_game_card(game: Game) -> SafeText:
"""Render the game card HTML.
Args:
game: The game object.
Returns:
The rendered HTML string.
"""
twitch_id: str = game.twitch_id
box_art_url: str = game.box_art_url or "https://static-cdn.jtvnw.net/ttv-static/404_boxart.jpg"
name: str = game.name or "Game name unknown"
slug: str = game.slug or "game-name-unknown"
drop_campaigns = game.drop_campaigns.all() # type: ignore # noqa: PGH003
return format_html(
"""
<div class="card mb-4 shadow-sm" id="#{}">
<div class="row g-0">
<div class="col-md-2">
<img src="{}" alt="{} box art" class="img-fluid rounded-start" height="283" width="212" loading="lazy">
</div>
<div class="col-md-10">
<div class="card-body">
<h2 class="card-title h5">
<a href="https://www.twitch.tv/directory/category/{}" class="text-decoration-none">{}</a>
</h2>
<div class="mt-auto">
<!-- Insert nice buttons -->
</div>
{}
</div>
</div>
</div>
</div>
""",
twitch_id,
box_art_url,
name,
slug,
name,
render_campaigns(drop_campaigns),
)
def render_campaigns(campaigns: list[DropCampaign]) -> SafeText:
"""Render the campaigns HTML.
Args:
campaigns: The list of campaigns.
Returns:
The rendered HTML string.
"""
campaign_html: str = ""
for campaign in campaigns:
if campaign.details_url == campaign.account_link_url:
link_html: SafeText = format_html(
'<a href="{}" class="text-decoration-none">Details</a>',
campaign.details_url,
)
else:
link_html: SafeText = format_html(
'<a href="{}" class="text-decoration-none">Details</a> | <a href="{}" class="text-decoration-none">Link Account</a>', # noqa: E501
campaign.details_url,
campaign.account_link_url,
)
time_until: str = timesince(campaign.ends_at, now()) if campaign.ends_at else ""
starts_at: str = campaign.starts_at.strftime("%A %d %B %H:%M") if campaign.starts_at else ""
ends_at: str = campaign.ends_at.strftime("%A %d %B %H:%M") if campaign.ends_at else ""
drops: list[TimeBasedDrop] = campaign.drops.all() # type: ignore # noqa: PGH003
campaign_html += format_html(
"""
<div class="mt-3">
{}
<p class="mb-2 text-muted">Ends in: <abbr title="{} - {}">{}</abbr></p>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3">
{}
</div>
</div>
""",
link_html,
starts_at,
ends_at,
time_until,
render_drops(drops),
)
return format_html(campaign_html)
def render_drops(drops: list[TimeBasedDrop]) -> SafeText:
"""Render the drops HTML.
Args:
drops: The list of drops.
Returns:
The rendered HTML string.
"""
drop_html: str = ""
for drop in drops:
benefits: list[Benefit] = drop.benefits.all() # type: ignore # noqa: PGH003
for benefit in benefits:
image_url: str = benefit.image_url or "https://static-cdn.jtvnw.net/ttv-static/404_boxart.jpg"
name = benefit.name or "Drop name unknown"
drop_html += format_html(
"""
<div class="col d-flex align-items-center position-relative">
<img src="{}" alt="{} drop image" class="img-fluid rounded me-3" height="50" width="50" loading="lazy">
{}
</div>
""",
image_url,
name,
name,
)
return format_html(drop_html)