collapse epic migrations 0007–0022 → 0007_finalize_earthman_deck; add reset_staging_db
- 16 incremental Earthman tweak migrations folded into one end-state finalize migration (rename mechanisms→energies / articulations→operations / reversal→reversal_qualifier; +italic_word; suit court reversals; Schizo energies+operations; card 49 polarity reversal titles; Castanedan Virtues trumps 6–9 + 19–21; trump 8 U+2011 hyphen; trump 9 U+00A0 nbsp; pips → MINOR) - 22 epic migrations → 7; 748 ITs green - new mgmt cmd `reset_staging_db` — drops schema (Postgres) / tables (sqlite) & re-runs migrate; refuses on prod hosts; needs `--i-mean-it` when DEBUG=False; interactive host-name confirmation locally; calls ensure_superuser after Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0
src/apps/epic/management/__init__.py
Normal file
0
src/apps/epic/management/__init__.py
Normal file
0
src/apps/epic/management/commands/__init__.py
Normal file
0
src/apps/epic/management/commands/__init__.py
Normal file
110
src/apps/epic/management/commands/reset_staging_db.py
Normal file
110
src/apps/epic/management/commands/reset_staging_db.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""Wipe the configured database and re-run all migrations from scratch.
|
||||||
|
|
||||||
|
Intended for ephemeral environments (staging) where losing every user, room,
|
||||||
|
billpost, token, etc. is acceptable. Refuses to run when DEBUG=False unless
|
||||||
|
the operator explicitly confirms with --i-mean-it, and always prints the
|
||||||
|
DB host before doing anything destructive.
|
||||||
|
|
||||||
|
Typical staging usage from the deploy host:
|
||||||
|
|
||||||
|
docker exec -it gamearray python manage.py reset_staging_db --i-mean-it
|
||||||
|
|
||||||
|
Locally (sqlite, DEBUG=True) the safety prompt is skipped:
|
||||||
|
|
||||||
|
python src/manage.py reset_staging_db
|
||||||
|
"""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
|
||||||
|
PROD_HOST_FRAGMENTS = ("earthmanrpg.me",)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Drop every table in the default DB and re-run migrations. Destructive."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"--i-mean-it",
|
||||||
|
action="store_true",
|
||||||
|
help="Required when DEBUG=False. Bypasses the interactive confirmation.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-superuser",
|
||||||
|
action="store_true",
|
||||||
|
help="Skip the post-migrate ensure_superuser call.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **opts):
|
||||||
|
db_settings = settings.DATABASES["default"]
|
||||||
|
engine = db_settings.get("ENGINE", "")
|
||||||
|
host = db_settings.get("HOST") or db_settings.get("NAME") or "(unknown)"
|
||||||
|
|
||||||
|
# Refuse outright if the host name suggests production
|
||||||
|
if any(frag in str(host) for frag in PROD_HOST_FRAGMENTS) and not host.startswith("staging"):
|
||||||
|
if "staging" not in str(host):
|
||||||
|
raise CommandError(
|
||||||
|
f"Refusing to reset DB at host={host!r} — looks like production. "
|
||||||
|
"Edit PROD_HOST_FRAGMENTS in this command if you really mean it."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING(
|
||||||
|
f"\nAbout to wipe DB:\n ENGINE: {engine}\n HOST/NAME: {host}\n"
|
||||||
|
))
|
||||||
|
|
||||||
|
if not settings.DEBUG and not opts["i_mean_it"]:
|
||||||
|
raise CommandError(
|
||||||
|
"DEBUG=False — pass --i-mean-it to confirm. "
|
||||||
|
"(This is the staging-safety check; it does not bypass the prod-host refusal above.)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.DEBUG and not opts["i_mean_it"]:
|
||||||
|
answer = input("Type the DB host/name to confirm: ").strip()
|
||||||
|
if answer != str(host):
|
||||||
|
raise CommandError(f"Got {answer!r}, expected {str(host)!r}. Aborting.")
|
||||||
|
|
||||||
|
# Drop schema. Postgres + sqlite both honor `flush --no-input`'s
|
||||||
|
# truncate-tables-in-place model, but for a *fresh* migration run we
|
||||||
|
# need the migration history wiped too. For Postgres the cleanest
|
||||||
|
# route is `DROP SCHEMA public CASCADE; CREATE SCHEMA public;` —
|
||||||
|
# for sqlite, deleting the file is simpler but Django's connection
|
||||||
|
# has it open. So: introspect the connection and drop all tables.
|
||||||
|
self.stdout.write("Dropping all tables…")
|
||||||
|
self._drop_all_tables()
|
||||||
|
|
||||||
|
self.stdout.write("Running migrate from scratch…")
|
||||||
|
call_command("migrate", verbosity=1, interactive=False)
|
||||||
|
|
||||||
|
if not opts["no_superuser"]:
|
||||||
|
try:
|
||||||
|
call_command("ensure_superuser", verbosity=1)
|
||||||
|
except Exception as exc: # pragma: no cover - depends on env vars
|
||||||
|
self.stdout.write(self.style.WARNING(
|
||||||
|
f"ensure_superuser skipped/failed: {exc}"
|
||||||
|
))
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS("\nDB reset complete."))
|
||||||
|
|
||||||
|
def _drop_all_tables(self):
|
||||||
|
vendor = connection.vendor
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
if vendor == "postgresql":
|
||||||
|
cursor.execute("DROP SCHEMA public CASCADE;")
|
||||||
|
cursor.execute("CREATE SCHEMA public;")
|
||||||
|
cursor.execute("GRANT ALL ON SCHEMA public TO public;")
|
||||||
|
elif vendor == "sqlite":
|
||||||
|
cursor.execute("PRAGMA foreign_keys = OFF;")
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT name FROM sqlite_master "
|
||||||
|
"WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||||
|
)
|
||||||
|
tables = [row[0] for row in cursor.fetchall()]
|
||||||
|
for table in tables:
|
||||||
|
cursor.execute(f'DROP TABLE IF EXISTS "{table}"')
|
||||||
|
cursor.execute("PRAGMA foreign_keys = ON;")
|
||||||
|
else:
|
||||||
|
raise CommandError(
|
||||||
|
f"reset_staging_db only knows postgresql + sqlite, got {vendor!r}"
|
||||||
|
)
|
||||||
200
src/apps/epic/migrations/0007_finalize_earthman_deck.py
Normal file
200
src/apps/epic/migrations/0007_finalize_earthman_deck.py
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
"""Collapse of old migrations 0007–0022 into a single end-state finalize.
|
||||||
|
|
||||||
|
Schema:
|
||||||
|
- mechanisms → energies (was 0008)
|
||||||
|
- articulations → operations (was 0008)
|
||||||
|
- reversal → reversal_qualifier (was 0014)
|
||||||
|
- +italic_word (was 0018)
|
||||||
|
|
||||||
|
Data (operates on the Earthman deck seeded in 0004; idempotent against the
|
||||||
|
current schema, which is the post-rename state thanks to the operations
|
||||||
|
above running first):
|
||||||
|
- Middle court reversal_qualifier per suit (was 0007)
|
||||||
|
- Schizo energies + operations w/ .card-ref (was 0008 + 0009)
|
||||||
|
- fa-hand-dots fallback for empty MAJOR icons (was 0010; only card 41
|
||||||
|
in fresh-seed state)
|
||||||
|
- Card 49 polarity-split reversal titles (was 0015 + 0016)
|
||||||
|
- Castanedan Virtues: trumps 6-9 + 19-21 (was 0017)
|
||||||
|
- italic_word for trumps 19-21 (was 0019)
|
||||||
|
- Trump 8 rename + non-breaking hyphen (was 0020 + 0021)
|
||||||
|
- Trump 9 non-breaking space (was 0021)
|
||||||
|
- Pip cards (number 1-10) MIDDLE → MINOR arcana (was 0022)
|
||||||
|
|
||||||
|
Skipped (all no-ops against fresh 0004 seed):
|
||||||
|
- 0011 (nomad/schizo icons already correct in 0004)
|
||||||
|
- 0012 (no PENTACLES seeded for Earthman in 0004)
|
||||||
|
- 0013 (nomad icon already fa-hat-cowboy-side in 0004)
|
||||||
|
"""
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
# ── Schizo energies + operations ─────────────────────────────────────────────
|
||||||
|
CR = '<span class="card-ref">{}</span>'
|
||||||
|
|
||||||
|
SCHIZO_ENERGIES = [
|
||||||
|
{"type": "LIBIDO", "effect": f'When encountering territorial Libido, may convert Emanation into {CR.format("1. The Priest")}.'},
|
||||||
|
{"type": "NUMEN", "effect": f'When encountering despotic Numen, may convert Emanation into {CR.format("1. The Powerful")}.'},
|
||||||
|
{"type": "VOLUPTAS", "effect": f'When encountering axiomatic Voluptas, may convert Emanation into {CR.format("1. The Normal")}.'},
|
||||||
|
{"type": "VOLUPTAS", "effect": f'When encountering annihilating Voluptas, may convert Emanation into {CR.format("1. The Surrendered")}.'},
|
||||||
|
]
|
||||||
|
|
||||||
|
SCHIZO_OPERATIONS = [
|
||||||
|
{"type": "COVER", "effect": f'When covering {CR.format("2. The Occultist")} she may choose, by converting her own Reversal into {CR.format("2. Pestilence")}, to convert this Reversal into {CR.format("1. The Pervert")}.'},
|
||||||
|
{"type": "CROWN", "effect": f'When crowning {CR.format("3. The Despot")} she may choose, by converting her own Reversal into {CR.format("3. War")}, to convert this Reversal into {CR.format("1. The Paranoiac")}.'},
|
||||||
|
{"type": "BEHIND", "effect": f'When behind {CR.format("4. The Capitalist")} he may choose, by converting his own Reversal into {CR.format("4. Famine")}, to convert this Reversal into {CR.format("1. The Neurotic")}.'},
|
||||||
|
{"type": "BEFORE", "effect": f'When before {CR.format("5. The Fascist")} he may choose, by converting his own Reversal into {CR.format("5. Death")}, to convert this Reversal into {CR.format("1. The Suicidal")}.'},
|
||||||
|
]
|
||||||
|
|
||||||
|
# ── Middle court suit reversals ──────────────────────────────────────────────
|
||||||
|
SUIT_REVERSAL_QUALIFIER = {
|
||||||
|
"BRANDS": "Seething",
|
||||||
|
"GRAILS": "Gloomy",
|
||||||
|
"BLADES": "Nervous",
|
||||||
|
"CROWNS": "Vacant",
|
||||||
|
}
|
||||||
|
COURT_NUMBERS = [11, 12, 13, 14]
|
||||||
|
|
||||||
|
# ── Castanedan Virtues ───────────────────────────────────────────────────────
|
||||||
|
IMPLICIT_VIRTUES = [
|
||||||
|
# (number, levity_qualifier, gravity_qualifier, reversal_title)
|
||||||
|
(6, "Sublimating", "Sedimentary", "Indulged Folly"),
|
||||||
|
(7, "Sublimating", "Sedimentary", "Indulgent Doing"),
|
||||||
|
(8, "Sublimating", "Sedimentary", "Self-Indulgence"),
|
||||||
|
(9, "Sublimating", "Sedimentary", "Indulging Personal History"),
|
||||||
|
]
|
||||||
|
|
||||||
|
EXPLICIT_VIRTUES = [
|
||||||
|
# (number, levity_emanation, gravity_emanation, levity_reversal, gravity_reversal, italic_word)
|
||||||
|
(19, "The Hunter's Stalking", "The Hunter's Stalking", "The Sleeper's Stalking", "The Quarry's Stalking", "Stalking"),
|
||||||
|
(20, "The Dreamer's Dreaming", "The Dreamer's Dreaming", "The Sleeper's Dreaming", "The Dreamed's Dreaming", "Dreaming"),
|
||||||
|
(21, "The Warrior's Intent", "The Warrior's Intent", "The Sleeper's Intent", "The Predator's Intent", "Intent"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def finalize(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
|
||||||
|
|
||||||
|
# Middle court suit reversals
|
||||||
|
for suit, qualifier in SUIT_REVERSAL_QUALIFIER.items():
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MIDDLE", suit=suit,
|
||||||
|
number__in=COURT_NUMBERS,
|
||||||
|
).update(reversal_qualifier=qualifier)
|
||||||
|
|
||||||
|
# Schizo: clear stray reversal_qualifier (0004 seeds 'Territoriality') +
|
||||||
|
# populate energies/operations
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=1,
|
||||||
|
).update(
|
||||||
|
reversal_qualifier="",
|
||||||
|
energies=SCHIZO_ENERGIES,
|
||||||
|
operations=SCHIZO_OPERATIONS,
|
||||||
|
)
|
||||||
|
|
||||||
|
# fa-hand-dots fallback for empty MAJOR icons (number ≥ 2)
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number__gte=2, icon="",
|
||||||
|
).update(icon="fa-hand-dots")
|
||||||
|
|
||||||
|
# Card 49 polarity reversal titles
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=49,
|
||||||
|
).update(
|
||||||
|
levity_reversal="The Vibrational Mould of Man",
|
||||||
|
gravity_reversal="The Bestowing Eagle",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Castanedan Virtues — implicit (trumps 6-9): trump 7 name canonicalize +
|
||||||
|
# qualifiers + reversal titles
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=7,
|
||||||
|
).update(name="Not-Doing")
|
||||||
|
for number, lvty, grav, rev in IMPLICIT_VIRTUES:
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=number,
|
||||||
|
).update(
|
||||||
|
levity_qualifier=lvty,
|
||||||
|
gravity_qualifier=grav,
|
||||||
|
levity_reversal=rev,
|
||||||
|
gravity_reversal=rev,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Castanedan Virtues — explicit (trumps 19-21): polarity-split titles +
|
||||||
|
# italic_word for the agency stem
|
||||||
|
for number, le, ge, lr, gr, word in EXPLICIT_VIRTUES:
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=number,
|
||||||
|
).update(
|
||||||
|
levity_emanation=le,
|
||||||
|
gravity_emanation=ge,
|
||||||
|
levity_reversal=lr,
|
||||||
|
gravity_reversal=gr,
|
||||||
|
italic_word=word,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Trump 8: "Losing Self-Importance" → "Self‑Unimportance" w/ U+2011
|
||||||
|
# non-breaking hyphen (keeps title on one line above qualifier)
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=8,
|
||||||
|
).update(name="Self‑Unimportance", slug="self-unimportance")
|
||||||
|
|
||||||
|
# Trump 9: insert U+00A0 between "Personal" and "History" so they wrap as
|
||||||
|
# a single unit ("Erasing / Personal History, / Sublimating")
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=9,
|
||||||
|
).update(name="Erasing Personal History")
|
||||||
|
|
||||||
|
# Pip cards (number 1-10) → MINOR arcana; courts (11-14) stay MIDDLE
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MIDDLE", number__lte=10,
|
||||||
|
).update(arcana="MINOR")
|
||||||
|
|
||||||
|
|
||||||
|
def revert(apps, schema_editor):
|
||||||
|
"""Reverse just enough to restore 0006 schema state. Data reverts to the
|
||||||
|
raw 0004 seed shape (without any of the post-seed tweaks)."""
|
||||||
|
TarotCard = apps.get_model("epic", "TarotCard")
|
||||||
|
DeckVariant = apps.get_model("epic", "DeckVariant")
|
||||||
|
try:
|
||||||
|
earthman = DeckVariant.objects.get(slug="earthman")
|
||||||
|
except DeckVariant.DoesNotExist:
|
||||||
|
return
|
||||||
|
# Pip arcana
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MINOR", number__lte=10,
|
||||||
|
).update(arcana="MIDDLE")
|
||||||
|
# Trump 8 + 9 names back
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=8,
|
||||||
|
).update(name="Losing Self-Importance", slug="losing-self-importance")
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=9,
|
||||||
|
).update(name="Erasing Personal History")
|
||||||
|
# Trump 7 name back
|
||||||
|
TarotCard.objects.filter(
|
||||||
|
deck_variant=earthman, arcana="MAJOR", number=7,
|
||||||
|
).update(name="Not Doing")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("epic", "0006_add_deck_variant_to_tableseat"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField("TarotCard", "mechanisms", "energies"),
|
||||||
|
migrations.RenameField("TarotCard", "articulations", "operations"),
|
||||||
|
migrations.RenameField("TarotCard", "reversal", "reversal_qualifier"),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="tarotcard",
|
||||||
|
name="italic_word",
|
||||||
|
field=models.CharField(blank=True, default="", max_length=50),
|
||||||
|
),
|
||||||
|
migrations.RunPython(finalize, reverse_code=revert),
|
||||||
|
]
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
"""Populate TarotCard.reversal for Earthman Middle Arcana court cards.
|
|
||||||
|
|
||||||
Each suit has a fixed reversal qualifier that replaces the polarity qualifier
|
|
||||||
(Elevated/Graven) when the card is spun to its reversed face:
|
|
||||||
Brands → Seething Grails → Gloomy Blades → Nervous Crowns → Vacant
|
|
||||||
|
|
||||||
Also clears the incorrectly inherited reversal on The Schizo (card 1), which
|
|
||||||
mistakenly carried 'Territoriality' from The Occultist (card 2).
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
SUIT_REVERSAL_QUALIFIER = {
|
|
||||||
"BRANDS": "Seething",
|
|
||||||
"GRAILS": "Gloomy",
|
|
||||||
"BLADES": "Nervous",
|
|
||||||
"CROWNS": "Vacant",
|
|
||||||
}
|
|
||||||
|
|
||||||
RANK_NAMES = {11: "Maid", 12: "Jack", 13: "Queen", 14: "King"}
|
|
||||||
|
|
||||||
|
|
||||||
def populate_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
|
|
||||||
|
|
||||||
# Middle Arcana court cards
|
|
||||||
for suit, qualifier in SUIT_REVERSAL_QUALIFIER.items():
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman,
|
|
||||||
arcana="MIDDLE",
|
|
||||||
suit=suit,
|
|
||||||
number__in=list(RANK_NAMES.keys()),
|
|
||||||
).update(reversal=qualifier)
|
|
||||||
|
|
||||||
# Clear The Schizo's incorrectly inherited reversal (belongs to The Occultist)
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman,
|
|
||||||
arcana="MAJOR",
|
|
||||||
number=1,
|
|
||||||
).update(reversal="")
|
|
||||||
|
|
||||||
|
|
||||||
def clear_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
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MIDDLE",
|
|
||||||
suit__in=list(SUIT_REVERSAL_QUALIFIER.keys()),
|
|
||||||
number__in=list(RANK_NAMES.keys()),
|
|
||||||
).update(reversal="")
|
|
||||||
TarotCard.objects.filter(deck_variant=earthman, arcana="MAJOR", number=1).update(
|
|
||||||
reversal="Territoriality"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0006_add_deck_variant_to_tableseat"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(populate_reversals, reverse_code=clear_reversals),
|
|
||||||
]
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
"""Rename mechanisms→energies and articulations→operations on TarotCard;
|
|
||||||
seed The Schizo (Earthman major arcana card 1) with Energy and Operation entries.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
CR = '<span class="card-ref">{}</span>'
|
|
||||||
|
|
||||||
SCHIZO_ENERGIES = [
|
|
||||||
{"type": "LIBIDO", "effect": f'When encountering territorial Libido, may convert Emanation into {CR.format("1. The Priest")}.'},
|
|
||||||
{"type": "NUMEN", "effect": f'When encountering despotic Numen, may convert Emanation into {CR.format("1. The Powerful")}.'},
|
|
||||||
{"type": "VOLUPTAS", "effect": f'When encountering axiomatic Voluptas, may convert Emanation into {CR.format("1. The Normal")}.'},
|
|
||||||
{"type": "VOLUPTAS", "effect": f'When encountering annihilating Voluptas, may convert Emanation into {CR.format("1. The Surrendered")}.'},
|
|
||||||
]
|
|
||||||
|
|
||||||
SCHIZO_OPERATIONS = [
|
|
||||||
{"type": "COVER", "effect": f'When covering {CR.format("2. The Occultist")} she may choose, by converting her own Reversal into {CR.format("2. Pestilence")}, to convert this Reversal into {CR.format("1. The Pervert")}.'},
|
|
||||||
{"type": "CROWN", "effect": f'When crowning {CR.format("3. The Despot")} she may choose, by converting her own Reversal into {CR.format("3. War")}, to convert this Reversal into {CR.format("1. The Paranoiac")}.'},
|
|
||||||
{"type": "BEHIND", "effect": f'When behind {CR.format("4. The Capitalist")} he may choose, by converting his own Reversal into {CR.format("4. Famine")}, to convert this Reversal into {CR.format("1. The Neurotic")}.'},
|
|
||||||
{"type": "BEFORE", "effect": f'When before {CR.format("5. The Fascist")} he may choose, by converting his own Reversal into {CR.format("5. Death")}, to convert this Reversal into {CR.format("1. The Suicidal")}.'},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def seed_schizo(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, arcana="MAJOR", number=1,
|
|
||||||
).update(energies=SCHIZO_ENERGIES, operations=SCHIZO_OPERATIONS)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_schizo(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, arcana="MAJOR", number=1,
|
|
||||||
).update(energies=[], operations=[])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0007_populate_middle_arcana_reversals"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField("TarotCard", "mechanisms", "energies"),
|
|
||||||
migrations.RenameField("TarotCard", "articulations", "operations"),
|
|
||||||
migrations.RunPython(seed_schizo, reverse_code=clear_schizo),
|
|
||||||
]
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
"""Re-seed The Schizo's energies and operations with .card-ref HTML spans."""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
CR = '<span class="card-ref">{}</span>'
|
|
||||||
|
|
||||||
SCHIZO_ENERGIES = [
|
|
||||||
{"type": "LIBIDO", "effect": f'When encountering territorial Libido, may convert Emanation into {CR.format("1. The Priest")}.'},
|
|
||||||
{"type": "NUMEN", "effect": f'When encountering despotic Numen, may convert Emanation into {CR.format("1. The Powerful")}.'},
|
|
||||||
{"type": "VOLUPTAS", "effect": f'When encountering axiomatic Voluptas, may convert Emanation into {CR.format("1. The Normal")}.'},
|
|
||||||
{"type": "VOLUPTAS", "effect": f'When encountering annihilating Voluptas, may convert Emanation into {CR.format("1. The Surrendered")}.'},
|
|
||||||
]
|
|
||||||
|
|
||||||
SCHIZO_OPERATIONS = [
|
|
||||||
{"type": "COVER", "effect": f'When covering {CR.format("2. The Occultist")} she may choose, by converting her own Reversal into {CR.format("2. Pestilence")}, to convert this Reversal into {CR.format("1. The Pervert")}.'},
|
|
||||||
{"type": "CROWN", "effect": f'When crowning {CR.format("3. The Despot")} she may choose, by converting her own Reversal into {CR.format("3. War")}, to convert this Reversal into {CR.format("1. The Paranoiac")}.'},
|
|
||||||
{"type": "BEHIND", "effect": f'When behind {CR.format("4. The Capitalist")} he may choose, by converting his own Reversal into {CR.format("4. Famine")}, to convert this Reversal into {CR.format("1. The Neurotic")}.'},
|
|
||||||
{"type": "BEFORE", "effect": f'When before {CR.format("5. The Fascist")} he may choose, by converting his own Reversal into {CR.format("5. Death")}, to convert this Reversal into {CR.format("1. The Suicidal")}.'},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def seed_schizo(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, arcana="MAJOR", number=1,
|
|
||||||
).update(energies=SCHIZO_ENERGIES, operations=SCHIZO_OPERATIONS)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_schizo(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, arcana="MAJOR", number=1,
|
|
||||||
).update(energies=[], operations=[])
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0008_rename_energies_operations_seed_schizo"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(seed_schizo, reverse_code=clear_schizo),
|
|
||||||
]
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
"""Assign fa-hand-dots icon to all Earthman Major Arcana cards with number >= 2.
|
|
||||||
|
|
||||||
Cards 0 (The Nomad) and 1 (The Schizo) keep their existing icon value so they
|
|
||||||
can receive distinct icons later. All other Major Arcana groups (Popes, Implicit
|
|
||||||
Virtues, Elements, Realms, Explicit Virtues, Zodiac, Lunars, Planets, Inner Rings,
|
|
||||||
polarity-split finals) default to fa-hand-dots until per-group icons are assigned.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def assign_hand_dots(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,
|
|
||||||
arcana="MAJOR",
|
|
||||||
number__gte=2,
|
|
||||||
icon="",
|
|
||||||
).update(icon="fa-hand-dots")
|
|
||||||
|
|
||||||
|
|
||||||
def clear_hand_dots(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,
|
|
||||||
arcana="MAJOR",
|
|
||||||
number__gte=2,
|
|
||||||
icon="fa-hand-dots",
|
|
||||||
).update(icon="")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0009_schizo_card_ref_spans"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(assign_hand_dots, reverse_code=clear_hand_dots),
|
|
||||||
]
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
"""Assign individual icons to The Nomad (0) and The Schizo (1).
|
|
||||||
|
|
||||||
All other Major Arcana already have fa-hand-dots from migration 0010.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
ICONS = {0: 'fa-hat-cowboy-side', 1: 'fa-hat-wizard'}
|
|
||||||
|
|
||||||
|
|
||||||
def assign_icons(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 number, icon in ICONS.items():
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=number
|
|
||||||
).update(icon=icon)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_icons(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, arcana="MAJOR", number__in=list(ICONS.keys())
|
|
||||||
).update(icon="")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0010_major_arcana_hand_dots_icon"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(assign_icons, reverse_code=clear_icons),
|
|
||||||
]
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
"""Delete 4 stray PENTACLES court cards from the Earthman deck.
|
|
||||||
|
|
||||||
These survived the migration collapse; the Earthman deck uses
|
|
||||||
BRANDS/GRAILS/BLADES/CROWNS only.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def delete_pentacles(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, suit="PENTACLES").delete()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0011_nomad_schizo_icons"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(delete_pentacles, reverse_code=migrations.RunPython.noop),
|
|
||||||
]
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
"""Fix The Nomad icon: fa-hat-cowboy → fa-hat-cowboy-side."""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def fix_nomad_icon(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, arcana="MAJOR", number=0, icon="fa-hat-cowboy"
|
|
||||||
).update(icon="fa-hat-cowboy-side")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0012_delete_stray_pentacles"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(fix_nomad_icon, reverse_code=migrations.RunPython.noop),
|
|
||||||
]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
"""Rename TarotCard.reversal → TarotCard.reversal_qualifier.
|
|
||||||
|
|
||||||
Symmetric naming with levity_qualifier / gravity_qualifier; disambiguates the
|
|
||||||
qualifier-text field from the reversal *axis* state and the keywords_reversed
|
|
||||||
list. Pure column rename — no data movement.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0013_fix_nomad_icon"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name="tarotcard",
|
|
||||||
old_name="reversal",
|
|
||||||
new_name="reversal_qualifier",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
"""Populate card 49's polarity-split reversal titles.
|
|
||||||
|
|
||||||
The Earthman deck's last two cards (48–49) carry distinct titles per polarity
|
|
||||||
(stored in `levity_emanation` / `gravity_emanation` / `levity_reversal` /
|
|
||||||
`gravity_reversal`) rather than a shared title + qualifier.
|
|
||||||
|
|
||||||
Card 48 had its full set seeded in migration 0004:
|
|
||||||
levity: Father Sky → reversal: The Storm
|
|
||||||
gravity: Mother Sea → reversal: The Flood
|
|
||||||
|
|
||||||
Card 49 had only emanations seeded; this migration fills the reversals:
|
|
||||||
levity: The Effulgent Mould of Man → reversal: The Vibrational Mould of Man
|
|
||||||
gravity: The Devouring Eagle → reversal: The All-Bestowing Eagle
|
|
||||||
|
|
||||||
The "qualifier" (Effulgent / Vibrational / Devouring / All-Bestowing) is baked
|
|
||||||
into the title between "The" and the title-proper rather than rendered as a
|
|
||||||
separate qualifier slot — the per-polarity title strings are stored verbatim.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def populate_card49_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
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=49,
|
|
||||||
).update(
|
|
||||||
levity_reversal="The Vibrational Mould of Man",
|
|
||||||
gravity_reversal="The All-Bestowing Eagle",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_card49_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
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=49,
|
|
||||||
).update(
|
|
||||||
levity_reversal="",
|
|
||||||
gravity_reversal="",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0014_rename_reversal_to_reversal_qualifier"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(populate_card49_reversals, reverse_code=clear_card49_reversals),
|
|
||||||
]
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
"""Tweak card 49 gravity_reversal: 'All-Bestowing Eagle' → 'Bestowing Eagle'."""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def forward(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, arcana="MAJOR", number=49,
|
|
||||||
).update(gravity_reversal="The Bestowing Eagle")
|
|
||||||
|
|
||||||
|
|
||||||
def reverse(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, arcana="MAJOR", number=49,
|
|
||||||
).update(gravity_reversal="The All-Bestowing Eagle")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0015_card49_polarity_reversal_titles"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward, reverse_code=reverse),
|
|
||||||
]
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
"""Populate the seven Castanedan Virtues — trumps 6–9 (Implicit) + 19–21 (Explicit).
|
|
||||||
|
|
||||||
Implicit Virtues (6–9): emanation qualifier differs by polarity (Sublimating /
|
|
||||||
Sedimentary), name is shared. Reversal is a single full string shared across
|
|
||||||
both polarities (the agency word — Controlled / Not / Losing / Erasing —
|
|
||||||
flips to Indulged / Indulgent / Self-Indulgence / Indulging). We fill the
|
|
||||||
standard `levity_qualifier` / `gravity_qualifier` slots so the major-arcana
|
|
||||||
upright renders "Controlled Folly,\nSublimating" via the existing template
|
|
||||||
branch; we fill BOTH `levity_reversal` + `gravity_reversal` with the same
|
|
||||||
string so a FLIP'd reversal still picks up the override (an empty side falls
|
|
||||||
through to the default major-arcana rendering).
|
|
||||||
|
|
||||||
Explicit Virtues (19–21): emanation is shared across polarities (e.g. "The
|
|
||||||
Hunter's Stalking" — no qualifier + stem decomposition), reversal differs by
|
|
||||||
polarity. All four polarity-split title fields filled.
|
|
||||||
|
|
||||||
Also canonicalizes trump 7's name from "Not Doing" to "Not-Doing" per the spec
|
|
||||||
doc (slug "not-doing" already correct).
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
IMPLICIT = [
|
|
||||||
# (number, levity_qualifier, gravity_qualifier, reversal_title)
|
|
||||||
(6, "Sublimating", "Sedimentary", "Indulged Folly"),
|
|
||||||
(7, "Sublimating", "Sedimentary", "Indulgent Doing"),
|
|
||||||
(8, "Sublimating", "Sedimentary", "Self-Indulgence"),
|
|
||||||
(9, "Sublimating", "Sedimentary", "Indulging Personal History"),
|
|
||||||
]
|
|
||||||
|
|
||||||
EXPLICIT = [
|
|
||||||
# (number, levity_emanation, gravity_emanation, levity_reversal, gravity_reversal)
|
|
||||||
(19, "The Hunter's Stalking", "The Hunter's Stalking", "The Sleeper's Stalking", "The Quarry's Stalking"),
|
|
||||||
(20, "The Dreamer's Dreaming", "The Dreamer's Dreaming", "The Sleeper's Dreaming", "The Dreamed's Dreaming"),
|
|
||||||
(21, "The Warrior's Intent", "The Warrior's Intent", "The Sleeper's Intent", "The Predator's Intent"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def forward(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
|
|
||||||
|
|
||||||
# Trump 7 name canonicalization
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=7,
|
|
||||||
).update(name="Not-Doing")
|
|
||||||
|
|
||||||
for number, lvty, grav, rev in IMPLICIT:
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=number,
|
|
||||||
).update(
|
|
||||||
levity_qualifier=lvty,
|
|
||||||
gravity_qualifier=grav,
|
|
||||||
levity_reversal=rev,
|
|
||||||
gravity_reversal=rev,
|
|
||||||
)
|
|
||||||
|
|
||||||
for number, le, ge, lr, gr in EXPLICIT:
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=number,
|
|
||||||
).update(
|
|
||||||
levity_emanation=le,
|
|
||||||
gravity_emanation=ge,
|
|
||||||
levity_reversal=lr,
|
|
||||||
gravity_reversal=gr,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def reverse(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, arcana="MAJOR", number=7,
|
|
||||||
).update(name="Not Doing")
|
|
||||||
|
|
||||||
for number, _lvty, _grav, _rev in IMPLICIT:
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=number,
|
|
||||||
).update(
|
|
||||||
levity_qualifier="",
|
|
||||||
gravity_qualifier="",
|
|
||||||
levity_reversal="",
|
|
||||||
gravity_reversal="",
|
|
||||||
)
|
|
||||||
|
|
||||||
for number, _le, _ge, _lr, _gr in EXPLICIT:
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=number,
|
|
||||||
).update(
|
|
||||||
levity_emanation="",
|
|
||||||
gravity_emanation="",
|
|
||||||
levity_reversal="",
|
|
||||||
gravity_reversal="",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0016_card49_bestowing_eagle"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward, reverse_code=reverse),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 6.0 on 2026-05-01 03:28
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('epic', '0017_castanedan_virtues'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='tarotcard',
|
|
||||||
name='italic_word',
|
|
||||||
field=models.CharField(blank=True, default='', max_length=50),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
"""Set TarotCard.italic_word for trumps 19-21 (Stalking / Dreaming / Intent).
|
|
||||||
|
|
||||||
Each of these three Castanedan virtues has its title key-word italicized
|
|
||||||
across every emanation/reversal slot ("The Hunter's *Stalking*", "The
|
|
||||||
Sleeper's *Stalking*", etc.). Storing the word in a single field lets the
|
|
||||||
renderer wrap it in <em> at display time without HTML in the data.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
WORDS = {
|
|
||||||
19: "Stalking",
|
|
||||||
20: "Dreaming",
|
|
||||||
21: "Intent",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def forward(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 number, word in WORDS.items():
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=number,
|
|
||||||
).update(italic_word=word)
|
|
||||||
|
|
||||||
|
|
||||||
def reverse(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, arcana="MAJOR", number__in=list(WORDS),
|
|
||||||
).update(italic_word="")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0018_add_italic_word"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward, reverse_code=reverse),
|
|
||||||
]
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
"""Trump 8 rename: Losing Self-Importance → Self-Unimportance.
|
|
||||||
|
|
||||||
The renamed form fits on one fan-card line above the Sublimating/Sedimentary
|
|
||||||
qualifier without a scaleX squeeze.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def forward(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, arcana="MAJOR", number=8,
|
|
||||||
).update(name="Self-Unimportance", slug="self-unimportance")
|
|
||||||
|
|
||||||
|
|
||||||
def reverse(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, arcana="MAJOR", number=8,
|
|
||||||
).update(name="Losing Self-Importance", slug="losing-self-importance")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0019_explicit_virtues_italic_word"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward, reverse_code=reverse),
|
|
||||||
]
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
"""Long-title wrap fixes for trumps 8 and 9.
|
|
||||||
|
|
||||||
Trump 8 "Self-Unimportance" → swap the hyphen for U+2011 (non-breaking
|
|
||||||
hyphen) so it stays glued and the title sits on one line above
|
|
||||||
Sublimating / Sedimentary.
|
|
||||||
|
|
||||||
Trump 9 "Erasing Personal History" → insert U+00A0 (non-breaking space)
|
|
||||||
between "Personal" and "History" so the browser keeps them together,
|
|
||||||
forcing "Erasing" alone on line 1 and "Personal History," on line 2.
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
# Trump 8
|
|
||||||
OLD_8 = "Self-Unimportance"
|
|
||||||
NEW_8 = "Self‑Unimportance"
|
|
||||||
|
|
||||||
# Trump 9
|
|
||||||
OLD_9 = "Erasing Personal History"
|
|
||||||
NEW_9 = "Erasing Personal History"
|
|
||||||
|
|
||||||
|
|
||||||
def forward(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, arcana="MAJOR", number=8,
|
|
||||||
).update(name=NEW_8)
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=9,
|
|
||||||
).update(name=NEW_9)
|
|
||||||
|
|
||||||
|
|
||||||
def reverse(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, arcana="MAJOR", number=8,
|
|
||||||
).update(name=OLD_8)
|
|
||||||
TarotCard.objects.filter(
|
|
||||||
deck_variant=earthman, arcana="MAJOR", number=9,
|
|
||||||
).update(name=OLD_9)
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0020_self_unimportance"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward, reverse_code=reverse),
|
|
||||||
]
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
"""Reclassify Earthman pip cards (number 1-10) from MIDDLE to MINOR arcana.
|
|
||||||
|
|
||||||
The 0004 reseed initially lumped pips + court cards under MIDDLE; pips
|
|
||||||
should be MINOR arcana, with MIDDLE reserved for the Earthman court
|
|
||||||
cards (Maid/Jack/Queen/King at numbers 11-14).
|
|
||||||
"""
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
def forward(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, arcana="MIDDLE", number__lte=10,
|
|
||||||
).update(arcana="MINOR")
|
|
||||||
|
|
||||||
|
|
||||||
def reverse(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, arcana="MINOR", number__lte=10,
|
|
||||||
).update(arcana="MIDDLE")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("epic", "0021_trump9_nbsp"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward, reverse_code=reverse),
|
|
||||||
]
|
|
||||||
Reference in New Issue
Block a user