Use template tags instead of partial HTML
This commit is contained in:
@ -43,7 +43,7 @@ repos:
|
||||
|
||||
# An extremely fast Python linter and formatter.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.5.7
|
||||
rev: v0.6.1
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
|
@ -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,9 +282,10 @@ 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"])
|
||||
if game:
|
||||
defaults["game"] = game
|
||||
|
||||
mappings: dict[str, str] = {
|
||||
@ -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__":
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load static campaign_tags game_tags %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
@ -8,20 +8,21 @@
|
||||
{% include "partials/info_box.html" %}
|
||||
{% include "partials/news.html" %}
|
||||
<h2>
|
||||
Reward campaigns -
|
||||
<div class="d-inline text-muted">{{ reward_campaigns.count }} campaigns</div>
|
||||
Reward campaign -
|
||||
<div class="d-inline text-muted">
|
||||
{{ reward_campaigns.count }}
|
||||
campaign{{ reward_campaigns.count|pluralize }}
|
||||
</div>
|
||||
</h2>
|
||||
{% for campaign in reward_campaigns %}
|
||||
{% include "partials/reward_campaign_card.html" %}
|
||||
{% render_campaign campaign %}
|
||||
{% endfor %}
|
||||
<h2>
|
||||
Drop campaigns -
|
||||
<div class="d-inline text-muted ">{{ games.count }} games</div>
|
||||
<div class="d-inline text-muted ">{{ games.count }} game{{ games.count|pluralize }}</div>
|
||||
</h2>
|
||||
{% for game in games %}
|
||||
{% if game.drop_campaigns.count > 0 %}
|
||||
{% include "partials/game_card.html" %}
|
||||
{% endif %}
|
||||
{% render_game_card game %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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>
|
@ -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>
|
0
core/templatetags/__init__.py
Normal file
0
core/templatetags/__init__.py
Normal file
93
core/templatetags/campaign_tags.py
Normal file
93
core/templatetags/campaign_tags.py
Normal 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)
|
130
core/templatetags/game_tags.py
Normal file
130
core/templatetags/game_tags.py
Normal 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)
|
Reference in New Issue
Block a user