From fabc9d23f6f371e6671e39d72b8ac4e634df9a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Sat, 2 Aug 2025 04:46:36 +0200 Subject: [PATCH] Add support for subscribing to games --- templates/base.html | 10 ++++ templates/twitch/game_detail.html | 31 +++++++--- .../0002_notificationsubscription.py | 29 ++++++++++ twitch/models.py | 17 ++++++ twitch/urls.py | 1 + twitch/views.py | 56 ++++++++++++++++++- 6 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 twitch/migrations/0002_notificationsubscription.py diff --git a/templates/base.html b/templates/base.html index 3310930..ff010f7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -55,6 +55,16 @@ Login | Sign Up {% endif %} + {% if messages %} + + {% endif %} {% block content %} {% endblock content %} diff --git a/templates/twitch/game_detail.html b/templates/twitch/game_detail.html index d041079..3335444 100644 --- a/templates/twitch/game_detail.html +++ b/templates/twitch/game_detail.html @@ -4,14 +4,29 @@ {% endblock title %} {% block content %}

{{ game.display_name }}

-
- - -
-
- - -
+ {% if user.is_authenticated %} +
+ {% csrf_token %} +
+ + +
+
+ + +
+ +
+ {% else %} + Login to subscribe! + {% endif %} {% if active_campaigns %}
Active Campaigns
diff --git a/twitch/migrations/0002_notificationsubscription.py b/twitch/migrations/0002_notificationsubscription.py new file mode 100644 index 0000000..305cb53 --- /dev/null +++ b/twitch/migrations/0002_notificationsubscription.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.4 on 2025-08-02 02:08 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('twitch', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='NotificationSubscription', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('notify_found', models.BooleanField(default=False)), + ('notify_live', models.BooleanField(default=False)), + ('game', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='twitch.game')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'game')}, + }, + ), + ] diff --git a/twitch/models.py b/twitch/models.py index da07aec..65ea817 100644 --- a/twitch/models.py +++ b/twitch/models.py @@ -5,6 +5,8 @@ from typing import ClassVar from django.db import models from django.utils import timezone +from accounts.models import User + class Game(models.Model): """Represents a game on Twitch.""" @@ -195,3 +197,18 @@ class DropBenefitEdge(models.Model): def __str__(self) -> str: """Return a string representation of the drop benefit edge.""" return f"{self.drop.name} - {self.benefit.name}" + + +class NotificationSubscription(models.Model): + """Users can subscribe to games to get notified.""" + + user = models.ForeignKey(User, on_delete=models.CASCADE) + game = models.ForeignKey(Game, on_delete=models.CASCADE) + notify_found = models.BooleanField(default=False) + notify_live = models.BooleanField(default=False) + + class Meta: + unique_together = ("user", "game") + + def __str__(self) -> str: + return f"{self.user} subscription to {Game.display_name}" diff --git a/twitch/urls.py b/twitch/urls.py index 0c36b52..a0de088 100644 --- a/twitch/urls.py +++ b/twitch/urls.py @@ -12,4 +12,5 @@ urlpatterns = [ path("campaigns//", views.DropCampaignDetailView.as_view(), name="campaign_detail"), path("games/", views.GameListView.as_view(), name="game_list"), path("games//", views.GameDetailView.as_view(), name="game_detail"), + path("games//subscribe/", views.subscribe_notifications, name="subscribe_notifications"), ] diff --git a/twitch/views.py b/twitch/views.py index 6f5b296..94afb42 100644 --- a/twitch/views.py +++ b/twitch/views.py @@ -4,19 +4,22 @@ import logging from dataclasses import dataclass from typing import TYPE_CHECKING, Any, cast +from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.db.models import Count, Prefetch, Q from django.db.models.query import QuerySet -from django.shortcuts import render +from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone from django.views.generic import DetailView, ListView -from twitch.models import DropCampaign, Game, Organization, TimeBasedDrop +from twitch.models import DropCampaign, Game, NotificationSubscription, Organization, TimeBasedDrop if TYPE_CHECKING: import datetime from django.db.models import QuerySet from django.http import HttpRequest, HttpResponse + from django.http.response import HttpResponseRedirect logger: logging.Logger = logging.getLogger(__name__) @@ -229,6 +232,12 @@ class GameDetailView(DetailView): context: dict[str, Any] = super().get_context_data(**kwargs) game: Game = self.get_object() + user = self.request.user + if not user.is_authenticated: + subscription: NotificationSubscription | None = None + else: + subscription = NotificationSubscription.objects.filter(user=user, game=game).first() + now: datetime.datetime = timezone.now() all_campaigns: QuerySet[DropCampaign, DropCampaign] = ( DropCampaign.objects.filter(game=game).select_related("owner").order_by("-end_at") @@ -248,6 +257,7 @@ class GameDetailView(DetailView): "active_campaigns": active_campaigns, "upcoming_campaigns": upcoming_campaigns, "expired_campaigns": expired_campaigns, + "subscription": subscription, "now": now, }) @@ -305,3 +315,45 @@ def dashboard(request: HttpRequest) -> HttpResponse: "now": now, }, ) + + +@login_required +def subscribe_notifications(request: HttpRequest, game_id: str) -> HttpResponseRedirect: + """Update notification for a user. + + Args: + request: The HTTP request. + game_id: The game we are updating. + + Returns: + Redirect back to the twitch:game_detail. + """ + game: Game = get_object_or_404(Game, pk=game_id) + if request.method == "POST": + notify_found = bool(request.POST.get("notify_found")) + notify_live = bool(request.POST.get("notify_live")) + + subscription, created = NotificationSubscription.objects.get_or_create(user=request.user, game=game) + + changes = [] + if not created: + if subscription.notify_found != notify_found: + changes.append(f"Notify when drop is found: {'enabled' if notify_found else 'disabled'}") + if subscription.notify_live != notify_live: + changes.append(f"Notify when drop is farmable: {'enabled' if notify_live else 'disabled'}") + + subscription.notify_found = notify_found + subscription.notify_live = notify_live + subscription.save() + + if created: + message = "You have subscribed to notifications for this game." + elif changes: + message = "Updated notification preferences: " + ", ".join(changes) + else: + message = "No changes were made to your notification preferences." + + messages.success(request, message) + return redirect("twitch:game_detail", pk=game.id) + + return redirect("twitch:game_detail", pk=game.id)