From c3f0342a2db9f652cc76d9e4aabf97123f27a9de Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Mon, 27 Apr 2026 01:51:40 -0400 Subject: [PATCH] Earthman deck: new TarotCard fields + full 49-card major arcana reseed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TarotCard: add reversal, levity/gravity_qualifier, levity/gravity_emanation, levity/gravity_reversal, mechanisms, articulations; emanation_for(polarity) + reversal_for(polarity) methods - 0035: schema migration for new fields - 0036: reseed major arcana — 52 cards → 50 (Nomad untouched); delete Wheel of Fortune/Junkboat/Great Hunt; insert Asteroid Belt; rename/renumber all into Pope/Horseman, Elements, Realms, Virtues, Zodiac, Lunars, Planets, Inner Rings; levity/gravity qualifiers throughout; cards 48-49 polarity-split levity/gravity emanation + reversal - 0037: Polestar qualifiers → Precessional / Recessional - 0038: correspondences → Fiorentine card names (Magician–Hierophant for 1-5; sign names for zodiac 22-33) - 0039: reversals — Pope/Horseman 1-5 → Territoriality/Despotism/Capitalism/Fascism; Zodiac 22-33 → House of Self … House of Reprisal - 0040: trump 21 → Intent (was Jovent) ⚠ pending: cards 1-2 reversal both 'Territoriality' — confirm if distinct; Implicit/Explicit Virtue qualifiers blank Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Sonnet 4.6 --- .../0035_earthman_deck_new_fields.py | 58 ++++ .../migrations/0036_earthman_deck_reseed.py | 307 ++++++++++++++++++ .../migrations/0037_polestar_qualifiers.py | 24 ++ .../migrations/0038_fix_correspondences.py | 60 ++++ .../epic/migrations/0039_fix_reversals.py | 59 ++++ .../0040_rename_jovent_to_intent.py | 24 ++ src/apps/epic/models.py | 31 +- 7 files changed, 561 insertions(+), 2 deletions(-) create mode 100644 src/apps/epic/migrations/0035_earthman_deck_new_fields.py create mode 100644 src/apps/epic/migrations/0036_earthman_deck_reseed.py create mode 100644 src/apps/epic/migrations/0037_polestar_qualifiers.py create mode 100644 src/apps/epic/migrations/0038_fix_correspondences.py create mode 100644 src/apps/epic/migrations/0039_fix_reversals.py create mode 100644 src/apps/epic/migrations/0040_rename_jovent_to_intent.py diff --git a/src/apps/epic/migrations/0035_earthman_deck_new_fields.py b/src/apps/epic/migrations/0035_earthman_deck_new_fields.py new file mode 100644 index 0000000..8e33a97 --- /dev/null +++ b/src/apps/epic/migrations/0035_earthman_deck_new_fields.py @@ -0,0 +1,58 @@ +# Generated by Django 6.0 on 2026-04-27 05:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('epic', '0034_character_model'), + ] + + operations = [ + migrations.AddField( + model_name='tarotcard', + name='articulations', + field=models.JSONField(default=list), + ), + migrations.AddField( + model_name='tarotcard', + name='gravity_emanation', + field=models.CharField(blank=True, default='', max_length=200), + ), + migrations.AddField( + model_name='tarotcard', + name='gravity_qualifier', + field=models.CharField(blank=True, default='', max_length=100), + ), + migrations.AddField( + model_name='tarotcard', + name='gravity_reversal', + field=models.CharField(blank=True, default='', max_length=200), + ), + migrations.AddField( + model_name='tarotcard', + name='levity_emanation', + field=models.CharField(blank=True, default='', max_length=200), + ), + migrations.AddField( + model_name='tarotcard', + name='levity_qualifier', + field=models.CharField(blank=True, default='', max_length=100), + ), + migrations.AddField( + model_name='tarotcard', + name='levity_reversal', + field=models.CharField(blank=True, default='', max_length=200), + ), + migrations.AddField( + model_name='tarotcard', + name='mechanisms', + field=models.JSONField(default=list), + ), + migrations.AddField( + model_name='tarotcard', + name='reversal', + field=models.CharField(blank=True, default='', max_length=200), + ), + ] diff --git a/src/apps/epic/migrations/0036_earthman_deck_reseed.py b/src/apps/epic/migrations/0036_earthman_deck_reseed.py new file mode 100644 index 0000000..2239caf --- /dev/null +++ b/src/apps/epic/migrations/0036_earthman_deck_reseed.py @@ -0,0 +1,307 @@ +""" +Re-seed the Earthman major arcana from 52 cards (0-51) to 50 cards (0 + 1-49). + +Card 0 (The Nomad) is left completely untouched. + +Changes: + DELETE old #10 Wheel of Fortune, #11 The Junkboat, #14 The Great Hunt + INSERT new #41 The Asteroid Belt + UPDATE all other major arcana with new numbers, names, slugs, groups, + correspondences, reversals, and levity/gravity qualifiers. + +Items flagged ⚠ below were not clearly legible from the source image and are +left blank; the user should fill them in a follow-up migration. +""" + +from django.db import migrations + + +# --------------------------------------------------------------------------- +# UPDATE spec: (current_slug, new_number, new_name, new_slug, new_group, +# correspondence, reversal, +# levity_qualifier, gravity_qualifier, +# levity_emanation, gravity_emanation, +# levity_reversal, gravity_reversal) +# --------------------------------------------------------------------------- +_UPDATES = [ + # ── Pope/Horseman ────────────────────────────────────────────────────── + ('pope-1-the-schizo', 1, 'The Schizo', + 'the-schizo', 'Pope/Horseman', + 'Territoriality', '', + 'Enlightened', 'Engraven', + '', '', '', ''), + + ('pope-2-the-occultist', 2, 'The Occultist', + 'the-occultist', 'Pope/Horseman', + 'Territoriality', '', + 'Enlightened', 'Engraven', + '', '', '', ''), + + ('pope-3-the-despot', 3, 'The Despot', + 'the-despot', 'Pope/Horseman', + 'Despotism', '', + 'Enlightened', 'Engraven', + '', '', '', ''), + + ('pope-4-the-capitalist', 4, 'The Capitalist', + 'the-capitalist', 'Pope/Horseman', + 'Capitalism', '', + 'Enlightened', 'Engraven', + '', '', '', ''), + + ('pope-5-the-fascist', 5, 'The Fascist', + 'the-fascist', 'Pope/Horseman', + 'Fascism', '', + 'Enlightened', 'Engraven', + '', '', '', ''), + + # ── Implicit Virtues ─────────────────────────────────────────────────── + # ⚠ levity/gravity qualifiers not determined + ('implicit-virtue-1-controlled-folly', 6, 'Controlled Folly', + 'controlled-folly', 'Implicit Virtues', + 'Fortitude', '', + '', '', '', '', '', ''), + + ('implicit-virtue-2-not-doing', 7, 'Not Doing', + 'not-doing', 'Implicit Virtues', + 'Temperance', '', + '', '', '', '', '', ''), + + ('implicit-virtue-3-losing-self-importance', 8, 'Losing Self-Importance', + 'losing-self-importance', 'Implicit Virtues', + 'Justice', '', + '', '', '', '', '', ''), + + ('implicit-virtue-4-erasing-personal-history', 9, 'Erasing Personal History', + 'erasing-personal-history', 'Implicit Virtues', + 'Prudence', '', + '', '', '', '', '', ''), + + # ── Elements (6) — Absolute elements move here alongside Classical ───── + # Space: was Absolute Element 2 (#38), Chariot correspondence is new + ('absolute-element-2-space', 10, 'Space', + 'space-em', 'Elements', + 'Chariot', 'Nexus', + 'Kinetic', 'Potential', + '', '', '', ''), + + # Time: was Absolute Element 1 (#37) + # ⚠ Hermit correspondence confirmed; "Hunchback" variant noted in image + ('absolute-element-1-time', 11, 'Time', + 'time-em', 'Elements', + 'Hermit', 'Tempo', + 'Kinetic', 'Potential', + '', '', '', ''), + + # Stone: was Classical Element 2 (#22) + ('classical-element-2-stone', 12, 'Stone', + 'stone-em', 'Elements', + 'Earth', 'Ossum', + 'Kinetic', 'Potential', + '', '', '', ''), + + # Fire: was Classical Element 1 (#21) + ('classical-element-1-fire', 13, 'Fire', + 'fire-em', 'Elements', + 'Fire', 'Ardor', + 'Kinetic', 'Potential', + '', '', '', ''), + + # Water: was Classical Element 4 (#24) + ('classical-element-4-water', 14, 'Water', + 'water-em', 'Elements', + 'Water', 'Humor', + 'Kinetic', 'Potential', + '', '', '', ''), + + # Air: was Classical Element 3 (#23) + ('classical-element-3-air', 15, 'Air', + 'air-em', 'Elements', + 'Air', 'Pneuma', + 'Kinetic', 'Potential', + '', '', '', ''), + + # ── Realms ───────────────────────────────────────────────────────────── + ('disco-inferno', 16, 'Disco Inferno', + 'disco-inferno', 'Realms', + 'Devil', 'Shame', + 'Deasil', 'Widdershins', + '', '', '', ''), + + ('torre-terrestre', 17, 'Torre Terrestre', + 'torre-terrestre', 'Realms', + 'Tower', 'Guilt', + 'Deasil', 'Widdershins', + '', '', '', ''), + + ('fantasia-celestia', 18, 'Fantasia Celestia', + 'fantasia-celestia', 'Realms', + 'Wheel of Fortune', 'Anxiety', + 'Deasil', 'Widdershins', + '', '', '', ''), + + # ── Explicit Virtues ─────────────────────────────────────────────────── + # ⚠ levity/gravity qualifiers not determined + # Stalking: was Explicit Virtue 1 (#18, Love/Charity) + ('explicit-virtue-1-stalking', 19, 'Stalking', + 'stalking', 'Explicit Virtues', + 'Charity', '', + '', '', '', '', '', ''), + + # Dreaming: was Explicit Virtue 3 (#20, Faith) — same number + ('explicit-virtue-3-dreaming', 20, 'Dreaming', + 'dreaming', 'Explicit Virtues', + 'Faith', '', + '', '', '', '', '', ''), + + # Jovent: was Explicit Virtue 2 (#19, Intent/Hope) — renamed + ('explicit-virtue-2-intent', 21, 'Jovent', + 'jovent', 'Explicit Virtues', + 'Hope', '', + '', '', '', '', '', ''), + + # ── Zodiac Signs & Houses (renumber 25-36 → 22-33) ──────────────────── + ('zodiac-1-aries', 22, 'Aries', 'aries', 'Zodiac Signs & Houses', 'House of Self', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-2-taurus', 23, 'Taurus', 'taurus', 'Zodiac Signs & Houses', 'House of Worth', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-3-gemini', 24, 'Gemini', 'gemini', 'Zodiac Signs & Houses', 'House of Education', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-4-cancer', 25, 'Cancer', 'cancer', 'Zodiac Signs & Houses', 'House of Family', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-5-leo', 26, 'Leo', 'leo', 'Zodiac Signs & Houses', 'House of Creation', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-6-virgo', 27, 'Virgo', 'virgo', 'Zodiac Signs & Houses', 'House of Ritual', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-7-libra', 28, 'Libra', 'libra', 'Zodiac Signs & Houses', 'House of Cooperation', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-8-scorpio', 29, 'Scorpio', 'scorpio', 'Zodiac Signs & Houses', 'House of Regeneration', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-9-sagittarius', 30, 'Sagittarius', 'sagittarius', 'Zodiac Signs & Houses', 'House of Enterprise', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-10-capricorn', 31, 'Capricorn', 'capricorn', 'Zodiac Signs & Houses', 'House of Career', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-11-aquarius', 32, 'Aquarius', 'aquarius', 'Zodiac Signs & Houses', 'House of Reward', '', 'Precessional', 'Recessional', '', '', '', ''), + ('zodiac-12-pisces', 33, 'Pisces', 'pisces', 'Zodiac Signs & Houses', 'House of Reprisal', '', 'Precessional', 'Recessional', '', '', '', ''), + + # ── Lunars ───────────────────────────────────────────────────────────── + # Animal Powers: was The Junkman (#12, Hanged Man) — renamed + ('the-junkman', 34, 'Animal Powers', + 'animal-powers', 'Lunars', + 'Hanged Man', 'Patrilineage', + 'Centrifugal', 'Centripetal', + '', '', '', ''), + + # Seeded Earth: was King Death & the Cosmic Tree (#13, Death) — renamed + ('king-death-and-the-cosmic-tree', 35, 'Seeded Earth', + 'seeded-earth', 'Lunars', + 'Death', 'Matrilineage', + 'Centrifugal', 'Centripetal', + '', '', '', ''), + + # Twins of Pluto: was Wanderer 11 King & Queen of Hades (#49) — renamed + ('wanderer-11-king-queen-hades', 36, 'The Twins of Pluto', + 'twins-of-pluto', 'Lunars', + 'Pluto', '', + 'Prograde', 'Retrograde', + '', '', '', ''), + + # ── Planets ──────────────────────────────────────────────────────────── + # ⚠ correspondences left blank — image unclear; user to confirm + # Reversals: user confirmed all planet cards reverse to same (blank = same) + ('wanderer-10-neptune', 37, 'Neptune', 'neptune', 'Planets', 'Neptune', '', 'Prograde', 'Retrograde', '', '', '', ''), + ('wanderer-9-uranus', 38, 'Uranus', 'uranus', 'Planets', 'Uranus', '', 'Prograde', 'Retrograde', '', '', '', ''), + ('wanderer-8-saturn', 39, 'Saturn', 'saturn', 'Planets', 'Saturn', '', 'Prograde', 'Retrograde', '', '', '', ''), + ('wanderer-7-jupiter', 40, 'Jupiter', 'jupiter', 'Planets', 'Jupiter', '', 'Prograde', 'Retrograde', '', '', '', ''), + # #41 The Asteroid Belt — INSERT separately below + ('wanderer-6-mars', 42, 'Mars', 'mars', 'Planets', 'Mars', '', 'Prograde', 'Retrograde', '', '', '', ''), + ('wanderer-5-venus', 43, 'Venus', 'venus', 'Planets', 'Venus', '', 'Prograde', 'Retrograde', '', '', '', ''), + ('wanderer-4-mercury', 44, 'Mercury', 'mercury', 'Planets', 'Mercury', '', 'Prograde', 'Retrograde', '', '', '', ''), + + # ── Inner Rings ──────────────────────────────────────────────────────── + # ⚠ Polestar levity/gravity qualifiers not confirmed from image + ('wanderer-1-polestar', 45, 'The Polestar', 'the-polestar', 'Inner Rings', 'Star', '', '', '', '', '', '', ''), + ('wanderer-2-antichthon',46, 'The Antichthon', 'the-antichthon', 'Inner Rings', 'Moon', '', 'Waxing', 'Waning', '', '', '', ''), + # Corestar: user changed Ascendant→Inclining, Descendent→Declining + ('wanderer-3-corestar', 47, 'The Corestar', 'the-corestar', 'Inner Rings', 'Sun', '', 'Inclining', 'Declining', '', '', '', ''), + + # ── Polarity-split finals ────────────────────────────────────────────── + # 48: The Eagle → Father Sky (levity) / Mother Sea (gravity) + ('the-eagle', 48, 'Father Sky / Mother Sea', + 'father-sky-mother-sea', '', + 'World', '', + '', '', + 'Father Sky', 'Mother Sea', + 'The Storm', 'The Flood'), + + # 49: The Mould of Man → Effulgent Mould of Man (levity) / Devouring Eagle (gravity) + ('the-mould-of-man', 49, 'The Effulgent Mould of Man / The Devouring Eagle', + 'effulgent-mould-devouring-eagle', '', + 'Trumpets', '', + '', '', + 'The Effulgent Mould of Man', 'The Devouring Eagle', + '', ''), +] + +_DELETE_SLUGS = [ + 'wheel-of-fortune-em', # old #10 — correspondence absorbed by Fantasia Celestia + 'the-junkboat', # old #11 — Space element takes the Chariot correspondence + 'the-great-hunt', # old #14 — Disco Inferno takes the Devil correspondence +] + + +def reseed_earthman_majors(apps, schema_editor): + TarotCard = apps.get_model('epic', 'TarotCard') + DeckVariant = apps.get_model('epic', 'DeckVariant') + + try: + earthman = DeckVariant.objects.get(slug='earthman') + except DeckVariant.DoesNotExist: + return # no-op in fresh DB without seeded variants + + # 1. Delete retired cards + TarotCard.objects.filter(deck_variant=earthman, slug__in=_DELETE_SLUGS).delete() + + # 2. Update existing cards — set new number to a temporary negative to + # avoid any incidental ordering conflicts mid-loop, then finalise. + # (No unique constraint on number, so straightforward.) + for row in _UPDATES: + (cur_slug, new_num, new_name, new_slug, new_group, + corr, reversal, lq, gq, le, ge, lr, gr) = row + TarotCard.objects.filter( + deck_variant=earthman, slug=cur_slug, + ).update( + number=new_num, + name=new_name, + slug=new_slug, + group=new_group, + correspondence=corr, + reversal=reversal, + levity_qualifier=lq, + gravity_qualifier=gq, + levity_emanation=le, + gravity_emanation=ge, + levity_reversal=lr, + gravity_reversal=gr, + ) + + # 3. Insert The Asteroid Belt (new card, no existing equivalent) + TarotCard.objects.get_or_create( + deck_variant=earthman, + slug='the-asteroid-belt', + defaults=dict( + number=41, + name='The Asteroid Belt', + arcana='MAJOR', + group='Planets', + correspondence='', + reversal='Ouroboros', + levity_qualifier='Prograde', + gravity_qualifier='Retrograde', + ), + ) + + +def reverse_reseed(apps, schema_editor): + pass # irreversible structural change + + +class Migration(migrations.Migration): + + dependencies = [ + ('epic', '0035_earthman_deck_new_fields'), + ] + + operations = [ + migrations.RunPython(reseed_earthman_majors, reverse_reseed), + ] diff --git a/src/apps/epic/migrations/0037_polestar_qualifiers.py b/src/apps/epic/migrations/0037_polestar_qualifiers.py new file mode 100644 index 0000000..a317a0e --- /dev/null +++ b/src/apps/epic/migrations/0037_polestar_qualifiers.py @@ -0,0 +1,24 @@ +from django.db import migrations + + +def set_polestar_qualifiers(apps, schema_editor): + TarotCard = apps.get_model('epic', 'TarotCard') + DeckVariant = apps.get_model('epic', 'DeckVariant') + try: + earthman = DeckVariant.objects.get(slug='earthman') + except DeckVariant.DoesNotExist: + return + TarotCard.objects.filter( + deck_variant=earthman, slug='the-polestar', + ).update(levity_qualifier='Precessional', gravity_qualifier='Recessional') + + +class Migration(migrations.Migration): + + dependencies = [ + ('epic', '0036_earthman_deck_reseed'), + ] + + operations = [ + migrations.RunPython(set_polestar_qualifiers, migrations.RunPython.noop), + ] diff --git a/src/apps/epic/migrations/0038_fix_correspondences.py b/src/apps/epic/migrations/0038_fix_correspondences.py new file mode 100644 index 0000000..5c93c60 --- /dev/null +++ b/src/apps/epic/migrations/0038_fix_correspondences.py @@ -0,0 +1,60 @@ +""" +Fix TarotCard.correspondence values for Earthman major arcana to store +the Fiorentine/Tarot card name (1:1 correspondence) rather than Earthman +conceptual terms. + +Two batches corrected: + 1. Pope/Horseman (1-5): Territoriality/Despotism/Capitalism/Fascism + → Magician/High Priestess/Empress/Emperor/Hierophant + 2. Zodiac (22-33): House of Self/Worth/… → Aries/Taurus/…/Pisces + (Minchiate Fiorentine has all 12 zodiac sign cards; correspondence = sign name) +""" + +from django.db import migrations + +_POPE_FIXES = { + 'the-schizo': 'Magician', + 'the-occultist': 'High Priestess', + 'the-despot': 'Empress', + 'the-capitalist': 'Emperor', + 'the-fascist': 'Hierophant', +} + +_ZODIAC_FIXES = { + 'aries': 'Aries', + 'taurus': 'Taurus', + 'gemini': 'Gemini', + 'cancer': 'Cancer', + 'leo': 'Leo', + 'virgo': 'Virgo', + 'libra': 'Libra', + 'scorpio': 'Scorpio', + 'sagittarius': 'Sagittarius', + 'capricorn': 'Capricorn', + 'aquarius': 'Aquarius', + 'pisces': 'Pisces', +} + + +def fix_correspondences(apps, schema_editor): + TarotCard = apps.get_model('epic', 'TarotCard') + DeckVariant = apps.get_model('epic', 'DeckVariant') + try: + earthman = DeckVariant.objects.get(slug='earthman') + except DeckVariant.DoesNotExist: + return + for slug, corr in {**_POPE_FIXES, **_ZODIAC_FIXES}.items(): + TarotCard.objects.filter(deck_variant=earthman, slug=slug).update( + correspondence=corr, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('epic', '0037_polestar_qualifiers'), + ] + + operations = [ + migrations.RunPython(fix_correspondences, migrations.RunPython.noop), + ] diff --git a/src/apps/epic/migrations/0039_fix_reversals.py b/src/apps/epic/migrations/0039_fix_reversals.py new file mode 100644 index 0000000..b31b189 --- /dev/null +++ b/src/apps/epic/migrations/0039_fix_reversals.py @@ -0,0 +1,59 @@ +""" +Fix TarotCard.reversal values for Pope/Horseman and Zodiac cards. + +Pope/Horseman (1-5): reversed-state names read from image reversal column. + ⚠ Cards 1-2 (Schizo/Occultist): image showed 'Territoriality*' spanning + rows 1-2 — stored as 'Territoriality' for both; user to confirm if + they have distinct reversed names. + +Zodiac (22-33): reversed state = corresponding house (e.g. Aries → House of Self). +""" + +from django.db import migrations + +_POPE_REVERSALS = { + 'the-schizo': 'Territoriality', # ⚠ confirm if distinct from Occultist + 'the-occultist': 'Territoriality', # ⚠ confirm + 'the-despot': 'Despotism', + 'the-capitalist': 'Capitalism', + 'the-fascist': 'Fascism', +} + +_ZODIAC_REVERSALS = { + 'aries': 'House of Self', + 'taurus': 'House of Worth', + 'gemini': 'House of Education', + 'cancer': 'House of Family', + 'leo': 'House of Creation', + 'virgo': 'House of Ritual', + 'libra': 'House of Cooperation', + 'scorpio': 'House of Regeneration', + 'sagittarius': 'House of Enterprise', + 'capricorn': 'House of Career', + 'aquarius': 'House of Reward', + 'pisces': 'House of Reprisal', +} + + +def fix_reversals(apps, schema_editor): + TarotCard = apps.get_model('epic', 'TarotCard') + DeckVariant = apps.get_model('epic', 'DeckVariant') + try: + earthman = DeckVariant.objects.get(slug='earthman') + except DeckVariant.DoesNotExist: + return + for slug, reversal in {**_POPE_REVERSALS, **_ZODIAC_REVERSALS}.items(): + TarotCard.objects.filter( + deck_variant=earthman, slug=slug, + ).update(reversal=reversal) + + +class Migration(migrations.Migration): + + dependencies = [ + ('epic', '0038_fix_correspondences'), + ] + + operations = [ + migrations.RunPython(fix_reversals, migrations.RunPython.noop), + ] diff --git a/src/apps/epic/migrations/0040_rename_jovent_to_intent.py b/src/apps/epic/migrations/0040_rename_jovent_to_intent.py new file mode 100644 index 0000000..452038b --- /dev/null +++ b/src/apps/epic/migrations/0040_rename_jovent_to_intent.py @@ -0,0 +1,24 @@ +from django.db import migrations + + +def rename_jovent_to_intent(apps, schema_editor): + TarotCard = apps.get_model('epic', 'TarotCard') + DeckVariant = apps.get_model('epic', 'DeckVariant') + try: + earthman = DeckVariant.objects.get(slug='earthman') + except DeckVariant.DoesNotExist: + return + TarotCard.objects.filter( + deck_variant=earthman, slug='jovent', + ).update(name='Intent', slug='intent') + + +class Migration(migrations.Migration): + + dependencies = [ + ('epic', '0039_fix_reversals'), + ] + + operations = [ + migrations.RunPython(rename_jovent_to_intent, migrations.RunPython.noop), + ] diff --git a/src/apps/epic/models.py b/src/apps/epic/models.py index 7d2482e..6bf6e97 100644 --- a/src/apps/epic/models.py +++ b/src/apps/epic/models.py @@ -242,10 +242,19 @@ class TarotCard(models.Model): arcana = models.CharField(max_length=6, choices=ARCANA_CHOICES) suit = models.CharField(max_length=10, choices=SUIT_CHOICES, null=True, blank=True) icon = models.CharField(max_length=50, blank=True, default='') # FA icon override (e.g. major arcana) - number = models.IntegerField() # 0–21 major (Fiorentine); 0–51 major (Earthman); 1–14 minor + number = models.IntegerField() # 0–21 major (Fiorentine); 0–49 major (Earthman); 1–14 minor slug = models.SlugField(max_length=120) - correspondence = models.CharField(max_length=200, blank=True) # standard / Italian equivalent + correspondence = models.CharField(max_length=200, blank=True) # Tarot / Minchiate equivalent group = models.CharField(max_length=100, blank=True) # Earthman major grouping + reversal = models.CharField(max_length=200, blank=True, default='') # reversed-state title; blank = same as name + levity_qualifier = models.CharField(max_length=100, blank=True, default='') + gravity_qualifier = models.CharField(max_length=100, blank=True, default='') + levity_emanation = models.CharField(max_length=200, blank=True, default='') # polarity-split upright (cards 48-49) + gravity_emanation = models.CharField(max_length=200, blank=True, default='') + levity_reversal = models.CharField(max_length=200, blank=True, default='') # polarity-split reversal (card 48) + gravity_reversal = models.CharField(max_length=200, blank=True, default='') + mechanisms = models.JSONField(default=list) # list of dicts; in-game effects + articulations = models.JSONField(default=list) # list of dicts; combinatory effects keywords_upright = models.JSONField(default=list) keywords_reversed = models.JSONField(default=list) cautions = models.JSONField(default=list) @@ -274,6 +283,24 @@ class TarotCard(models.Model): court = {11: 'M', 12: 'J', 13: 'Q', 14: 'K'} return court.get(self.number, str(self.number)) + def emanation_for(self, polarity): + """Return the upright title for a given polarity ('levity' or 'gravity'). + Falls back to name for cards without a polarity split.""" + if polarity == 'levity' and self.levity_emanation: + return self.levity_emanation + if polarity == 'gravity' and self.gravity_emanation: + return self.gravity_emanation + return self.name + + def reversal_for(self, polarity): + """Return the reversed title for a given polarity. + Falls back to reversal (blank = same as emanation_for).""" + if polarity == 'levity' and self.levity_reversal: + return self.levity_reversal + if polarity == 'gravity' and self.gravity_reversal: + return self.gravity_reversal + return self.reversal or self.emanation_for(polarity) + @property def name_group(self): """Returns 'Group N:' prefix if the name contains ': ', else ''."""