Jacks & Cavaliers replaced in Earthman deck w. Maids & Jacks; numerals or numbers + symbols added to cards; migrations made in apps.epic to rename cards; _tarot_fan.html partial updated accordingly
This commit is contained in:
82
src/apps/epic/migrations/0011_rename_earthman_court_cards.py
Normal file
82
src/apps/epic/migrations/0011_rename_earthman_court_cards.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
"""
|
||||||
|
Data migration: rename Earthman court cards at positions 11 and 12.
|
||||||
|
|
||||||
|
Old naming (from 0010): Jack (11) / Cavalier (12)
|
||||||
|
New naming: Maid (11) / Jack (12)
|
||||||
|
|
||||||
|
Must rename 11 → Maid first so the "jack-of-*-em" slugs are free
|
||||||
|
before the 12s claim them.
|
||||||
|
"""
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
SUITS = ["Wands", "Cups", "Swords", "Coins"]
|
||||||
|
|
||||||
|
|
||||||
|
def rename_court_cards(apps, schema_editor):
|
||||||
|
TarotCard = apps.get_model("epic", "TarotCard")
|
||||||
|
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||||
|
|
||||||
|
earthman = DeckVariant.objects.filter(slug="earthman").first()
|
||||||
|
if not earthman:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Step 1: Jack (11) → Maid — frees up jack-of-*-em slugs
|
||||||
|
for suit in SUITS:
|
||||||
|
suit_slug = suit.lower()
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, number=11, slug=f"jack-of-{suit_slug}-em"
|
||||||
|
).update(
|
||||||
|
name=f"Maid of {suit}",
|
||||||
|
slug=f"maid-of-{suit_slug}-em",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 2: Cavalier (12) → Jack — takes the now-free jack-of-*-em slugs
|
||||||
|
for suit in SUITS:
|
||||||
|
suit_slug = suit.lower()
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, number=12, slug=f"cavalier-of-{suit_slug}-em"
|
||||||
|
).update(
|
||||||
|
name=f"Jack of {suit}",
|
||||||
|
slug=f"jack-of-{suit_slug}-em",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_court_cards(apps, schema_editor):
|
||||||
|
TarotCard = apps.get_model("epic", "TarotCard")
|
||||||
|
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||||
|
|
||||||
|
earthman = DeckVariant.objects.filter(slug="earthman").first()
|
||||||
|
if not earthman:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Step 1: Jack (12) → Cavalier — frees up jack-of-*-em slugs
|
||||||
|
for suit in SUITS:
|
||||||
|
suit_slug = suit.lower()
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, number=12, slug=f"jack-of-{suit_slug}-em"
|
||||||
|
).update(
|
||||||
|
name=f"Cavalier of {suit}",
|
||||||
|
slug=f"cavalier-of-{suit_slug}-em",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 2: Maid (11) → Jack
|
||||||
|
for suit in SUITS:
|
||||||
|
suit_slug = suit.lower()
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, number=11, slug=f"maid-of-{suit_slug}-em"
|
||||||
|
).update(
|
||||||
|
name=f"Jack of {suit}",
|
||||||
|
slug=f"jack-of-{suit_slug}-em",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("epic", "0010_seed_deck_variants_and_earthman"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(rename_court_cards, reverse_code=reverse_court_cards),
|
||||||
|
]
|
||||||
@@ -233,6 +233,38 @@ class TarotCard(models.Model):
|
|||||||
ordering = ["deck_variant", "arcana", "suit", "number"]
|
ordering = ["deck_variant", "arcana", "suit", "number"]
|
||||||
unique_together = [("deck_variant", "slug")]
|
unique_together = [("deck_variant", "slug")]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _to_roman(n):
|
||||||
|
if n == 0:
|
||||||
|
return '0'
|
||||||
|
val = [50, 40, 10, 9, 5, 4, 1]
|
||||||
|
syms = ['L','XL','X','IX','V','IV','I']
|
||||||
|
result = ''
|
||||||
|
for v, s in zip(val, syms):
|
||||||
|
while n >= v:
|
||||||
|
result += s
|
||||||
|
n -= v
|
||||||
|
return result
|
||||||
|
|
||||||
|
@property
|
||||||
|
def corner_rank(self):
|
||||||
|
if self.arcana == self.MAJOR:
|
||||||
|
return self._to_roman(self.number)
|
||||||
|
court = {11: 'M', 12: 'J', 13: 'Q', 14: 'K'}
|
||||||
|
return court.get(self.number, str(self.number))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def suit_icon(self):
|
||||||
|
if self.arcana == self.MAJOR:
|
||||||
|
return ''
|
||||||
|
return {
|
||||||
|
self.WANDS: 'fa-wand-sparkles',
|
||||||
|
self.CUPS: 'fa-trophy',
|
||||||
|
self.SWORDS: 'fa-gun',
|
||||||
|
self.COINS: 'fa-sack-dollar',
|
||||||
|
self.PENTACLES: 'fa-sack-dollar',
|
||||||
|
}.get(self.suit, '')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,11 @@ def tarot_fan(request, deck_id):
|
|||||||
deck = get_object_or_404(DeckVariant, pk=deck_id)
|
deck = get_object_or_404(DeckVariant, pk=deck_id)
|
||||||
if not request.user.unlocked_decks.filter(pk=deck_id).exists():
|
if not request.user.unlocked_decks.filter(pk=deck_id).exists():
|
||||||
return HttpResponse(status=403)
|
return HttpResponse(status=403)
|
||||||
cards = list(TarotCard.objects.filter(deck_variant=deck).order_by("arcana", "number"))
|
_suit_order = {"WANDS": 0, "CUPS": 1, "SWORDS": 2, "COINS": 3, "PENTACLES": 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", {
|
return render(request, "apps/gameboard/_partials/_tarot_fan.html", {
|
||||||
"deck": deck,
|
"deck": deck,
|
||||||
"cards": cards,
|
"cards": cards,
|
||||||
|
|||||||
@@ -254,6 +254,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fan-card-corner {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.15rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: rgba(var(--secUser), 0.75);
|
||||||
|
|
||||||
|
&--tl { top: 0.4rem; left: 0.4rem; }
|
||||||
|
&--br { bottom: 0.4rem; right: 0.4rem; transform: rotate(180deg); }
|
||||||
|
|
||||||
|
.fan-corner-rank {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.18rem 0;
|
||||||
|
}
|
||||||
|
i { font-size: 1.5rem; }
|
||||||
|
}
|
||||||
|
|
||||||
.fan-card-face {
|
.fan-card-face {
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
{% for card in cards %}
|
{% for card in cards %}
|
||||||
<div class="fan-card" data-index="{{ forloop.counter0 }}">
|
<div class="fan-card" data-index="{{ forloop.counter0 }}">
|
||||||
|
<div class="fan-card-corner fan-card-corner--tl">
|
||||||
|
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||||
|
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
||||||
|
</div>
|
||||||
<div class="fan-card-face">
|
<div class="fan-card-face">
|
||||||
<p class="fan-card-number">{{ card.number }}</p>
|
|
||||||
<h3 class="fan-card-name">{{ card.name }}</h3>
|
<h3 class="fan-card-name">{{ card.name }}</h3>
|
||||||
<p class="fan-card-arcana">{{ card.get_arcana_display }}</p>
|
<p class="fan-card-arcana">{{ card.get_arcana_display }}</p>
|
||||||
{% if card.correspondence %}
|
{% if card.correspondence %}
|
||||||
<p class="fan-card-correspondence">{{ card.correspondence }}</p>
|
<p class="fan-card-correspondence">{{ card.correspondence }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if card.suit %}
|
</div>
|
||||||
<p class="fan-card-suit">{{ card.suit }}</p>
|
<div class="fan-card-corner fan-card-corner--br">
|
||||||
{% endif %}
|
<span class="fan-corner-rank">{{ card.corner_rank }}</span>
|
||||||
|
{% if card.suit_icon %}<i class="fa-solid {{ card.suit_icon }}"></i>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Reference in New Issue
Block a user