sig-select sprint: SigReservation model + sig_reserve view (OK/NVM hold); full sig-select.js rewrite with stage preview, WS hover cursors, reservation lock (must NVM before OK-ing another card — enforced server-side 409 + JS guard); sizeSigModal() + sizeSigCard() in room.js (JS-based card sizing avoids libsass cqw/cqh limitation); stat block hidden until OK pressed; mobile touch: dismiss stage on outside-grid tap when unfocused; 17 IT + Jasmine specs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import uuid
|
||||
|
||||
from datetime import timedelta
|
||||
from django.db import models
|
||||
from django.db.models import UniqueConstraint
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.conf import settings
|
||||
@@ -324,6 +325,37 @@ class TarotDeck(models.Model):
|
||||
self.save(update_fields=["drawn_card_ids"])
|
||||
|
||||
|
||||
# ── SigReservation — provisional card hold during SIG_SELECT ──────────────────
|
||||
|
||||
class SigReservation(models.Model):
|
||||
LEVITY = 'levity'
|
||||
GRAVITY = 'gravity'
|
||||
POLARITY_CHOICES = [(LEVITY, 'Levity'), (GRAVITY, 'Gravity')]
|
||||
|
||||
room = models.ForeignKey(Room, on_delete=models.CASCADE, related_name='sig_reservations')
|
||||
gamer = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='sig_reservations'
|
||||
)
|
||||
card = models.ForeignKey(
|
||||
'TarotCard', on_delete=models.CASCADE, related_name='sig_reservations'
|
||||
)
|
||||
role = models.CharField(max_length=2)
|
||||
polarity = models.CharField(max_length=7, choices=POLARITY_CHOICES)
|
||||
reserved_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
UniqueConstraint(
|
||||
fields=['room', 'gamer'],
|
||||
name='one_sig_reservation_per_gamer_per_room',
|
||||
),
|
||||
UniqueConstraint(
|
||||
fields=['room', 'card', 'polarity'],
|
||||
name='one_reservation_per_card_per_polarity_per_room',
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# ── Significator deck helpers ─────────────────────────────────────────────────
|
||||
|
||||
def sig_deck_cards(room):
|
||||
@@ -358,6 +390,41 @@ def sig_deck_cards(room):
|
||||
return unique_cards + unique_cards # × 2 = 36
|
||||
|
||||
|
||||
def _sig_unique_cards(room):
|
||||
"""Return the 18 unique TarotCard objects that form one sig pile."""
|
||||
deck_variant = room.owner.equipped_deck
|
||||
if deck_variant is None:
|
||||
return []
|
||||
wands_pentacles = list(TarotCard.objects.filter(
|
||||
deck_variant=deck_variant,
|
||||
arcana=TarotCard.MINOR,
|
||||
suit__in=[TarotCard.WANDS, TarotCard.PENTACLES],
|
||||
number__in=[11, 12, 13, 14],
|
||||
))
|
||||
swords_cups = list(TarotCard.objects.filter(
|
||||
deck_variant=deck_variant,
|
||||
arcana=TarotCard.MINOR,
|
||||
suit__in=[TarotCard.SWORDS, TarotCard.CUPS],
|
||||
number__in=[11, 12, 13, 14],
|
||||
))
|
||||
major = list(TarotCard.objects.filter(
|
||||
deck_variant=deck_variant,
|
||||
arcana=TarotCard.MAJOR,
|
||||
number__in=[0, 1],
|
||||
))
|
||||
return wands_pentacles + swords_cups + major
|
||||
|
||||
|
||||
def levity_sig_cards(room):
|
||||
"""The 18 cards available to the levity group (PC/NC/SC)."""
|
||||
return _sig_unique_cards(room)
|
||||
|
||||
|
||||
def gravity_sig_cards(room):
|
||||
"""The 18 cards available to the gravity group (BC/EC/AC)."""
|
||||
return _sig_unique_cards(room)
|
||||
|
||||
|
||||
def sig_seat_order(room):
|
||||
"""Return TableSeats in canonical PC→NC→EC→SC→AC→BC order."""
|
||||
_order = {r: i for i, r in enumerate(SIG_SEAT_ORDER)}
|
||||
|
||||
Reference in New Issue
Block a user