Add /game view and fix scraper not adding Owner
This commit is contained in:
@ -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.
|
||||
|
||||
|
@ -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
103
core/templates/game.html
Normal 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 %}
|
@ -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),
|
||||
)
|
||||
|
||||
|
@ -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(),
|
||||
]
|
||||
|
@ -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:
|
||||
|
Reference in New Issue
Block a user