Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
game kit: new Applet model rows (context=game-kit) for Trinkets, Tokens, Card Decks, Dice Sets via applets migration 0008; _game_kit_context() helper in gameboard.views; toggle_game_kit_sections view + URL; new _game_kit_sections.html (HTMX-swappable, visibility-conditional) + _game_kit_applet_menu.html partials; game_kit.html wired to gear btn + menu; Dice Sets now renders _forthcoming.html partial; 16 new green ITs in GameKitViewTest + ToggleGameKitSectionsViewTest login form: .input-group now position:fixed + vertically centred (top:50%) across all breakpoints as default; landscape block reduced to left/right sidebar offsets only; form-control width 24rem, text-align:center; alert block moved below h2 in base.html; alert margin 0.75rem all sides; home.html header switches between Howdy Stranger (anon) and Dashboard (authed) room.html position indicators: slots 3/4/5 (AC/SC/EC) column order flipped via SCSS data-slot selectors so .fa-chair sits table-side and label+status icon sit outward Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
162 lines
6.2 KiB
Python
162 lines
6.2 KiB
Python
from django.contrib.auth.decorators import login_required
|
|
from django.db.models import Q
|
|
from django.http import HttpResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.utils import timezone
|
|
|
|
from apps.applets.utils import applet_context
|
|
from apps.applets.models import Applet, UserApplet
|
|
from apps.epic.models import DeckVariant, Room, RoomInvite
|
|
from apps.lyric.models import Token
|
|
|
|
|
|
GAMEBOARD_APPLET_ORDER = [
|
|
"new-game",
|
|
"my-games",
|
|
"game-kit",
|
|
]
|
|
|
|
|
|
@login_required(login_url="/")
|
|
def gameboard(request):
|
|
pass_token = request.user.tokens.filter(token_type=Token.PASS).first() if request.user.is_staff else None
|
|
coin = request.user.tokens.filter(token_type=Token.COIN).first()
|
|
carte = request.user.tokens.filter(token_type=Token.CARTE).first()
|
|
free_tokens = list(request.user.tokens.filter(
|
|
token_type=Token.FREE, expires_at__gt=timezone.now()
|
|
).order_by("expires_at"))
|
|
return render(
|
|
request, "apps/gameboard/gameboard.html", {
|
|
"pass_token": pass_token,
|
|
"coin": coin,
|
|
"carte": carte,
|
|
"equipped_trinket_id": str(request.user.equipped_trinket_id or ""),
|
|
"equipped_deck_id": str(request.user.equipped_deck_id or ""),
|
|
"deck_variants": list(request.user.unlocked_decks.all()),
|
|
"free_tokens": free_tokens,
|
|
"free_count": len(free_tokens),
|
|
"applets": applet_context(request.user, "gameboard"),
|
|
"page_class": "page-gameboard",
|
|
"my_games": Room.objects.filter(
|
|
Q(owner=request.user) |
|
|
Q(gate_slots__gamer=request.user) |
|
|
Q(invites__invitee_email=request.user.email, invites__status=RoomInvite.PENDING)
|
|
).distinct(),
|
|
}
|
|
)
|
|
|
|
@login_required(login_url="/")
|
|
def toggle_game_applets(request):
|
|
checked = request.POST.getlist("applets")
|
|
for applet in Applet.objects.filter(context="gameboard"):
|
|
UserApplet.objects.update_or_create(
|
|
user=request.user,
|
|
applet=applet,
|
|
defaults={"visible": applet.slug in checked},
|
|
)
|
|
if request.headers.get("HX-Request"):
|
|
return render(request, "apps/gameboard/_partials/_applets.html", {
|
|
"applets": applet_context(request.user, "gameboard"),
|
|
"pass_token": request.user.tokens.filter(token_type=Token.PASS).first() if request.user.is_staff else None,
|
|
"coin": request.user.tokens.filter(token_type=Token.COIN).first(),
|
|
"carte": request.user.tokens.filter(token_type=Token.CARTE).first(),
|
|
"equipped_trinket_id": str(request.user.equipped_trinket_id or ""),
|
|
"equipped_deck_id": str(request.user.equipped_deck_id or ""),
|
|
"deck_variants": list(request.user.unlocked_decks.all()),
|
|
"free_tokens": list(request.user.tokens.filter(
|
|
token_type=Token.FREE, expires_at__gt=timezone.now()
|
|
).order_by("expires_at")),
|
|
"free_count": request.user.tokens.filter(
|
|
token_type=Token.FREE, expires_at__gt=timezone.now()
|
|
).count(),
|
|
"my_games": Room.objects.filter(
|
|
Q(owner=request.user) |
|
|
Q(gate_slots__gamer=request.user) |
|
|
Q(invites__invitee_email=request.user.email, invites__status=RoomInvite.PENDING)
|
|
).distinct(),
|
|
})
|
|
return redirect("gameboard")
|
|
|
|
|
|
@login_required(login_url="/")
|
|
def equip_trinket(request, token_id):
|
|
token = get_object_or_404(Token, pk=token_id, user=request.user)
|
|
if request.method == "POST":
|
|
request.user.equipped_trinket = token
|
|
request.user.save(update_fields=["equipped_trinket"])
|
|
return HttpResponse(status=204)
|
|
return render(
|
|
request,
|
|
"apps/gameboard/_partials/_equip_trinket_btn.html",
|
|
{"token": token},
|
|
)
|
|
|
|
|
|
@login_required(login_url="/")
|
|
def equip_deck(request, deck_id):
|
|
deck = get_object_or_404(DeckVariant, pk=deck_id)
|
|
if request.method == "POST":
|
|
request.user.equipped_deck = deck
|
|
request.user.save(update_fields=["equipped_deck"])
|
|
return HttpResponse(status=204)
|
|
return HttpResponse(status=405)
|
|
|
|
|
|
def _game_kit_context(user):
|
|
coin = user.tokens.filter(token_type=Token.COIN).first()
|
|
pass_token = user.tokens.filter(token_type=Token.PASS).first() if user.is_staff else None
|
|
carte = user.tokens.filter(token_type=Token.CARTE).first()
|
|
free_tokens = list(user.tokens.filter(
|
|
token_type=Token.FREE, expires_at__gt=timezone.now()
|
|
).order_by("expires_at"))
|
|
tithe_tokens = list(user.tokens.filter(token_type=Token.TITHE))
|
|
return {
|
|
"coin": coin,
|
|
"pass_token": pass_token,
|
|
"carte": carte,
|
|
"free_tokens": free_tokens,
|
|
"tithe_tokens": tithe_tokens,
|
|
"unlocked_decks": list(user.unlocked_decks.all()),
|
|
"applets": applet_context(user, "game-kit"),
|
|
}
|
|
|
|
|
|
@login_required(login_url="/")
|
|
def game_kit(request):
|
|
return render(request, "apps/gameboard/game_kit.html", {
|
|
**_game_kit_context(request.user),
|
|
"page_class": "page-gameboard",
|
|
})
|
|
|
|
|
|
@login_required(login_url="/")
|
|
def toggle_game_kit_sections(request):
|
|
checked = request.POST.getlist("applets")
|
|
for applet in Applet.objects.filter(context="game-kit"):
|
|
UserApplet.objects.update_or_create(
|
|
user=request.user,
|
|
applet=applet,
|
|
defaults={"visible": applet.slug in checked},
|
|
)
|
|
if request.headers.get("HX-Request"):
|
|
return render(request, "apps/gameboard/_partials/_game_kit_sections.html",
|
|
_game_kit_context(request.user))
|
|
return redirect("game_kit")
|
|
|
|
|
|
@login_required(login_url="/")
|
|
def tarot_fan(request, deck_id):
|
|
from apps.epic.models import TarotCard
|
|
deck = get_object_or_404(DeckVariant, pk=deck_id)
|
|
if not request.user.unlocked_decks.filter(pk=deck_id).exists():
|
|
return HttpResponse(status=403)
|
|
_suit_order = {"WANDS": 0, "CUPS": 1, "SWORDS": 2, "PENTACLES": 3, "COINS": 4}
|
|
cards = sorted(
|
|
TarotCard.objects.filter(deck_variant=deck),
|
|
key=lambda c: (0 if c.arcana == "MAJOR" else 1, _suit_order.get(c.suit or "", 9), c.number),
|
|
)
|
|
return render(request, "apps/gameboard/_partials/_tarot_fan.html", {
|
|
"deck": deck,
|
|
"cards": cards,
|
|
})
|