Add /game view and fix scraper not adding Owner

This commit is contained in:
2024-08-22 06:06:58 +02:00
parent 6d5821d0aa
commit 8f68b38116
6 changed files with 184 additions and 26 deletions

View File

@ -162,11 +162,12 @@ async def handle_reward_campaign(reward_campaign: dict) -> RewardCampaign:
return reward_campaign_instance
async def add_or_update_game(game_json: dict | None) -> Game | None:
async def add_or_update_game(game_json: dict | None, owner: Owner | None) -> Game | None:
"""Add or update a game in the database.
Args:
game_json (dict): The game to add or update.
owner (Owner): The owner of the game.
Returns:
Game: The game that was added or updated.
@ -179,20 +180,17 @@ async def add_or_update_game(game_json: dict | None) -> Game | None:
"displayName": "name",
"boxArtURL": "box_art_url",
}
defaults = {new_key: game_json[key] for key, new_key in mappings.items() if game_json.get(key)}
defaults: dict = {new_key: game_json[key] for key, new_key in mappings.items() if game_json.get(key)}
if game_json.get("slug"):
defaults["game_url"] = f"https://www.twitch.tv/directory/game/{game_json["slug"]}"
if game_json.get("owner"):
owner: Owner | None = await add_or_update_owner(game_json["owner"])
if owner:
defaults["org"] = owner
our_game, created = await Game.objects.aupdate_or_create(twitch_id=game_json["id"], defaults=defaults)
if created:
logger.info("Added game %s", our_game.twitch_id)
if owner:
await owner.games.aadd(our_game) # type: ignore # noqa: PGH003
return our_game
@ -282,9 +280,12 @@ async def add_drop_campaign(drop_campaign: dict | None) -> None:
if not drop_campaign:
return
defaults: dict[str, str | Game] = {}
defaults: dict[str, str | Game | Owner] = {}
owner: Owner | None = await get_owner(drop_campaign)
if drop_campaign.get("game"):
game: Game | None = await add_or_update_game(drop_campaign["game"])
game: Game | None = await add_or_update_game(drop_campaign["game"], owner)
if game:
defaults["game"] = game
@ -318,6 +319,21 @@ async def add_drop_campaign(drop_campaign: dict | None) -> None:
await add_time_based_drops(drop_campaign, our_drop_campaign)
async def get_owner(drop_campaign: dict) -> Owner | None:
"""Get the owner of the drop campaign.
Args:
drop_campaign (dict): The drop campaign containing the owner.
Returns:
Owner: The owner of the drop campaign.
"""
owner = None
if drop_campaign.get("owner"):
owner: Owner | None = await add_or_update_owner(drop_campaign["owner"])
return owner
async def add_time_based_drops(drop_campaign: dict, our_drop_campaign: DropCampaign) -> None:
"""Add time-based drops to the database.

View File

@ -107,7 +107,7 @@ class TimeBasedDrop(models.Model):
class Benefit(models.Model):
"""This is the benefit we will see on the front end."""
"""Benefits are the rewards for the drops."""
id = models.TextField(primary_key=True) # "d5cdf372-502b-11ef-bafd-0a58a9feac02"
created_at = models.DateTimeField(null=True, auto_created=True) # "2024-08-11T00:00:00Z"

103
core/templates/game.html Normal file
View File

@ -0,0 +1,103 @@
{% extends "base.html" %}
{% block content %}
<div class="container my-5">
<div class="text-center">
<header class="h2 mt-4">
{{ game.name }}
</header>
<img src="{{ game.box_art_url }}" alt="{{ game.name }} box art" class="img-fluid rounded" height="283"
width="212" loading="lazy">
</div>
<div class="mt-5">
<h3 class="h4">Game Details</h3>
<ul class="list-group">
<li class="list-group-item"><strong>Twitch ID:</strong> {{ game.twitch_id }}</li>
<li class="list-group-item"><strong>Game URL:</strong> <a href="{{ game.url }}"
target="_blank">{{ game.url }}</a></li>
<li class="list-group-item"><strong>Game name:</strong> {{ game.name }}</li>
<li class="list-group-item"><strong>Game box art URL:</strong> <a href="{{ game.box_art_url }}"
target="_blank">{{ game.box_art_url }}</a></li>
</ul>
</div>
<div class="mt-5">
<h3 class="h4">Organization</h3>
<ul class="list-group">
{% if game.org %}
<li class="list-group-item">
<a href="#">{{ game.org.name }} - <span class="text-muted">{{ game.org.id }}</span></a>
</li>
{% else %}
<li class="list-group-item">No organization associated with this game.</li>
{% endif %}
</ul>
</div>
<div class="mt-5">
<h3 class="h4">Drop Campaigns</h3>
{% if game.drop_campaigns.all %}
<div>
{% for drop_campaign in game.drop_campaigns.all %}
<div>
<h2>
{{ drop_campaign.name }}
</h2>
<div>
<div>
<img src="{{ drop_campaign.image_url }}" alt="{{ drop_campaign.name }} image"
class="img-fluid mb-3 rounded">
<p><strong>Status:</strong> {{ drop_campaign.status }}</p>
<p>{{ drop_campaign.description }}</p>
<p><strong>Starts at:</strong> {{ drop_campaign.starts_at }}</p>
<p><strong>Ends at:</strong> {{ drop_campaign.ends_at }}</p>
<p><strong>More details:</strong> <a href="{{ drop_campaign.details_url }}"
target="_blank">{{ drop_campaign.details_url }}</a></p>
<p><strong>Account Link:</strong> <a href="{{ drop_campaign.account_link_url }}"
target="_blank">{{ drop_campaign.account_link_url }}</a></p>
<h2 class="mt-4">Time-Based Drops</h2>
{% if drop_campaign.drops.all %}
<div>
{% for drop in drop_campaign.drops.all %}
<hr>
<div>
<h3 class="mb-2">{{ drop.name }}</h3>
{% if drop.benefits.all %}
{% for benefit in drop.benefits.all %}
<img src="{{ benefit.image_url }}" alt="{{ benefit.name }} image"
class="img-fluid rounded mb-2">
<p><strong>Required Subscriptions:</strong> {{ drop.required_subs }}</p>
<p><strong>Required Minutes Watched:</strong> {{ drop.required_minutes_watched }}</p>
<p><strong>Starts at:</strong> {{ drop.starts_at }}</p>
<p><strong>Ends at:</strong> {{ drop.ends_at }}</p>
<p><strong>Entitlement Limit:</strong> {{ benefit.entitlement_limit }}</p>
<p><strong>Available on iOS:</strong> {{ benefit.is_ios_available }}</p>
<p><strong>Twitch Created At:</strong> {{ benefit.twitch_created_at }}</p>
{% empty %}
<div>No benefits available for this drop.</div>
{% endfor %}
{% else %}
<p>No benefits available for this drop.</p>
{% endif %}
</div>
{% empty %}
<div>No time-based drops available for this campaign.</div>
{% endfor %}
</div>
{% else %}
<p>No time-based drops available for this campaign.</p>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p>No drop campaigns available for this game.</p>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@ -33,7 +33,9 @@ def render_game_card(game: Game) -> SafeText:
<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>
<span>
<a href="/game/{}" class="text-decoration-none">{}</a> - <a href="https://www.twitch.tv/directory/category/{}" class="text-decoration-none text-muted">Twitch</a>
</span>
</h2>
<div class="mt-auto">
<!-- Insert nice buttons -->
@ -43,11 +45,12 @@ def render_game_card(game: Game) -> SafeText:
</div>
</div>
</div>
""",
""", # noqa: E501
box_art_url,
name,
slug,
game.twitch_id,
name,
slug,
render_campaigns(drop_campaigns),
)

View File

@ -4,7 +4,7 @@ from debug_toolbar.toolbar import debug_toolbar_urls
from django.contrib import admin
from django.urls import URLPattern, URLResolver, path
from core.views import WebhooksView, game_view, index, reward_campaign_view
from core.views import WebhooksView, game_view, games_view, index, reward_campaign_view
app_name: str = "core"
@ -12,7 +12,8 @@ urlpatterns: list[URLPattern | URLResolver] = [
path(route="admin/", view=admin.site.urls),
path(route="", view=index, name="index"),
path(route="webhooks/", view=WebhooksView.as_view(), name="webhooks"),
path(route="games/", view=game_view, name="games"),
path(route="game/<int:twitch_id>/", view=game_view, name="game"),
path(route="games/", view=games_view, name="games"),
path(route="reward_campaigns/", view=reward_campaign_view, name="reward_campaigns"),
*debug_toolbar_urls(),
]

View File

@ -35,21 +35,24 @@ def get_games_with_drops() -> BaseManager[Game]:
Returns:
BaseManager[Game]: The games with drops.
"""
active_campaigns_query: BaseManager[DropCampaign] = DropCampaign.objects.filter(ends_at__gte=timezone.now())
active_time_based_drops: BaseManager[TimeBasedDrop] = TimeBasedDrop.objects.filter(ends_at__gte=timezone.now())
# Prefetch the benefits for the active drops.
# Benefits have more information about the drop. Used for getting image_url.
benefits: BaseManager[Benefit] = Benefit.objects.all()
benefits_prefetch = Prefetch(lookup="benefits", queryset=benefits)
active_time_based_drops: BaseManager[TimeBasedDrop] = TimeBasedDrop.objects.filter(
ends_at__gte=timezone.now(),
).prefetch_related(benefits_prefetch)
benefits_prefetch = Prefetch(lookup="benefits", queryset=Benefit.objects.all())
time_based_drops_prefetch = Prefetch(
lookup="drops",
queryset=active_time_based_drops.prefetch_related(benefits_prefetch),
)
# Prefetch the drops for the active campaigns.
active_campaigns: BaseManager[DropCampaign] = DropCampaign.objects.filter(ends_at__gte=timezone.now())
drops_prefetch = Prefetch(lookup="drops", queryset=active_time_based_drops)
campaigns_prefetch = Prefetch(
lookup="drop_campaigns",
queryset=active_campaigns_query.prefetch_related(time_based_drops_prefetch),
queryset=active_campaigns.prefetch_related(drops_prefetch),
)
return (
Game.objects.filter(drop_campaigns__in=active_campaigns_query)
Game.objects.filter(drop_campaigns__in=active_campaigns)
.distinct()
.prefetch_related(campaigns_prefetch)
.order_by("name")
@ -80,7 +83,39 @@ def index(request: HttpRequest) -> HttpResponse:
return TemplateResponse(request, "index.html", context)
def game_view(request: HttpRequest) -> HttpResponse:
def game_view(request: HttpRequest, twitch_id: int) -> HttpResponse:
"""Render the game view page.
Args:
request (HttpRequest): The request object.
twitch_id (int): The Twitch ID of the game.
Returns:
HttpResponse: The response object.
"""
try:
time_based_drops_prefetch = Prefetch(
lookup="drops",
queryset=TimeBasedDrop.objects.prefetch_related("benefits"),
)
drop_campaigns_prefetch = Prefetch(
lookup="drop_campaigns",
queryset=DropCampaign.objects.prefetch_related(time_based_drops_prefetch),
)
game: Game = (
Game.objects.select_related("org").prefetch_related(drop_campaigns_prefetch).get(twitch_id=twitch_id)
)
except Game.DoesNotExist:
return HttpResponse(status=404)
except Game.MultipleObjectsReturned:
return HttpResponse(status=500)
context: dict[str, Any] = {"game": game}
return TemplateResponse(request=request, template="game.html", context=context)
def games_view(request: HttpRequest) -> HttpResponse:
"""Render the game view page.
Args: