added default Earthman 108-card tarot deck, 78-card Minchiate Fiorentine deck, admin tests for each; DeckVariant model governs deck toggle; ran new migrations for apps.epic, apps.lyric; seeded DeckVariant migration to ensure Earthman is default deck; added min. tarot url; most new FTs passing
This commit is contained in:
39
src/apps/epic/migrations/0007_tarotcard_tarotdeck.py
Normal file
39
src/apps/epic/migrations/0007_tarotcard_tarotdeck.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 6.0 on 2026-03-24 23:33
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('epic', '0006_table_status_and_table_seat'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TarotCard',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('arcana', models.CharField(choices=[('MAJOR', 'Major Arcana'), ('MINOR', 'Minor Arcana')], max_length=5)),
|
||||
('suit', models.CharField(blank=True, choices=[('WANDS', 'Wands'), ('CUPS', 'Cups'), ('SWORDS', 'Swords'), ('PENTACLES', 'Pentacles')], max_length=10, null=True)),
|
||||
('number', models.IntegerField()),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('keywords_upright', models.JSONField(default=list)),
|
||||
('keywords_reversed', models.JSONField(default=list)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['arcana', 'suit', 'number'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TarotDeck',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('drawn_card_ids', models.JSONField(default=list)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('room', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='tarot_deck', to='epic.room')),
|
||||
],
|
||||
),
|
||||
]
|
||||
164
src/apps/epic/migrations/0008_seed_tarot_cards.py
Normal file
164
src/apps/epic/migrations/0008_seed_tarot_cards.py
Normal file
@@ -0,0 +1,164 @@
|
||||
from django.db import migrations
|
||||
|
||||
MAJOR_ARCANA = [
|
||||
(0, "The Fool", "the-fool", ["beginnings", "spontaneity", "freedom"], ["recklessness", "naivety", "risk"]),
|
||||
(1, "The Magician", "the-magician", ["willpower", "skill", "resourcefulness"], ["manipulation", "untapped potential", "deceit"]),
|
||||
(2, "The High Priestess", "the-high-priestess", ["intuition", "mystery", "inner knowledge"], ["secrets", "disconnection", "withdrawal"]),
|
||||
(3, "The Empress", "the-empress", ["fertility", "abundance", "nurturing"], ["dependence", "smothering", "creative block"]),
|
||||
(4, "The Emperor", "the-emperor", ["authority", "structure", "stability"], ["rigidity", "domination", "inflexibility"]),
|
||||
(5, "The Hierophant", "the-hierophant", ["tradition", "conformity", "institutions"], ["rebellion", "unconventionality", "challenge"]),
|
||||
(6, "The Lovers", "the-lovers", ["love", "harmony", "choice"], ["disharmony", "imbalance", "misalignment"]),
|
||||
(7, "The Chariot", "the-chariot", ["control", "willpower", "victory"], ["aggression", "lack of direction", "defeat"]),
|
||||
(8, "Strength", "strength", ["courage", "patience", "compassion"], ["self-doubt", "weakness", "insecurity"]),
|
||||
(9, "The Hermit", "the-hermit", ["introspection", "guidance", "solitude"], ["isolation", "loneliness", "withdrawal"]),
|
||||
(10, "Wheel of Fortune", "wheel-of-fortune", ["change", "cycles", "fate"], ["bad luck", "resistance", "clinging to control"]),
|
||||
(11, "Justice", "justice", ["fairness", "truth", "cause and effect"], ["injustice", "dishonesty", "avoidance"]),
|
||||
(12, "The Hanged Man", "the-hanged-man", ["pause", "surrender", "new perspective"], ["stalling", "resistance", "indecision"]),
|
||||
(13, "Death", "death", ["endings", "transition", "transformation"], ["fear of change", "stagnation", "resistance"]),
|
||||
(14, "Temperance", "temperance", ["balance", "patience", "moderation"], ["imbalance", "excess", "lack of harmony"]),
|
||||
(15, "The Devil", "the-devil", ["bondage", "materialism", "shadow self"], ["detachment", "freedom", "releasing control"]),
|
||||
(16, "The Tower", "the-tower", ["sudden change", "upheaval", "revelation"], ["avoidance", "fear of change", "delaying disaster"]),
|
||||
(17, "The Star", "the-star", ["hope", "renewal", "inspiration"], ["despair", "insecurity", "hopelessness"]),
|
||||
(18, "The Moon", "the-moon", ["illusion", "fear", "the unconscious"], ["confusion", "misinterpretation", "clarity"]),
|
||||
(19, "The Sun", "the-sun", ["positivity", "success", "vitality"], ["negativity", "depression", "sadness"]),
|
||||
(20, "Judgement", "judgement", ["reflection", "reckoning", "absolution"], ["self-doubt", "lack of self-awareness", "loathing"]),
|
||||
(21, "The World", "the-world", ["completion", "integration", "accomplishment"], ["incompletion", "no closure", "shortcuts"]),
|
||||
]
|
||||
|
||||
MINOR_SUITS = [
|
||||
("WANDS", "wands"),
|
||||
("CUPS", "cups"),
|
||||
("SWORDS", "swords"),
|
||||
("PENTACLES", "pentacles"),
|
||||
]
|
||||
|
||||
MINOR_NAMES = [
|
||||
(1, "Ace", "ace"),
|
||||
(2, "Two", "two"),
|
||||
(3, "Three", "three"),
|
||||
(4, "Four", "four"),
|
||||
(5, "Five", "five"),
|
||||
(6, "Six", "six"),
|
||||
(7, "Seven", "seven"),
|
||||
(8, "Eight", "eight"),
|
||||
(9, "Nine", "nine"),
|
||||
(10, "Ten", "ten"),
|
||||
(11, "Page", "page"),
|
||||
(12, "Knight", "knight"),
|
||||
(13, "Queen", "queen"),
|
||||
(14, "King", "king"),
|
||||
]
|
||||
|
||||
# Keywords: [suit][number-1] → (upright_list, reversed_list)
|
||||
MINOR_KEYWORDS = {
|
||||
"WANDS": [
|
||||
(["inspiration", "new venture", "spark"], ["delays", "lack of motivation", "false start"]),
|
||||
(["planning", "progress", "decisions"], ["impatience", "lack of planning", "hesitation"]),
|
||||
(["expansion", "foresight", "enterprise"], ["obstacles", "lack of foresight", "delays"]),
|
||||
(["celebration", "harmony", "homecoming"], ["lack of support", "transience", "home conflicts"]),
|
||||
(["conflict", "competition", "tension"], ["avoiding conflict", "compromise", "truce"]),
|
||||
(["victory", "recognition", "progress"], ["excess pride", "lack of recognition", "fall"]),
|
||||
(["challenge", "courage", "competition"], ["anxiety", "giving up", "overwhelmed"]),
|
||||
(["rapid action", "adventure", "change"], ["haste", "scattered energy", "delays"]),
|
||||
(["resilience", "persistence", "last stand"], ["exhaustion", "giving up", "surrender"]),
|
||||
(["completion", "celebration", "travel"], ["burdens", "oppression", "carrying too much"]),
|
||||
(["exploration", "enthusiasm", "adventure"], ["hasty decisions", "scattered energy", "immaturity"]),
|
||||
(["energy", "passion", "adventure"], ["scattered energy", "frustration", "aggression"]),
|
||||
(["confidence", "independence", "courage"], ["selfishness", "jealousy", "insecurity"]),
|
||||
(["big picture", "leadership", "vision"], ["impulsiveness", "haste", "overconfidence"]),
|
||||
],
|
||||
"CUPS": [
|
||||
(["new feelings", "intuition", "opportunity"], ["blocked creativity", "emptiness", "hesitation"]),
|
||||
(["partnership", "unity", "celebration"], ["imbalance", "broken bonds", "misalignment"]),
|
||||
(["creativity", "community", "abundance"], ["independence", "isolation", "looking inward"]),
|
||||
(["contemplation", "apathy", "reevaluation"], ["withdrawal", "boredom", "seeking motivation"]),
|
||||
(["loss", "grief", "disappointment"], ["acceptance", "moving on", "forgiveness"]),
|
||||
(["nostalgia", "reunion", "joy"], ["living in the past", "naivety", "unrealistic"]),
|
||||
(["illusion", "fantasy", "wishful thinking"], ["alignment", "clarity", "sobriety"]),
|
||||
(["disappointment", "abandonment", "walking away"], ["hopelessness", "aimlessness", "stagnation"]),
|
||||
(["contentment", "fulfilment", "satisfaction"], ["inner happiness", "materialism", "indulgence"]),
|
||||
(["divine love", "bliss", "fulfilment"], ["inner happiness", "alignment", "personal values"]),
|
||||
(["sensitivity", "creativity", "intuition"], ["insecurity", "emotional immaturity", "creative blocks"]),
|
||||
(["compassion", "romanticism", "diplomacy"], ["moodiness", "emotional manipulation", "deception"]),
|
||||
(["compassion", "empathy", "nurturing"], ["emotional insecurity", "over-giving", "neglect"]),
|
||||
(["emotional maturity", "diplomacy", "wisdom"], ["manipulation", "moodiness", "coldness"]),
|
||||
],
|
||||
"SWORDS": [
|
||||
(["raw power", "breakthrough", "clarity"], ["confusion", "brutality", "mental chaos"]),
|
||||
(["difficult choices", "stalemate", "truce"], ["indecision", "lies", "confusion"]),
|
||||
(["heartbreak", "sorrow", "grief"], ["recovery", "forgiveness", "moving on"]),
|
||||
(["rest", "restoration", "retreat"], ["restlessness", "burnout", "illness"]),
|
||||
(["defeat", "change", "transition"], ["resistance to change", "inability to move"]),
|
||||
(["victory", "success", "ambition"], ["an eye for an eye", "dishonour", "manipulation"]),
|
||||
(["deception", "trickery", "tactics"], ["imposter syndrome", "coming clean", "rethinking"]),
|
||||
(["restriction", "isolation", "imprisonment"], ["self-limiting beliefs", "inner critic", "opening up"]),
|
||||
(["anxiety", "worry", "fear"], ["recovery from anxiety", "inner turmoil", "secrets"]),
|
||||
(["ruin", "painful endings", "loss"], ["recovery", "regeneration", "resisting an end"]),
|
||||
(["new ideas", "mental agility", "curiosity"], ["manipulation", "all talk no action", "ruthlessness"]),
|
||||
(["action", "impulsiveness", "ambition"], ["no direction", "disregard for consequences"]),
|
||||
(["clarity", "directness", "structure"], ["coldness", "cruelty", "manipulation"]),
|
||||
(["mental clarity", "truth", "authority"], ["abuse of power", "manipulation", "coldness"]),
|
||||
],
|
||||
"PENTACLES": [
|
||||
(["opportunity", "new venture", "manifestation"], ["lost opportunity", "lack of planning", "scarcity"]),
|
||||
(["juggling resources", "flexibility", "fun"], ["imbalance", "disorganisation", "overwhelm"]),
|
||||
(["teamwork", "building", "apprenticeship"], ["lack of teamwork", "disharmony", "misalignment"]),
|
||||
(["stability", "security", "conservation"], ["greed", "stinginess", "possessiveness"]),
|
||||
(["isolation", "insecurity", "worry"], ["recovery from loss", "overcoming hardship"]),
|
||||
(["generosity", "charity", "community"], ["strings attached", "power dynamics", "inequality"]),
|
||||
(["hard work", "perseverance", "diligence"], ["lack of reward", "laziness", "low quality"]),
|
||||
(["apprenticeship", "education", "skill"], ["perfectionism", "misdirected activity", "misuse"]),
|
||||
(["abundance", "luxury", "self-sufficiency"], ["overindulgence", "superficiality", "materialism"]),
|
||||
(["wealth", "financial security", "achievement"], ["financial failure", "greed", "lost success"]),
|
||||
(["ambition", "diligence", "management"], ["underhandedness", "greediness", "unethical"]),
|
||||
(["hard work", "productivity", "routine"], ["laziness", "obsession with work", "burnout"]),
|
||||
(["nurturing", "practical", "abundance"], ["financial dependence", "smothering", "insecurity"]),
|
||||
(["abundance", "prosperity", "security"], ["greed", "indulgence", "sensual obsession"]),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def seed_tarot_cards(apps, schema_editor):
|
||||
TarotCard = apps.get_model("epic", "TarotCard")
|
||||
|
||||
# Major Arcana
|
||||
for number, name, slug, upright, reversed_ in MAJOR_ARCANA:
|
||||
TarotCard.objects.create(
|
||||
name=name,
|
||||
arcana="MAJOR",
|
||||
suit=None,
|
||||
number=number,
|
||||
slug=slug,
|
||||
keywords_upright=upright,
|
||||
keywords_reversed=reversed_,
|
||||
)
|
||||
|
||||
# Minor Arcana
|
||||
for suit_code, suit_slug in MINOR_SUITS:
|
||||
for number, rank_name, rank_slug in MINOR_NAMES:
|
||||
upright, reversed_ = MINOR_KEYWORDS[suit_code][number - 1]
|
||||
TarotCard.objects.create(
|
||||
name=f"{rank_name} of {suit_code.capitalize()}",
|
||||
arcana="MINOR",
|
||||
suit=suit_code,
|
||||
number=number,
|
||||
slug=f"{rank_slug}-of-{suit_slug}",
|
||||
keywords_upright=upright,
|
||||
keywords_reversed=reversed_,
|
||||
)
|
||||
|
||||
|
||||
def unseed_tarot_cards(apps, schema_editor):
|
||||
TarotCard = apps.get_model("epic", "TarotCard")
|
||||
TarotCard.objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("epic", "0007_tarotcard_tarotdeck"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(seed_tarot_cards, reverse_code=unseed_tarot_cards),
|
||||
]
|
||||
@@ -0,0 +1,68 @@
|
||||
# Generated by Django 6.0 on 2026-03-25 00:48
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('epic', '0008_seed_tarot_cards'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DeckVariant',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('card_count', models.IntegerField()),
|
||||
('description', models.TextField(blank=True)),
|
||||
('is_default', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='tarotcard',
|
||||
options={'ordering': ['deck_variant', 'arcana', 'suit', 'number']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tarotcard',
|
||||
name='correspondence',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tarotcard',
|
||||
name='group',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tarotcard',
|
||||
name='name',
|
||||
field=models.CharField(max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tarotcard',
|
||||
name='slug',
|
||||
field=models.SlugField(max_length=120),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tarotcard',
|
||||
name='suit',
|
||||
field=models.CharField(blank=True, choices=[('WANDS', 'Wands'), ('CUPS', 'Cups'), ('SWORDS', 'Swords'), ('PENTACLES', 'Pentacles'), ('COINS', 'Coins')], max_length=10, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tarotcard',
|
||||
name='deck_variant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cards', to='epic.deckvariant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tarotdeck',
|
||||
name='deck_variant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='active_decks', to='epic.deckvariant'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='tarotcard',
|
||||
unique_together={('deck_variant', 'slug')},
|
||||
),
|
||||
]
|
||||
202
src/apps/epic/migrations/0010_seed_deck_variants_and_earthman.py
Normal file
202
src/apps/epic/migrations/0010_seed_deck_variants_and_earthman.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""
|
||||
Data migration:
|
||||
1. Create DeckVariant records (Fiorentine Minchiate + Earthman).
|
||||
2. Backfill the 78 existing TarotCards → Fiorentine Minchiate.
|
||||
3. Seed all 108 Earthman cards (52 major + 56 minor).
|
||||
"""
|
||||
from django.db import migrations
|
||||
|
||||
# ── Earthman Major Arcana (52 cards, numbers 0–51) ──────────────────────────
|
||||
# (name, slug, group, correspondence)
|
||||
EARTHMAN_MAJOR = [
|
||||
# ── The Schiz ──────────────────────────────────────────────────────────
|
||||
(0, "The Schiz", "the-schiz", "", "The Fool / Il Matto"),
|
||||
|
||||
# ── The Popes ──────────────────────────────────────────────────────────
|
||||
(1, "Pope I: President", "pope-i-president", "The Popes", "The Magician / Il Bagatto"),
|
||||
(2, "Pope II: Tsar", "pope-ii-tsar", "The Popes", "The Popess / La Papessa"),
|
||||
(3, "Pope III: Chairman", "pope-iii-chairman", "The Popes", "The Empress / L'Imperatrice"),
|
||||
(4, "Pope IV: Emperor", "pope-iv-emperor", "The Popes", "The Emperor / L'Imperatore"),
|
||||
(5, "Pope V: Chancellor", "pope-v-chancellor", "The Popes", "The Pope / Il Papa"),
|
||||
|
||||
# ── The Virtues, Implicit (cardinal / acquired) ────────────────────────
|
||||
(6, "Virtue VI: Controlled Folly", "virtue-vi-controlled-folly", "The Virtues, Implicit", "Fortitude / La Fortezza"),
|
||||
(7, "Virtue VII: Not-Doing", "virtue-vii-not-doing", "The Virtues, Implicit", "Justice / La Giustizia"),
|
||||
(8, "Virtue VIII: Losing Self-Importance","virtue-viii-losing-self-importance","The Virtues, Implicit", "Temperance / La Temperanza"),
|
||||
(9, "Virtue IX: Erasing Personal History","virtue-ix-erasing-personal-history","The Virtues, Implicit", "Prudence / La Prudenza"),
|
||||
|
||||
# ── Wheel ──────────────────────────────────────────────────────────────
|
||||
(10, "Wheel of Fortune", "wheel-of-fortune-em", "", "La Ruota della Fortuna"),
|
||||
|
||||
# ── Solo cards ─────────────────────────────────────────────────────────
|
||||
(11, "The Junkboat", "the-junkboat", "", "The Chariot / Il Carro"),
|
||||
(12, "The Junkman", "the-junkman", "", "The Hanged Man / L'Appeso"),
|
||||
(13, "Death", "death-em", "", "La Morte"),
|
||||
(14, "The Traitor", "the-traitor", "", "The Devil / Il Diavolo"),
|
||||
(15, "Disco Inferno", "disco-inferno", "", "The Tower / La Torre"),
|
||||
(16, "Torre Terrestre", "torre-terrestre", "", "Purgatorio"),
|
||||
(17, "Fantasia Celestia", "fantasia-celestia", "", "Paradiso"),
|
||||
|
||||
# ── The Virtues, Explicit (theological / infused) ─────────────────────
|
||||
(18, "Virtue XVIII: Stalking", "virtue-xviii-stalking", "The Virtues, Explicit", "Love / Charity / La Carità"),
|
||||
(19, "Virtue XIX: Intent", "virtue-xix-intent", "The Virtues, Explicit", "Hope / La Speranza"),
|
||||
(20, "Virtue XX: Dreaming", "virtue-xx-dreaming", "The Virtues, Explicit", "Faith / La Fede"),
|
||||
|
||||
# ── The Elements, Classical ────────────────────────────────────────────
|
||||
(21, "Element XXI: Fire", "element-xxi-fire", "The Elements, Classical", "Ardor [Ar]"),
|
||||
(22, "Element XXII: Earth", "element-xxii-earth", "The Elements, Classical", "Ossum [Om]"),
|
||||
(23, "Element XXIII: Air", "element-xxiii-air", "The Elements, Classical", "Pneuma [Pn]"),
|
||||
(24, "Element XXIV: Water", "element-xxiv-water", "The Elements, Classical", "Humor [Hm]"),
|
||||
|
||||
# ── The Zodiac ─────────────────────────────────────────────────────────
|
||||
(25, "Zodiac XXV: Aries", "zodiac-xxv-aries", "The Zodiac", "The Ram"),
|
||||
(26, "Zodiac XXVI: Taurus", "zodiac-xxvi-taurus", "The Zodiac", "The Bull"),
|
||||
(27, "Zodiac XXVII: Gemini", "zodiac-xxvii-gemini", "The Zodiac", "The Twins"),
|
||||
(28, "Zodiac XXVIII: Cancer", "zodiac-xxviii-cancer", "The Zodiac", "The Crab"),
|
||||
(29, "Zodiac XXIX: Leo", "zodiac-xxix-leo", "The Zodiac", "The Lion"),
|
||||
(30, "Zodiac XXX: Virgo", "zodiac-xxx-virgo", "The Zodiac", "The Maiden"),
|
||||
(31, "Zodiac XXXI: Libra", "zodiac-xxxi-libra", "The Zodiac", "The Scales"),
|
||||
(32, "Zodiac XXXII: Scorpio", "zodiac-xxxii-scorpio", "The Zodiac", "The Scorpion"),
|
||||
(33, "Zodiac XXXIII: Sagittarius", "zodiac-xxxiii-sagittarius", "The Zodiac", "The Archer"),
|
||||
(34, "Zodiac XXXIV: Capricorn", "zodiac-xxxiv-capricorn", "The Zodiac", "The Sea-Goat"),
|
||||
(35, "Zodiac XXXV: Aquarius", "zodiac-xxxv-aquarius", "The Zodiac", "The Water-Bearer"),
|
||||
(36, "Zodiac XXXVI: Pisces", "zodiac-xxxvi-pisces", "The Zodiac", "The Fish"),
|
||||
|
||||
# ── The Elements, Absolute ─────────────────────────────────────────────
|
||||
(37, "Element XXXVII: Time", "element-xxxvii-time", "The Elements, Absolute", "Tempo [Tp]"),
|
||||
(38, "Element XXXVIII: Space", "element-xxxviii-space", "The Elements, Absolute", "Nexus [Nx]"),
|
||||
|
||||
# ── The Wanderers ──────────────────────────────────────────────────────
|
||||
(39, "Wanderer XXXIX: The Polestar", "wanderer-xxxix-polestar", "The Wanderers", "The Star / Le Stelle"),
|
||||
(40, "Wanderer XL: The Antichthon", "wanderer-xl-antichthon", "The Wanderers", "The Moon / La Luna"),
|
||||
(41, "Wanderer XLI: The Corestar", "wanderer-xli-corestar", "The Wanderers", "The Sun / Il Sole"),
|
||||
(42, "Wanderer XLII: Mercury", "wanderer-xlii-mercury", "The Wanderers", "Mercurio"),
|
||||
(43, "Wanderer XLIII: Venus", "wanderer-xliii-venus", "The Wanderers", "Venere"),
|
||||
(44, "Wanderer XLIV: Mars", "wanderer-xliv-mars", "The Wanderers", "Marte"),
|
||||
(45, "Wanderer XLV: Jupiter", "wanderer-xlv-jupiter", "The Wanderers", "Giove"),
|
||||
(46, "Wanderer XLVI: Saturn", "wanderer-xlvi-saturn", "The Wanderers", "Saturno"),
|
||||
(47, "Wanderer XLVII: Uranus", "wanderer-xlvii-uranus", "The Wanderers", "Urano"),
|
||||
(48, "Wanderer XLVIII: Neptune", "wanderer-xlviii-neptune", "The Wanderers", "Nettuno"),
|
||||
(49, "Wanderer XLIX: The King & Queen of Hades", "wanderer-xlix-king-queen-hades", "The Wanderers", "The Binary / Plutone-Proserpina"),
|
||||
|
||||
# ── Finale ─────────────────────────────────────────────────────────────
|
||||
(50, "The Eagle", "the-eagle", "", "Judgement / L'Angelo"),
|
||||
(51, "Divine Calculus", "divine-calculus", "", "The World / Il Mondo"),
|
||||
]
|
||||
|
||||
# ── Earthman Minor Arcana ────────────────────────────────────────────────────
|
||||
# 4 suits × 14 cards. Suits: WANDS / CUPS / SWORDS / COINS
|
||||
# Court cards: Jack (11) / Cavalier (12) / Queen (13) / King (14)
|
||||
EARTHMAN_SUITS = [
|
||||
("WANDS", "wands", "Ardor [Ar] — Fire"),
|
||||
("CUPS", "cups", "Humor [Hm] — Water"),
|
||||
("SWORDS","swords","Pneuma [Pn] — Air"),
|
||||
("COINS", "coins", "Ossum [Om] — Stone"),
|
||||
]
|
||||
|
||||
EARTHMAN_RANKS = [
|
||||
(1, "Ace", "ace"),
|
||||
(2, "2", "two"),
|
||||
(3, "3", "three"),
|
||||
(4, "4", "four"),
|
||||
(5, "5", "five"),
|
||||
(6, "6", "six"),
|
||||
(7, "7", "seven"),
|
||||
(8, "8", "eight"),
|
||||
(9, "9", "nine"),
|
||||
(10, "10", "ten"),
|
||||
(11, "Jack", "jack"),
|
||||
(12, "Cavalier", "cavalier"),
|
||||
(13, "Queen", "queen"),
|
||||
(14, "King", "king"),
|
||||
]
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
TarotCard = apps.get_model("epic", "TarotCard")
|
||||
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||
|
||||
# ── 1. Create DeckVariant records ────────────────────────────────────
|
||||
fiorentine = DeckVariant.objects.create(
|
||||
name="Fiorentine Minchiate",
|
||||
slug="fiorentine-minchiate",
|
||||
card_count=78,
|
||||
description="Standard 78-card Minchiate deck. Alt / lite play mode.",
|
||||
is_default=False,
|
||||
)
|
||||
earthman = DeckVariant.objects.create(
|
||||
name="Earthman Deck",
|
||||
slug="earthman",
|
||||
card_count=108,
|
||||
description=(
|
||||
"Primary 108-card Earthman deck. "
|
||||
"52 Major Arcana (The Schiz through Divine Calculus) "
|
||||
"+ 56 Minor Arcana across Wands, Cups, Swords, Coins."
|
||||
),
|
||||
is_default=True,
|
||||
)
|
||||
|
||||
# ── 2. Backfill existing 78 Fiorentine cards ─────────────────────────
|
||||
TarotCard.objects.filter(deck_variant__isnull=True).update(
|
||||
deck_variant=fiorentine
|
||||
)
|
||||
|
||||
# ── 3. Seed Earthman Major Arcana ────────────────────────────────────
|
||||
for number, name, slug, group, correspondence in EARTHMAN_MAJOR:
|
||||
TarotCard.objects.create(
|
||||
deck_variant=earthman,
|
||||
name=name,
|
||||
arcana="MAJOR",
|
||||
suit=None,
|
||||
number=number,
|
||||
slug=slug,
|
||||
group=group,
|
||||
correspondence=correspondence,
|
||||
keywords_upright=[],
|
||||
keywords_reversed=[],
|
||||
)
|
||||
|
||||
# ── 4. Seed Earthman Minor Arcana ────────────────────────────────────
|
||||
for suit_code, suit_slug, _element in EARTHMAN_SUITS:
|
||||
for number, rank_name, rank_slug in EARTHMAN_RANKS:
|
||||
name = f"{rank_name} of {suit_code.capitalize()}"
|
||||
slug = f"{rank_slug}-of-{suit_slug}-em"
|
||||
TarotCard.objects.create(
|
||||
deck_variant=earthman,
|
||||
name=name,
|
||||
arcana="MINOR",
|
||||
suit=suit_code,
|
||||
number=number,
|
||||
slug=slug,
|
||||
group="",
|
||||
correspondence="",
|
||||
keywords_upright=[],
|
||||
keywords_reversed=[],
|
||||
)
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
TarotCard = apps.get_model("epic", "TarotCard")
|
||||
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||
|
||||
# Remove Earthman cards and clear FK from Fiorentine cards
|
||||
earthman = DeckVariant.objects.filter(slug="earthman").first()
|
||||
if earthman:
|
||||
TarotCard.objects.filter(deck_variant=earthman).delete()
|
||||
|
||||
fiorentine = DeckVariant.objects.filter(slug="fiorentine-minchiate").first()
|
||||
if fiorentine:
|
||||
TarotCard.objects.filter(deck_variant=fiorentine).update(deck_variant=None)
|
||||
|
||||
DeckVariant.objects.filter(slug__in=["earthman", "fiorentine-minchiate"]).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("epic", "0009_deckvariant_alter_tarotcard_options_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward, reverse_code=reverse),
|
||||
]
|
||||
Reference in New Issue
Block a user