Add support for subscribing to games
This commit is contained in:
parent
c447abc6fe
commit
fabc9d23f6
6 changed files with 134 additions and 10 deletions
|
|
@ -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 %}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,29 @@
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{ game.display_name }}</h1>
|
<h1>{{ game.display_name }}</h1>
|
||||||
<div>
|
{% if user.is_authenticated %}
|
||||||
<input type="checkbox" id="found" />
|
<form method="post"
|
||||||
<label for="found">🔔 Get notified as soon as a drop for {{ game.display_name }} appears on Twitch.</label>
|
action="{% url 'twitch:subscribe_notifications' game_id=game.id %}">
|
||||||
</div>
|
{% csrf_token %}
|
||||||
<div>
|
<div>
|
||||||
<input type="checkbox" id="live" />
|
<input type="checkbox"
|
||||||
<label for="live">🎮 Get notified when the drop is live and ready to be farmed.</label>
|
id="found"
|
||||||
</div>
|
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>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
|
|
||||||
29
twitch/migrations/0002_notificationsubscription.py
Normal file
29
twitch/migrations/0002_notificationsubscription.py
Normal 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')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue