Allow subscribe to orgs
This commit is contained in:
parent
fabc9d23f6
commit
4af2b02a01
10 changed files with 190 additions and 14 deletions
|
|
@ -40,6 +40,7 @@ lint.ignore = [
|
||||||
"CPY001", # Checks for the absence of copyright notices within Python files.
|
"CPY001", # Checks for the absence of copyright notices within Python files.
|
||||||
"D100", # Checks for undocumented public module definitions.
|
"D100", # Checks for undocumented public module definitions.
|
||||||
"D104", # Checks for undocumented public package definitions.
|
"D104", # Checks for undocumented public package definitions.
|
||||||
|
"D105", # Checks for undocumented magic method definitions.
|
||||||
"D106", # Checks for undocumented public class definitions, for nested classes.
|
"D106", # Checks for undocumented public class definitions, for nested classes.
|
||||||
"ERA001", # Checks for commented-out Python code.
|
"ERA001", # Checks for commented-out Python code.
|
||||||
"FIX002", # Checks for "TODO" comments.
|
"FIX002", # Checks for "TODO" comments.
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@
|
||||||
<a href="{% url 'twitch:dashboard' %}">Dashboard</a> |
|
<a href="{% url 'twitch:dashboard' %}">Dashboard</a> |
|
||||||
<a href="{% url 'twitch:campaign_list' %}">Campaigns</a> |
|
<a href="{% url 'twitch:campaign_list' %}">Campaigns</a> |
|
||||||
<a href="{% url 'twitch:game_list' %}">Games</a> |
|
<a href="{% url 'twitch:game_list' %}">Games</a> |
|
||||||
|
<a href="{% url 'twitch:org_list' %}">Organizations</a> |
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<a href="{% url 'admin:index' %}">Admin</a> |
|
<a href="{% url 'admin:index' %}">Admin</a> |
|
||||||
|
|
@ -60,7 +61,7 @@
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<li>
|
<li>
|
||||||
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important:{% endif %}
|
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important:{% endif %}
|
||||||
{{ message }}
|
{{ message|linebreaksbr }}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
{# TODO: Link to organization #}
|
{# TODO: Link to organization #}
|
||||||
<a href="#">{{ campaign.owner.name }}</a>
|
<a href="{% url 'twitch:org_detail' campaign.owner.id %}">{{ campaign.owner.name }}</a>
|
||||||
</p>
|
</p>
|
||||||
{% if campaign.image_url %}
|
{% if campaign.image_url %}
|
||||||
<img height="70"
|
<img height="70"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Games by Organization - Twitch Drops Tracker
|
Games
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Games by Organization</h1>
|
<h1>Games</h1>
|
||||||
{% if games_by_org %}
|
{% if games_by_org %}
|
||||||
{% for organization, games in games_by_org.items %}
|
{% for organization, games in games_by_org.items %}
|
||||||
<h2>{{ organization.name }}</h2>
|
<h2>{{ organization.name }}</h2>
|
||||||
|
|
|
||||||
18
templates/twitch/org_list.html
Normal file
18
templates/twitch/org_list.html
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}
|
||||||
|
Games
|
||||||
|
{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Organizations</h1>
|
||||||
|
{% if orgs %}
|
||||||
|
<ul>
|
||||||
|
{% for organization in orgs %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'twitch:org_detail' organization.id %}">{{ organization.name }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
No games found.
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content %}
|
||||||
37
templates/twitch/organization_detail.html
Normal file
37
templates/twitch/organization_detail.html
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}
|
||||||
|
{{ organization.name }}
|
||||||
|
{% endblock title %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ organization.name }}</h1>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<form method="post"
|
||||||
|
action="{% url 'twitch:subscribe_org_notifications' org_id=organization.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div>
|
||||||
|
<input type="checkbox"
|
||||||
|
id="found"
|
||||||
|
name="notify_found"
|
||||||
|
{% if subscription and subscription.notify_found %}checked{% endif %} />
|
||||||
|
<label for="found">🔔 Notify me when a drop for {{ organization.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">🎮 Notify me when the drop is live and ready to be farmed.</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Save preferences</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
Login to subscribe!
|
||||||
|
{% endif %}
|
||||||
|
<ul>
|
||||||
|
{% for game in games %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'twitch:game_detail' pk=game.id %}">{{ game }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 5.2.4 on 2025-08-02 03:39
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('twitch', '0002_notificationsubscription'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='notificationsubscription',
|
||||||
|
unique_together={('user', 'game')},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='notificationsubscription',
|
||||||
|
name='organization',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='twitch.organization'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notificationsubscription',
|
||||||
|
name='game',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='twitch.game'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='notificationsubscription',
|
||||||
|
unique_together={('user', 'game'), ('user', 'organization')},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -203,12 +203,17 @@ class NotificationSubscription(models.Model):
|
||||||
"""Users can subscribe to games to get notified."""
|
"""Users can subscribe to games to get notified."""
|
||||||
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
game = models.ForeignKey(Game, on_delete=models.CASCADE)
|
game = models.ForeignKey(Game, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
|
organization = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
notify_found = models.BooleanField(default=False)
|
notify_found = models.BooleanField(default=False)
|
||||||
notify_live = models.BooleanField(default=False)
|
notify_live = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("user", "game")
|
unique_together: ClassVar[list[tuple[str, str]]] = [
|
||||||
|
("user", "game"),
|
||||||
|
("user", "organization"),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.user} subscription to {Game.display_name}"
|
return f"{self.user} subscription to {Game.display_name}"
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,8 @@ 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"),
|
path("games/<str:game_id>/subscribe/", views.subscribe_game_notifications, name="subscribe_notifications"),
|
||||||
|
path("organizations/", views.OrgListView.as_view(), name="org_list"),
|
||||||
|
path("organizations/<str:pk>/", views.OrgDetailView.as_view(), name="org_detail"),
|
||||||
|
path("organizations/<str:org_id>/subscribe/", views.subscribe_org_notifications, name="subscribe_org_notifications"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
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.http.response import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect, 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
|
||||||
|
|
@ -24,6 +25,37 @@ if TYPE_CHECKING:
|
||||||
logger: logging.Logger = logging.getLogger(__name__)
|
logger: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OrgListView(ListView):
|
||||||
|
"""List view for organization."""
|
||||||
|
|
||||||
|
model = Organization
|
||||||
|
template_name = "twitch/org_list.html"
|
||||||
|
context_object_name = "orgs"
|
||||||
|
|
||||||
|
|
||||||
|
class OrgDetailView(DetailView):
|
||||||
|
"""Detail view for organization."""
|
||||||
|
|
||||||
|
model = Organization
|
||||||
|
template_name = "twitch/organization_detail.html"
|
||||||
|
context_object_name = "organization"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs) -> dict[str, Any]:
|
||||||
|
"""Add additional context data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: Additional arguments.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Context data.
|
||||||
|
"""
|
||||||
|
organization: Organization = self.object
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
games = Game.objects.filter(drop_campaigns__owner=organization).distinct()
|
||||||
|
context["games"] = games
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class DropCampaignListView(ListView):
|
class DropCampaignListView(ListView):
|
||||||
"""List view for drop campaigns."""
|
"""List view for drop campaigns."""
|
||||||
|
|
||||||
|
|
@ -318,8 +350,8 @@ def dashboard(request: HttpRequest) -> HttpResponse:
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def subscribe_notifications(request: HttpRequest, game_id: str) -> HttpResponseRedirect:
|
def subscribe_game_notifications(request: HttpRequest, game_id: str) -> HttpResponseRedirect:
|
||||||
"""Update notification for a user.
|
"""Update Game notification for a user.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request: The HTTP request.
|
request: The HTTP request.
|
||||||
|
|
@ -338,22 +370,67 @@ def subscribe_notifications(request: HttpRequest, game_id: str) -> HttpResponseR
|
||||||
changes = []
|
changes = []
|
||||||
if not created:
|
if not created:
|
||||||
if subscription.notify_found != notify_found:
|
if subscription.notify_found != notify_found:
|
||||||
changes.append(f"Notify when drop is found: {'enabled' if notify_found else 'disabled'}")
|
changes.append(f"{'Enabled' if notify_found else 'Disabled'} notification when drop is found")
|
||||||
if subscription.notify_live != notify_live:
|
if subscription.notify_live != notify_live:
|
||||||
changes.append(f"Notify when drop is farmable: {'enabled' if notify_live else 'disabled'}")
|
changes.append(f"{'Enabled' if notify_live else 'Disabled'} notification when drop is farmable")
|
||||||
|
|
||||||
subscription.notify_found = notify_found
|
subscription.notify_found = notify_found
|
||||||
subscription.notify_live = notify_live
|
subscription.notify_live = notify_live
|
||||||
subscription.save()
|
subscription.save()
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
message = "You have subscribed to notifications for this game."
|
message = f"You have subscribed to notifications for {game.display_name}"
|
||||||
elif changes:
|
elif changes:
|
||||||
message = "Updated notification preferences: " + ", ".join(changes)
|
message = "\n".join(changes)
|
||||||
else:
|
else:
|
||||||
message = "No changes were made to your notification preferences."
|
message = ""
|
||||||
|
|
||||||
messages.success(request, message)
|
messages.success(request, message)
|
||||||
return redirect("twitch:game_detail", pk=game.id)
|
return redirect("twitch:game_detail", pk=game.id)
|
||||||
|
|
||||||
|
messages.warning(request, "Only POST is available for this view.")
|
||||||
return redirect("twitch:game_detail", pk=game.id)
|
return redirect("twitch:game_detail", pk=game.id)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def subscribe_org_notifications(request: HttpRequest, org_id: str) -> HttpResponseRedirect:
|
||||||
|
"""Update Organization notification for a user.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: The HTTP request.
|
||||||
|
org_id: The org we are updating.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Redirect back to the twitch:organization_detail.
|
||||||
|
"""
|
||||||
|
organization: Organization = get_object_or_404(Organization, pk=org_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, organization=organization)
|
||||||
|
|
||||||
|
changes = []
|
||||||
|
if not created:
|
||||||
|
if subscription.notify_found != notify_found:
|
||||||
|
changes.append(f"{'Enabled' if notify_found else 'Disabled'} notification when drop is found")
|
||||||
|
if subscription.notify_live != notify_live:
|
||||||
|
changes.append(f"{'Enabled' if notify_live else 'Disabled'} notification when drop is farmable")
|
||||||
|
|
||||||
|
subscription.notify_found = notify_found
|
||||||
|
subscription.notify_live = notify_live
|
||||||
|
subscription.save()
|
||||||
|
|
||||||
|
if created:
|
||||||
|
message = f"You have subscribed to notifications for this {organization.name}"
|
||||||
|
elif changes:
|
||||||
|
message = "\n".join(changes)
|
||||||
|
else:
|
||||||
|
message = ""
|
||||||
|
|
||||||
|
messages.success(request, message)
|
||||||
|
return redirect("organization_detail", org_id=organization.id)
|
||||||
|
|
||||||
|
messages.warning(request, "Only POST is available for this view.")
|
||||||
|
return redirect("organization_detail", org_id=organization.id)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue