Add support for subscribing to games

This commit is contained in:
Joakim Hellsén 2025-08-02 04:46:36 +02:00
commit fabc9d23f6
6 changed files with 134 additions and 10 deletions

View file

@ -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')},
},
),
]

View file

@ -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}"

View file

@ -12,4 +12,5 @@ urlpatterns = [
path("campaigns/<str:pk>/", views.DropCampaignDetailView.as_view(), name="campaign_detail"),
path("games/", views.GameListView.as_view(), name="game_list"),
path("games/<str:pk>/", views.GameDetailView.as_view(), name="game_detail"),
path("games/<str:game_id>/subscribe/", views.subscribe_notifications, name="subscribe_notifications"),
]

View file

@ -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)