Improve HTML on profile and game details

This commit is contained in:
Joakim Hellsén 2025-08-04 00:01:34 +02:00
commit 2ff314ecc8
10 changed files with 60 additions and 16 deletions

View file

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, ClassVar from typing import TYPE_CHECKING
from django.contrib.auth import login from django.contrib.auth import login
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -11,6 +11,7 @@ from django.views.generic import CreateView
from accounts.forms import CustomUserCreationForm from accounts.forms import CustomUserCreationForm
from accounts.models import User from accounts.models import User
from twitch.models import NotificationSubscription
if TYPE_CHECKING: if TYPE_CHECKING:
from django.forms import BaseModelForm from django.forms import BaseModelForm
@ -36,7 +37,7 @@ class CustomLogoutView(LogoutView):
"""Custom logout view.""" """Custom logout view."""
next_page = reverse_lazy("twitch:dashboard") next_page = reverse_lazy("twitch:dashboard")
http_method_names: ClassVar[list[str]] = ["get", "post", "options"] # pyright: ignore[reportIncompatibleVariableOverride] http_method_names = ["get", "post", "options"]
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Allow GET requests for logout. """Allow GET requests for logout.
@ -84,4 +85,12 @@ def profile_view(request: HttpRequest) -> HttpResponse:
Returns: Returns:
HttpResponse: Rendered profile template. HttpResponse: Rendered profile template.
""" """
return render(request, "accounts/profile.html", {"user": request.user}) subscriptions = NotificationSubscription.objects.filter(user=request.user)
return render(
request,
"accounts/profile.html",
{
"user": request.user,
"subscriptions": subscriptions,
},
)

View file

@ -37,6 +37,8 @@ lint.pydocstyle.convention = "google"
lint.isort.required-imports = ["from __future__ import annotations"] lint.isort.required-imports = ["from __future__ import annotations"]
lint.ignore = [ lint.ignore = [
"ANN002", # Checks that function *args arguments have type annotations.
"ANN003", # Checks that function **kwargs arguments have type annotations.
"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.
@ -45,8 +47,7 @@ lint.ignore = [
"ERA001", # Checks for commented-out Python code. "ERA001", # Checks for commented-out Python code.
"FIX002", # Checks for "TODO" comments. "FIX002", # Checks for "TODO" comments.
"PLR6301", # Checks for the presence of unused self parameter in methods definitions. "PLR6301", # Checks for the presence of unused self parameter in methods definitions.
"ANN002", # Checks that function *args arguments have type annotations. "RUF012", # Checks for mutable default values in class attributes.
"ANN003", # Checks that function **kwargs arguments have type annotations.
# Conflicting lint rules when using Ruff's formatter # Conflicting lint rules when using Ruff's formatter
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules

View file

@ -26,4 +26,19 @@
</tr> </tr>
</table> </table>
<a href="{% url 'accounts:logout' %}">Logout</a> <a href="{% url 'accounts:logout' %}">Logout</a>
<h2>Will get notifications to:</h2>
<ul>
{% for subscription in subscriptions %}
<li>
{% if subscription.game_id %}
<a href="{% url 'twitch:game_detail' subscription.game_id %}">{{ subscription.game.display_name }}</a>
{% endif %}
{% if subscription.organization_id %}
<a href="{% url 'twitch:organization_detail' subscription.organization_id %}">{{ subscription.organization.name }}</a>
{% endif %}
</li>
{% empty %}
<li>You have no subscriptions yet.</li>
{% endfor %}
</ul>
{% endblock content %} {% endblock content %}

View file

@ -9,7 +9,7 @@
</h1> </h1>
<p> <p>
{# TODO: Link to organization #} {# TODO: Link to organization #}
<a href="{% url 'twitch:org_detail' campaign.owner.id %}">{{ campaign.owner.name }}</a> <a href="{% url 'twitch:organization_detail' campaign.owner.id %}">{{ campaign.owner.name }}</a>
</p> </p>
{% if campaign.image_url %} {% if campaign.image_url %}
<img height="70" <img height="70"

View file

@ -4,6 +4,9 @@
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
<h1>{{ game.display_name }}</h1> <h1>{{ game.display_name }}</h1>
{% if owner %}
<small><a href="{% url 'twitch:organization_detail' owner.id %}">{{ owner.name }}</a></small>
{% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<form method="post" <form method="post"
action="{% url 'twitch:subscribe_notifications' game_id=game.id %}"> action="{% url 'twitch:subscribe_notifications' game_id=game.id %}">

View file

@ -8,7 +8,7 @@
<ul> <ul>
{% for organization in orgs %} {% for organization in orgs %}
<li> <li>
<a href="{% url 'twitch:org_detail' organization.id %}">{{ organization.name }}</a> <a href="{% url 'twitch:organization_detail' organization.id %}">{{ organization.name }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View file

@ -13,14 +13,14 @@
id="found" id="found"
name="notify_found" name="notify_found"
{% if subscription and subscription.notify_found %}checked{% endif %} /> {% if subscription and subscription.notify_found %}checked{% endif %} />
<label for="found">🔔 Notify me when a drop for {{ organization.name }} appears on Twitch.</label> <label for="found">🔔 Get notified as soon as a drop for {{ organization.name }} appears on Twitch.</label>
</div> </div>
<div> <div>
<input type="checkbox" <input type="checkbox"
id="live" id="live"
name="notify_live" name="notify_live"
{% if subscription and subscription.notify_live %}checked{% endif %} /> {% if subscription and subscription.notify_live %}checked{% endif %} />
<label for="live">🎮 Notify me 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 preferences</button> <button type="submit">Save preferences</button>
</form> </form>

View file

@ -216,4 +216,8 @@ class NotificationSubscription(models.Model):
] ]
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.user} subscription to {Game.display_name}" if self.game:
return f"{self.user} subscription to game: {self.game.display_name}"
if self.organization:
return f"{self.user} subscription to organization: {self.organization.name}"
return f"{self.user} subscription"

View file

@ -14,6 +14,6 @@ urlpatterns = [
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_game_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/", views.OrgListView.as_view(), name="org_list"),
path("organizations/<str:pk>/", views.OrgDetailView.as_view(), name="org_detail"), path("organizations/<str:pk>/", views.OrgDetailView.as_view(), name="organization_detail"),
path("organizations/<str:org_id>/subscribe/", views.subscribe_org_notifications, name="subscribe_org_notifications"), path("organizations/<str:org_id>/subscribe/", views.subscribe_org_notifications, name="subscribe_org_notifications"),
] ]

View file

@ -49,10 +49,21 @@ class OrgDetailView(DetailView):
Returns: Returns:
dict: Context data. dict: Context data.
""" """
organization: Organization = self.object
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
games = Game.objects.filter(drop_campaigns__owner=organization).distinct() organization: Organization = self.object
context["games"] = games
user = self.request.user
if not user.is_authenticated:
subscription: NotificationSubscription | None = None
else:
subscription = NotificationSubscription.objects.filter(user=user, organization=organization).first()
games: QuerySet[Game, Game] = Game.objects.filter(drop_campaigns__owner=organization).distinct()
context.update({
"subscription": subscription,
"games": games,
})
return context return context
@ -290,6 +301,7 @@ class GameDetailView(DetailView):
"upcoming_campaigns": upcoming_campaigns, "upcoming_campaigns": upcoming_campaigns,
"expired_campaigns": expired_campaigns, "expired_campaigns": expired_campaigns,
"subscription": subscription, "subscription": subscription,
"owner": active_campaigns[0].owner if active_campaigns else None,
"now": now, "now": now,
}) })
@ -430,7 +442,7 @@ def subscribe_org_notifications(request: HttpRequest, org_id: str) -> HttpRespon
message = "" message = ""
messages.success(request, message) messages.success(request, message)
return redirect("organization_detail", org_id=organization.id) return redirect("twitch:organization_detail", pk=organization.id)
messages.warning(request, "Only POST is available for this view.") messages.warning(request, "Only POST is available for this view.")
return redirect("organization_detail", org_id=organization.id) return redirect("twitch:organization_detail", pk=organization.id)