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

@ -55,6 +55,16 @@
<a href="{% url 'accounts:login' %}">Login</a> | <a href="{% url 'accounts:login' %}">Login</a> |
<a href="{% url 'accounts:signup' %}">Sign Up</a> <a href="{% url 'accounts:signup' %}">Sign Up</a>
{% endif %} {% endif %}
{% if messages %}
<ul>
{% for message in messages %}
<li>
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important:{% endif %}
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
{% block content %} {% block content %}
<!-- Main content will be injected here --> <!-- Main content will be injected here -->
{% endblock content %} {% endblock content %}

View file

@ -4,14 +4,29 @@
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
<h1>{{ game.display_name }}</h1> <h1>{{ game.display_name }}</h1>
{% if user.is_authenticated %}
<form method="post"
action="{% url 'twitch:subscribe_notifications' game_id=game.id %}">
{% csrf_token %}
<div> <div>
<input type="checkbox" id="found" /> <input type="checkbox"
id="found"
name="notify_found"
{% if subscription and subscription.notify_found %}checked{% endif %} />
<label for="found">🔔 Get notified as soon as a drop for {{ game.display_name }} appears on Twitch.</label> <label for="found">🔔 Get notified as soon as a drop for {{ game.display_name }} appears on Twitch.</label>
</div> </div>
<div> <div>
<input type="checkbox" id="live" /> <input type="checkbox"
id="live"
name="notify_live"
{% if subscription and subscription.notify_live %}checked{% endif %} />
<label for="live">🎮 Get notified when the drop is live and ready to be farmed.</label> <label for="live">🎮 Get notified when the drop is live and ready to be farmed.</label>
</div> </div>
<button type="submit">Save notification preferences</button>
</form>
{% else %}
Login to subscribe!
{% endif %}
{% if active_campaigns %} {% if active_campaigns %}
<h5>Active Campaigns</h5> <h5>Active Campaigns</h5>
<table> <table>

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.db import models
from django.utils import timezone from django.utils import timezone
from accounts.models import User
class Game(models.Model): class Game(models.Model):
"""Represents a game on Twitch.""" """Represents a game on Twitch."""
@ -195,3 +197,18 @@ class DropBenefitEdge(models.Model):
def __str__(self) -> str: def __str__(self) -> str:
"""Return a string representation of the drop benefit edge.""" """Return a string representation of the drop benefit edge."""
return f"{self.drop.name} - {self.benefit.name}" 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("campaigns/<str:pk>/", views.DropCampaignDetailView.as_view(), name="campaign_detail"),
path("games/", views.GameListView.as_view(), name="game_list"), path("games/", views.GameListView.as_view(), name="game_list"),
path("games/<str:pk>/", views.GameDetailView.as_view(), name="game_detail"), 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 dataclasses import dataclass
from typing import TYPE_CHECKING, Any, cast 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 import Count, Prefetch, Q
from django.db.models.query import QuerySet 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.utils import timezone
from django.views.generic import DetailView, ListView 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: if TYPE_CHECKING:
import datetime import datetime
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseRedirect
logger: logging.Logger = logging.getLogger(__name__) logger: logging.Logger = logging.getLogger(__name__)
@ -229,6 +232,12 @@ class GameDetailView(DetailView):
context: dict[str, Any] = super().get_context_data(**kwargs) context: dict[str, Any] = super().get_context_data(**kwargs)
game: Game = self.get_object() 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() now: datetime.datetime = timezone.now()
all_campaigns: QuerySet[DropCampaign, DropCampaign] = ( all_campaigns: QuerySet[DropCampaign, DropCampaign] = (
DropCampaign.objects.filter(game=game).select_related("owner").order_by("-end_at") DropCampaign.objects.filter(game=game).select_related("owner").order_by("-end_at")
@ -248,6 +257,7 @@ class GameDetailView(DetailView):
"active_campaigns": active_campaigns, "active_campaigns": active_campaigns,
"upcoming_campaigns": upcoming_campaigns, "upcoming_campaigns": upcoming_campaigns,
"expired_campaigns": expired_campaigns, "expired_campaigns": expired_campaigns,
"subscription": subscription,
"now": now, "now": now,
}) })
@ -305,3 +315,45 @@ def dashboard(request: HttpRequest) -> HttpResponse:
"now": now, "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)