collapse migrations: 41 epic + 20 lyric + 12 applets + others → fresh initials + 4 themed seeds
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

- Delete all incremental migration files across all apps; regenerate 0001_initial.py
  per app via makemigrations (schema unchanged, no model edits)
- applets/0003_seed_applets.py: all 20 Applet rows in one migration
- epic/0003_seed_fiorentine_deck.py: Fiorentine Minchiate DeckVariant + 78 cards
- epic/0004_seed_earthman_deck.py: Earthman DeckVariant + 50 major + 56 minor/middle
  arcana (106 cards); stray PENTACLES courts absent — clean from the start
- epic/0005_seed_astro_reference_tables.py: 12 signs, 10 planets, 9 aspect types
  (incl. Semisquare + Sesquiquadrate), 12 house labels
- 706 ITs green on fresh DB

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-27 21:06:23 -04:00
parent e2c9dc4e8a
commit 2b4f20c0e8
91 changed files with 916 additions and 3540 deletions

View File

@@ -1,8 +1,7 @@
# Generated by Django 6.0 on 2026-03-12 19:46
# Generated by Django 6.0 on 2026-04-28 00:59
import django.db.models.deletion
import datetime
import uuid
from django.conf import settings
from django.db import migrations, models
@@ -11,22 +10,50 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Room',
name='AspectType',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=200)),
('visibility', models.CharField(choices=[('PRIVATE', 'Private'), ('PUBLIC', 'Public'), ('INVITE ONLY', 'Invite Only')], default='PRIVATE', max_length=20)),
('gate_status', models.CharField(choices=[('GATHERING', 'Gathering'), ('OPEN', 'Open'), ('RENEWAL_DUE', 'Renewal Due')], default='GATHERING', max_length=20)),
('renewal_period', models.DurationField(blank=True, null=True)),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('symbol', models.CharField(max_length=5)),
('angle', models.PositiveSmallIntegerField()),
('orb', models.FloatField()),
],
options={
'ordering': ['angle'],
},
),
migrations.CreateModel(
name='Character',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('birth_dt', models.DateTimeField(blank=True, null=True)),
('birth_lat', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
('birth_lon', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
('birth_place', models.CharField(blank=True, max_length=200)),
('house_system', models.CharField(choices=[('O', 'Porphyry'), ('P', 'Placidus'), ('K', 'Koch'), ('W', 'Whole Sign')], default='O', max_length=1)),
('chart_data', models.JSONField(blank=True, null=True)),
('celtic_cross', models.JSONField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('board_state', models.JSONField(default=dict)),
('seed_count', models.IntegerField(default=12)),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owned_rooms', to=settings.AUTH_USER_MODEL)),
('confirmed_at', models.DateTimeField(blank=True, null=True)),
('retired_at', models.DateTimeField(blank=True, null=True)),
],
options={
'ordering': ['-created_at'],
},
),
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.CreateModel(
@@ -37,9 +64,129 @@ class Migration(migrations.Migration):
('status', models.CharField(choices=[('EMPTY', 'Empty'), ('RESERVED', 'Reserved'), ('FILLED', 'Filled')], default='EMPTY', max_length=10)),
('reserved_at', models.DateTimeField(blank=True, null=True)),
('filled_at', models.DateTimeField(blank=True, null=True)),
('funded_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='funded_slots', to=settings.AUTH_USER_MODEL)),
('gamer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='gate_slots', to=settings.AUTH_USER_MODEL)),
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gate_slots', to='epic.room')),
('debited_token_type', models.CharField(blank=True, max_length=8, null=True)),
('debited_token_expires_at', models.DateTimeField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='HouseLabel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.PositiveSmallIntegerField(unique=True)),
('name', models.CharField(max_length=30)),
('keywords', models.CharField(blank=True, max_length=100)),
],
options={
'ordering': ['number'],
},
),
migrations.CreateModel(
name='Planet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('symbol', models.CharField(max_length=5)),
('order', models.PositiveSmallIntegerField(unique=True)),
],
options={
'ordering': ['order'],
},
),
migrations.CreateModel(
name='Room',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=200)),
('visibility', models.CharField(choices=[('PRIVATE', 'Private'), ('PUBLIC', 'Public'), ('INVITE ONLY', 'Invite Only')], default='PRIVATE', max_length=20)),
('gate_status', models.CharField(choices=[('GATHERING', 'GATHERING GAMERS'), ('OPEN', 'Open'), ('RENEWAL_DUE', 'Renewal Due')], default='GATHERING', max_length=20)),
('table_status', models.CharField(blank=True, choices=[('ROLE_SELECT', 'Role Select'), ('SIG_SELECT', 'Significator Select'), ('SKY_SELECT', 'Sky Select'), ('IN_GAME', 'In Game')], max_length=20, null=True)),
('sig_select_started_at', models.DateTimeField(blank=True, null=True)),
('renewal_period', models.DurationField(blank=True, default=datetime.timedelta(days=7), null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('board_state', models.JSONField(default=dict)),
('seed_count', models.IntegerField(default=12)),
],
),
migrations.CreateModel(
name='RoomInvite',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('invitee_email', models.EmailField(max_length=254)),
('status', models.CharField(choices=[('PENDING', 'Pending'), ('ACCEPTED', 'Accepted'), ('DECLINED', 'Declined')], default='PENDING', max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='Sign',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('symbol', models.CharField(max_length=5)),
('element', models.CharField(choices=[('Fire', 'Fire'), ('Earth', 'Earth'), ('Air', 'Air'), ('Water', 'Water')], max_length=5)),
('modality', models.CharField(choices=[('Cardinal', 'Cardinal'), ('Fixed', 'Fixed'), ('Mutable', 'Mutable')], max_length=8)),
('order', models.PositiveSmallIntegerField(unique=True)),
('start_degree', models.FloatField()),
],
options={
'ordering': ['order'],
},
),
migrations.CreateModel(
name='SigReservation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(max_length=2)),
('polarity', models.CharField(choices=[('levity', 'Levity'), ('gravity', 'Gravity')], max_length=7)),
('reserved_at', models.DateTimeField(auto_now_add=True)),
('ready', models.BooleanField(default=False)),
('countdown_remaining', models.IntegerField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='TableSeat',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slot_number', models.IntegerField()),
('role', models.CharField(blank=True, choices=[('PC', 'Player'), ('BC', 'Builder'), ('SC', 'Shepherd'), ('AC', 'Alchemist'), ('NC', 'Narrator'), ('EC', 'Economist')], max_length=2, null=True)),
('role_revealed', models.BooleanField(default=False)),
('seat_position', models.IntegerField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='TarotCard',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('arcana', models.CharField(choices=[('MAJOR', 'Major Arcana'), ('MINOR', 'Minor Arcana'), ('MIDDLE', 'Middle Arcana')], max_length=6)),
('suit', models.CharField(blank=True, choices=[('WANDS', 'Wands'), ('CUPS', 'Cups'), ('SWORDS', 'Swords'), ('PENTACLES', 'Pentacles'), ('CROWNS', 'Crowns'), ('BRANDS', 'Brands'), ('GRAILS', 'Grails'), ('BLADES', 'Blades')], max_length=10, null=True)),
('icon', models.CharField(blank=True, default='', max_length=50)),
('number', models.IntegerField()),
('slug', models.SlugField(max_length=120)),
('correspondence', models.CharField(blank=True, max_length=200)),
('group', models.CharField(blank=True, max_length=100)),
('reversal', models.CharField(blank=True, default='', max_length=200)),
('levity_qualifier', models.CharField(blank=True, default='', max_length=100)),
('gravity_qualifier', models.CharField(blank=True, default='', max_length=100)),
('levity_emanation', models.CharField(blank=True, default='', max_length=200)),
('gravity_emanation', models.CharField(blank=True, default='', max_length=200)),
('levity_reversal', models.CharField(blank=True, default='', max_length=200)),
('gravity_reversal', models.CharField(blank=True, default='', max_length=200)),
('mechanisms', models.JSONField(default=list)),
('articulations', models.JSONField(default=list)),
('keywords_upright', models.JSONField(default=list)),
('keywords_reversed', models.JSONField(default=list)),
('cautions', models.JSONField(default=list)),
],
options={
'ordering': ['deck_variant', '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)),
],
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 6.0 on 2026-03-13 20:32
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='room',
name='renewal_period',
field=models.DurationField(blank=True, default=datetime.timedelta(days=7), null=True),
),
]

View File

@@ -0,0 +1,120 @@
# Generated by Django 6.0 on 2026-04-28 00:59
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('epic', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='gateslot',
name='funded_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='funded_slots', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='gateslot',
name='gamer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='gate_slots', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='room',
name='owner',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owned_rooms', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='gateslot',
name='room',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='gate_slots', to='epic.room'),
),
migrations.AddField(
model_name='roominvite',
name='inviter',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_invites', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='roominvite',
name='room',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='epic.room'),
),
migrations.AddField(
model_name='sigreservation',
name='gamer',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sig_reservations', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='sigreservation',
name='room',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sig_reservations', to='epic.room'),
),
migrations.AddField(
model_name='tableseat',
name='gamer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='table_seats', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='tableseat',
name='room',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='table_seats', to='epic.room'),
),
migrations.AddField(
model_name='sigreservation',
name='seat',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sig_reservation', to='epic.tableseat'),
),
migrations.AddField(
model_name='character',
name='seat',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='characters', to='epic.tableseat'),
),
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='tableseat',
name='significator',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='significator_seats', to='epic.tarotcard'),
),
migrations.AddField(
model_name='sigreservation',
name='card',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sig_reservations', to='epic.tarotcard'),
),
migrations.AddField(
model_name='character',
name='significator',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='character_significators', to='epic.tarotcard'),
),
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.AddField(
model_name='tarotdeck',
name='room',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='tarot_deck', to='epic.room'),
),
migrations.AlterUniqueTogether(
name='tarotcard',
unique_together={('deck_variant', 'slug')},
),
migrations.AddConstraint(
model_name='sigreservation',
constraint=models.UniqueConstraint(fields=('room', 'gamer'), name='one_sig_reservation_per_gamer_per_room'),
),
migrations.AddConstraint(
model_name='sigreservation',
constraint=models.UniqueConstraint(fields=('room', 'card', 'polarity'), name='one_reservation_per_card_per_polarity_per_room'),
),
]

View File

@@ -1,27 +0,0 @@
# Generated by Django 6.0 on 2026-03-13 22:19
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0002_alter_room_renewal_period'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='RoomInvite',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('invitee_email', models.EmailField(max_length=254)),
('status', models.CharField(choices=[('PENDING', 'Pending'), ('ACCEPTED', 'Accepted'), ('DECLINED', 'Declined')], default='PENDING', max_length=10)),
('created_at', models.DateTimeField(auto_now_add=True)),
('inviter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_invites', to=settings.AUTH_USER_MODEL)),
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='epic.room')),
],
),
]

View File

@@ -0,0 +1,121 @@
"""Seed the Fiorentine Minchiate deck (78 cards)."""
from django.db import migrations
# (arcana, suit, number, name, slug, correspondence, group, icon, keywords_upright, keywords_reversed)
FIORENTINE_CARDS = [
("MAJOR","",0,"The Fool","the-fool","","","",["beginnings","spontaneity","freedom"],["recklessness","naivety","risk"]),
("MAJOR","",1,"The Magician","the-magician","","","",["willpower","skill","resourcefulness"],["manipulation","untapped potential","deceit"]),
("MAJOR","",2,"The High Priestess","the-high-priestess","","","",["intuition","mystery","inner knowledge"],["secrets","disconnection","withdrawal"]),
("MAJOR","",3,"The Empress","the-empress","","","",["fertility","abundance","nurturing"],["dependence","smothering","creative block"]),
("MAJOR","",4,"The Emperor","the-emperor","","","",["authority","structure","stability"],["rigidity","domination","inflexibility"]),
("MAJOR","",5,"The Hierophant","the-hierophant","","","",["tradition","conformity","institutions"],["rebellion","unconventionality","challenge"]),
("MAJOR","",6,"The Lovers","the-lovers","","","",["love","harmony","choice"],["disharmony","imbalance","misalignment"]),
("MAJOR","",7,"The Chariot","the-chariot","","","",["control","willpower","victory"],["aggression","lack of direction","defeat"]),
("MAJOR","",8,"Strength","strength","","","",["courage","patience","compassion"],["self-doubt","weakness","insecurity"]),
("MAJOR","",9,"The Hermit","the-hermit","","","",["introspection","guidance","solitude"],["isolation","loneliness","withdrawal"]),
("MAJOR","",10,"Wheel of Fortune","wheel-of-fortune","","","",["change","cycles","fate"],["bad luck","resistance","clinging to control"]),
("MAJOR","",11,"Justice","justice","","","",["fairness","truth","cause and effect"],["injustice","dishonesty","avoidance"]),
("MAJOR","",12,"The Hanged Man","the-hanged-man","","","",["pause","surrender","new perspective"],["stalling","resistance","indecision"]),
("MAJOR","",13,"Death","death","","","",["endings","transition","transformation"],["fear of change","stagnation","resistance"]),
("MAJOR","",14,"Temperance","temperance","","","",["balance","patience","moderation"],["imbalance","excess","lack of harmony"]),
("MAJOR","",15,"The Devil","the-devil","","","",["bondage","materialism","shadow self"],["detachment","freedom","releasing control"]),
("MAJOR","",16,"The Tower","the-tower","","","",["sudden change","upheaval","revelation"],["avoidance","fear of change","delaying disaster"]),
("MAJOR","",17,"The Star","the-star","","","",["hope","renewal","inspiration"],["despair","insecurity","hopelessness"]),
("MAJOR","",18,"The Moon","the-moon","","","",["illusion","fear","the unconscious"],["confusion","misinterpretation","clarity"]),
("MAJOR","",19,"The Sun","the-sun","","","",["positivity","success","vitality"],["negativity","depression","sadness"]),
("MAJOR","",20,"Judgement","judgement","","","",["reflection","reckoning","absolution"],["self-doubt","lack of self-awareness","loathing"]),
("MAJOR","",21,"The World","the-world","","","",["completion","integration","accomplishment"],["incompletion","no closure","shortcuts"]),
("MINOR","CUPS",1,"Ace of Cups","ace-of-cups","","","",["new feelings","intuition","opportunity"],["blocked creativity","emptiness","hesitation"]),
("MINOR","CUPS",2,"Two of Cups","two-of-cups","","","",["partnership","unity","celebration"],["imbalance","broken bonds","misalignment"]),
("MINOR","CUPS",3,"Three of Cups","three-of-cups","","","",["creativity","community","abundance"],["independence","isolation","looking inward"]),
("MINOR","CUPS",4,"Four of Cups","four-of-cups","","","",["contemplation","apathy","reevaluation"],["withdrawal","boredom","seeking motivation"]),
("MINOR","CUPS",5,"Five of Cups","five-of-cups","","","",["loss","grief","disappointment"],["acceptance","moving on","forgiveness"]),
("MINOR","CUPS",6,"Six of Cups","six-of-cups","","","",["nostalgia","reunion","joy"],["living in the past","naivety","unrealistic"]),
("MINOR","CUPS",7,"Seven of Cups","seven-of-cups","","","",["illusion","fantasy","wishful thinking"],["alignment","clarity","sobriety"]),
("MINOR","CUPS",8,"Eight of Cups","eight-of-cups","","","",["disappointment","abandonment","walking away"],["hopelessness","aimlessness","stagnation"]),
("MINOR","CUPS",9,"Nine of Cups","nine-of-cups","","","",["contentment","fulfilment","satisfaction"],["inner happiness","materialism","indulgence"]),
("MINOR","CUPS",10,"Ten of Cups","ten-of-cups","","","",["divine love","bliss","fulfilment"],["inner happiness","alignment","personal values"]),
("MINOR","CUPS",11,"Page of Cups","page-of-cups","","","",["sensitivity","creativity","intuition"],["insecurity","emotional immaturity","creative blocks"]),
("MINOR","CUPS",12,"Knight of Cups","knight-of-cups","","","",["compassion","romanticism","diplomacy"],["moodiness","emotional manipulation","deception"]),
("MINOR","CUPS",13,"Queen of Cups","queen-of-cups","","","",["compassion","empathy","nurturing"],["emotional insecurity","over-giving","neglect"]),
("MINOR","CUPS",14,"King of Cups","king-of-cups","","","",["emotional maturity","diplomacy","wisdom"],["manipulation","moodiness","coldness"]),
("MINOR","PENTACLES",1,"Ace of Pentacles","ace-of-pentacles","","","",["opportunity","new venture","manifestation"],["lost opportunity","lack of planning","scarcity"]),
("MINOR","PENTACLES",2,"Two of Pentacles","two-of-pentacles","","","",["juggling resources","flexibility","fun"],["imbalance","disorganisation","overwhelm"]),
("MINOR","PENTACLES",3,"Three of Pentacles","three-of-pentacles","","","",["teamwork","building","apprenticeship"],["lack of teamwork","disharmony","misalignment"]),
("MINOR","PENTACLES",4,"Four of Pentacles","four-of-pentacles","","","",["stability","security","conservation"],["greed","stinginess","possessiveness"]),
("MINOR","PENTACLES",5,"Five of Pentacles","five-of-pentacles","","","",["isolation","insecurity","worry"],["recovery from loss","overcoming hardship"]),
("MINOR","PENTACLES",6,"Six of Pentacles","six-of-pentacles","","","",["generosity","charity","community"],["strings attached","power dynamics","inequality"]),
("MINOR","PENTACLES",7,"Seven of Pentacles","seven-of-pentacles","","","",["hard work","perseverance","diligence"],["lack of reward","laziness","low quality"]),
("MINOR","PENTACLES",8,"Eight of Pentacles","eight-of-pentacles","","","",["apprenticeship","education","skill"],["perfectionism","misdirected activity","misuse"]),
("MINOR","PENTACLES",9,"Nine of Pentacles","nine-of-pentacles","","","",["abundance","luxury","self-sufficiency"],["overindulgence","superficiality","materialism"]),
("MINOR","PENTACLES",10,"Ten of Pentacles","ten-of-pentacles","","","",["wealth","financial security","achievement"],["financial failure","greed","lost success"]),
("MINOR","PENTACLES",11,"Page of Pentacles","page-of-pentacles","","","",["ambition","diligence","management"],["underhandedness","greediness","unethical"]),
("MINOR","PENTACLES",12,"Knight of Pentacles","knight-of-pentacles","","","",["hard work","productivity","routine"],["laziness","obsession with work","burnout"]),
("MINOR","PENTACLES",13,"Queen of Pentacles","queen-of-pentacles","","","",["nurturing","practical","abundance"],["financial dependence","smothering","insecurity"]),
("MINOR","PENTACLES",14,"King of Pentacles","king-of-pentacles","","","",["abundance","prosperity","security"],["greed","indulgence","sensual obsession"]),
("MINOR","SWORDS",1,"Ace of Swords","ace-of-swords","","","",["raw power","breakthrough","clarity"],["confusion","brutality","mental chaos"]),
("MINOR","SWORDS",2,"Two of Swords","two-of-swords","","","",["difficult choices","stalemate","truce"],["indecision","lies","confusion"]),
("MINOR","SWORDS",3,"Three of Swords","three-of-swords","","","",["heartbreak","sorrow","grief"],["recovery","forgiveness","moving on"]),
("MINOR","SWORDS",4,"Four of Swords","four-of-swords","","","",["rest","restoration","retreat"],["restlessness","burnout","illness"]),
("MINOR","SWORDS",5,"Five of Swords","five-of-swords","","","",["defeat","change","transition"],["resistance to change","inability to move"]),
("MINOR","SWORDS",6,"Six of Swords","six-of-swords","","","",["victory","success","ambition"],["an eye for an eye","dishonour","manipulation"]),
("MINOR","SWORDS",7,"Seven of Swords","seven-of-swords","","","",["deception","trickery","tactics"],["imposter syndrome","coming clean","rethinking"]),
("MINOR","SWORDS",8,"Eight of Swords","eight-of-swords","","","",["restriction","isolation","imprisonment"],["self-limiting beliefs","inner critic","opening up"]),
("MINOR","SWORDS",9,"Nine of Swords","nine-of-swords","","","",["anxiety","worry","fear"],["recovery from anxiety","inner turmoil","secrets"]),
("MINOR","SWORDS",10,"Ten of Swords","ten-of-swords","","","",["ruin","painful endings","loss"],["recovery","regeneration","resisting an end"]),
("MINOR","SWORDS",11,"Page of Swords","page-of-swords","","","",["new ideas","mental agility","curiosity"],["manipulation","all talk no action","ruthlessness"]),
("MINOR","SWORDS",12,"Knight of Swords","knight-of-swords","","","",["action","impulsiveness","ambition"],["no direction","disregard for consequences"]),
("MINOR","SWORDS",13,"Queen of Swords","queen-of-swords","","","",["clarity","directness","structure"],["coldness","cruelty","manipulation"]),
("MINOR","SWORDS",14,"King of Swords","king-of-swords","","","",["mental clarity","truth","authority"],["abuse of power","manipulation","coldness"]),
("MINOR","WANDS",1,"Ace of Wands","ace-of-wands","","","",["inspiration","new venture","spark"],["delays","lack of motivation","false start"]),
("MINOR","WANDS",2,"Two of Wands","two-of-wands","","","",["planning","progress","decisions"],["impatience","lack of planning","hesitation"]),
("MINOR","WANDS",3,"Three of Wands","three-of-wands","","","",["expansion","foresight","enterprise"],["obstacles","lack of foresight","delays"]),
("MINOR","WANDS",4,"Four of Wands","four-of-wands","","","",["celebration","harmony","homecoming"],["lack of support","transience","home conflicts"]),
("MINOR","WANDS",5,"Five of Wands","five-of-wands","","","",["conflict","competition","tension"],["avoiding conflict","compromise","truce"]),
("MINOR","WANDS",6,"Six of Wands","six-of-wands","","","",["victory","recognition","progress"],["excess pride","lack of recognition","fall"]),
("MINOR","WANDS",7,"Seven of Wands","seven-of-wands","","","",["challenge","courage","competition"],["anxiety","giving up","overwhelmed"]),
("MINOR","WANDS",8,"Eight of Wands","eight-of-wands","","","",["rapid action","adventure","change"],["haste","scattered energy","delays"]),
("MINOR","WANDS",9,"Nine of Wands","nine-of-wands","","","",["resilience","persistence","last stand"],["exhaustion","giving up","surrender"]),
("MINOR","WANDS",10,"Ten of Wands","ten-of-wands","","","",["completion","celebration","travel"],["burdens","oppression","carrying too much"]),
("MINOR","WANDS",11,"Page of Wands","page-of-wands","","","",["exploration","enthusiasm","adventure"],["hasty decisions","scattered energy","immaturity"]),
("MINOR","WANDS",12,"Knight of Wands","knight-of-wands","","","",["energy","passion","adventure"],["scattered energy","frustration","aggression"]),
("MINOR","WANDS",13,"Queen of Wands","queen-of-wands","","","",["confidence","independence","courage"],["selfishness","jealousy","insecurity"]),
("MINOR","WANDS",14,"King of Wands","king-of-wands","","","",["big picture","leadership","vision"],["impulsiveness","haste","overconfidence"]),
]
def seed(apps, schema_editor):
DeckVariant = apps.get_model('epic', 'DeckVariant')
TarotCard = apps.get_model('epic', 'TarotCard')
deck = DeckVariant.objects.create(
name='Fiorentine Minchiate',
slug='fiorentine-minchiate',
card_count=78,
is_default=False,
)
for arcana, suit, number, name, slug, correspondence, group, icon, kw_up, kw_rev in FIORENTINE_CARDS:
TarotCard.objects.create(
deck_variant=deck,
arcana=arcana,
suit=suit or None,
number=number,
name=name,
slug=slug,
correspondence=correspondence,
group=group,
icon=icon,
keywords_upright=kw_up,
keywords_reversed=kw_rev,
)
class Migration(migrations.Migration):
dependencies = [
('epic', '0002_initial'),
]
operations = [
migrations.RunPython(seed, migrations.RunPython.noop),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 6.0 on 2026-03-15 00:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0003_roominvite'),
]
operations = [
migrations.AlterField(
model_name='room',
name='gate_status',
field=models.CharField(choices=[('GATHERING', 'GATHERING GAMERS'), ('OPEN', 'Open'), ('RENEWAL_DUE', 'Renewal Due')], default='GATHERING', max_length=20),
),
]

View File

@@ -0,0 +1,184 @@
"""Seed the Earthman deck: DeckVariant + 50 major + 56 minor/middle arcana (106 cards total)."""
from django.db import migrations
# Major arcana
# (number, name, slug, group, correspondence, icon,
# reversal, levity_qualifier, gravity_qualifier,
# levity_emanation, gravity_emanation, levity_reversal, gravity_reversal,
# mechanisms, articulations, keywords_upright, keywords_reversed, cautions)
EM_MAJOR = [
(0,"The Nomad","the-nomad","","The Fool / Il Matto","fa-hat-cowboy-side","","","","","","","",[], [],["beginnings","spontaneity","freedom"],["recklessness","naivety","risk"],[]),
(1,"The Schizo","the-schizo","Pope/Horseman","Magician","fa-hat-wizard","Territoriality","Enlightened","Engraven","","","","",[], [],["willpower","skill","resourcefulness"],["manipulation","untapped potential","deceit"],["This card will reverse into <span class=\"card-ref\">I. The Pervert</span> when it comes under dominion of <span class=\"card-ref\">II. The Occultist</span>, which in turn reverses into <span class=\"card-ref\">II. Pestilence</span>.","This card will reverse into <span class=\"card-ref\">I. The Paranoiac</span> when it comes under dominion of <span class=\"card-ref\">III. The Despot</span>, which in turn reverses into <span class=\"card-ref\">III. War</span>.","This card will reverse into <span class=\"card-ref\">I. The Neurotic</span> when it comes under dominion of <span class=\"card-ref\">IV. The Capitalist</span>, which in turn reverses into <span class=\"card-ref\">IV. Famine</span>.","This card will reverse into <span class=\"card-ref\">I. The Suicidal</span> when it comes under dominion of <span class=\"card-ref\">V. The Fascist</span>, which in turn reverses into <span class=\"card-ref\">V. Death</span>."]),
(2,"The Occultist","the-occultist","Pope/Horseman","High Priestess","fa-hand-dots","Territoriality","Enlightened","Engraven","","","","",[], [],["intuition","mystery","inner knowledge"],["secrets","disconnection","withdrawal"],[]),
(3,"The Despot","the-despot","Pope/Horseman","Empress","fa-hand-dots","Despotism","Enlightened","Engraven","","","","",[], [],["fertility","abundance","nurturing"],["dependence","smothering","creative block"],[]),
(4,"The Capitalist","the-capitalist","Pope/Horseman","Emperor","fa-hand-dots","Capitalism","Enlightened","Engraven","","","","",[], [],["authority","structure","stability"],["rigidity","domination","inflexibility"],[]),
(5,"The Fascist","the-fascist","Pope/Horseman","Hierophant","fa-hand-dots","Fascism","Enlightened","Engraven","","","","",[], [],["tradition","conformity","institutions"],["rebellion","unconventionality","challenge"],[]),
(6,"Controlled Folly","controlled-folly","Implicit Virtues","Fortitude","fa-hand-dots","","","","","","","",[], [],["courage","patience","compassion"],["self-doubt","weakness","insecurity"],[]),
(7,"Not Doing","not-doing","Implicit Virtues","Temperance","fa-hand-dots","","","","","","","",[], [],["fairness","truth","cause and effect"],["injustice","dishonesty","avoidance"],[]),
(8,"Losing Self-Importance","losing-self-importance","Implicit Virtues","Justice","fa-hand-dots","","","","","","","",[], [],["balance","patience","moderation"],["imbalance","excess","lack of harmony"],[]),
(9,"Erasing Personal History","erasing-personal-history","Implicit Virtues","Prudence","fa-hand-dots","","","","","","","",[], [],[],[],[]),
(10,"Space","space-em","Elements","Chariot","fa-hand-dots","Nexus","Kinetic","Potential","","","","",[], [],[],[],[]),
(11,"Time","time-em","Elements","Hermit","fa-hand-dots","Tempo","Kinetic","Potential","","","","",[], [],[],[],[]),
(12,"Stone","stone-em","Elements","Earth","fa-hand-dots","Ossum","Kinetic","Potential","","","","",[], [],[],[],[]),
(13,"Fire","fire-em","Elements","Fire","fa-hand-dots","Ardor","Kinetic","Potential","","","","",[], [],[],[],[]),
(14,"Water","water-em","Elements","Water","fa-hand-dots","Humor","Kinetic","Potential","","","","",[], [],[],[],[]),
(15,"Air","air-em","Elements","Air","fa-hand-dots","Pneuma","Kinetic","Potential","","","","",[], [],[],[],[]),
(16,"Disco Inferno","disco-inferno","Realms","Devil","fa-hand-dots","Shame","Deasil","Widdershins","","","","",[], [],["sudden change","upheaval","revelation"],["avoidance","fear of change","delaying disaster"],[]),
(17,"Torre Terrestre","torre-terrestre","Realms","Tower","fa-hand-dots","Guilt","Deasil","Widdershins","","","","",[], [],[],[],[]),
(18,"Fantasia Celestia","fantasia-celestia","Realms","Wheel of Fortune","fa-hand-dots","Anxiety","Deasil","Widdershins","","","","",[], [],[],[],[]),
(19,"Stalking","stalking","Explicit Virtues","Charity","fa-hand-dots","","","","","","","",[], [],["love","harmony","choice"],["disharmony","imbalance","misalignment"],[]),
(20,"Dreaming","dreaming","Explicit Virtues","Faith","fa-hand-dots","","","","","","","",[], [],[],[],[]),
(21,"Intent","intent","Explicit Virtues","Hope","fa-hand-dots","","","","","","","",[], [],[],[],[]),
(22,"Aries","aries","Zodiac Signs & Houses","Aries","fa-hand-dots","House of Self","Precessional","Recessional","","","","",[], [],[],[],[]),
(23,"Taurus","taurus","Zodiac Signs & Houses","Taurus","fa-hand-dots","House of Worth","Precessional","Recessional","","","","",[], [],[],[],[]),
(24,"Gemini","gemini","Zodiac Signs & Houses","Gemini","fa-hand-dots","House of Education","Precessional","Recessional","","","","",[], [],[],[],[]),
(25,"Cancer","cancer","Zodiac Signs & Houses","Cancer","fa-hand-dots","House of Family","Precessional","Recessional","","","","",[], [],[],[],[]),
(26,"Leo","leo","Zodiac Signs & Houses","Leo","fa-hand-dots","House of Creation","Precessional","Recessional","","","","",[], [],[],[],[]),
(27,"Virgo","virgo","Zodiac Signs & Houses","Virgo","fa-hand-dots","House of Ritual","Precessional","Recessional","","","","",[], [],[],[],[]),
(28,"Libra","libra","Zodiac Signs & Houses","Libra","fa-hand-dots","House of Cooperation","Precessional","Recessional","","","","",[], [],[],[],[]),
(29,"Scorpio","scorpio","Zodiac Signs & Houses","Scorpio","fa-hand-dots","House of Regeneration","Precessional","Recessional","","","","",[], [],[],[],[]),
(30,"Sagittarius","sagittarius","Zodiac Signs & Houses","Sagittarius","fa-hand-dots","House of Enterprise","Precessional","Recessional","","","","",[], [],[],[],[]),
(31,"Capricorn","capricorn","Zodiac Signs & Houses","Capricorn","fa-hand-dots","House of Career","Precessional","Recessional","","","","",[], [],[],[],[]),
(32,"Aquarius","aquarius","Zodiac Signs & Houses","Aquarius","fa-hand-dots","House of Reward","Precessional","Recessional","","","","",[], [],[],[],[]),
(33,"Pisces","pisces","Zodiac Signs & Houses","Pisces","fa-hand-dots","House of Reprisal","Precessional","Recessional","","","","",[], [],[],[],[]),
(34,"Animal Powers","animal-powers","Lunars","Hanged Man","fa-hand-dots","Patrilineage","Centrifugal","Centripetal","","","","",[], [],["pause","surrender","new perspective"],["stalling","resistance","indecision"],[]),
(35,"Seeded Earth","seeded-earth","Lunars","Death","fa-hand-dots","Matrilineage","Centrifugal","Centripetal","","","","",[], [],["endings","transition","transformation"],["fear of change","stagnation","resistance"],[]),
(36,"The Twins of Pluto","twins-of-pluto","Lunars","Pluto","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(37,"Neptune","neptune","Planets","Neptune","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(38,"Uranus","uranus","Planets","Uranus","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(39,"Saturn","saturn","Planets","Saturn","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(40,"Jupiter","jupiter","Planets","Jupiter","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(41,"The Asteroid Belt","the-asteroid-belt","Planets","","","Ouroboros","Prograde","Retrograde","","","","",[], [],[],[],[]),
(42,"Mars","mars","Planets","Mars","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(43,"Venus","venus","Planets","Venus","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(44,"Mercury","mercury","Planets","Mercury","fa-hand-dots","","Prograde","Retrograde","","","","",[], [],[],[],[]),
(45,"The Polestar","the-polestar","Inner Rings","Star","fa-hand-dots","","Precessional","Recessional","","","","",[], [],["hope","renewal","inspiration"],["despair","insecurity","hopelessness"],[]),
(46,"The Antichthon","the-antichthon","Inner Rings","Moon","fa-hand-dots","","Waxing","Waning","","","","",[], [],["illusion","fear","the unconscious"],["confusion","misinterpretation","clarity"],[]),
(47,"The Corestar","the-corestar","Inner Rings","Sun","fa-hand-dots","","Inclining","Declining","","","","",[], [],["positivity","success","vitality"],["negativity","depression","sadness"],[]),
(48,"Father Sky / Mother Sea","father-sky-mother-sea","","World","fa-hand-dots","","","","Father Sky","Mother Sea","The Storm","The Flood",[], [],["completion","integration","accomplishment"],["incompletion","no closure","shortcuts"],[]),
(49,"The Effulgent Mould of Man / The Devouring Eagle","effulgent-mould-devouring-eagle","","Trumpets","fa-hand-dots","","","","The Effulgent Mould of Man","The Devouring Eagle","","",[], [],["reflection","reckoning","absolution"],["self-doubt","lack of self-awareness","loathing"],[]),
]
# Minor/Middle arcana
# (arcana, suit, number, name, slug, keywords_upright, keywords_reversed)
EM_MINOR = [
("MINOR","BRANDS",1,"Ace of Brands","ace-of-wands-em",["inspiration","new venture","spark"],["delays","lack of motivation","false start"]),
("MINOR","BRANDS",2,"Two of Brands","two-of-wands-em",["planning","progress","decisions"],["impatience","lack of planning","hesitation"]),
("MINOR","BRANDS",3,"Three of Brands","three-of-wands-em",["expansion","foresight","enterprise"],["obstacles","lack of foresight","delays"]),
("MINOR","BRANDS",4,"Four of Brands","four-of-wands-em",["celebration","harmony","homecoming"],["lack of support","transience","home conflicts"]),
("MINOR","BRANDS",5,"Five of Brands","five-of-wands-em",["conflict","competition","tension"],["avoiding conflict","compromise","truce"]),
("MINOR","BRANDS",6,"Six of Brands","six-of-wands-em",["victory","recognition","progress"],["excess pride","lack of recognition","fall"]),
("MINOR","BRANDS",7,"Seven of Brands","seven-of-wands-em",["challenge","courage","competition"],["anxiety","giving up","overwhelmed"]),
("MINOR","BRANDS",8,"Eight of Brands","eight-of-wands-em",["rapid action","adventure","change"],["haste","scattered energy","delays"]),
("MINOR","BRANDS",9,"Nine of Brands","nine-of-wands-em",["resilience","persistence","last stand"],["exhaustion","giving up","surrender"]),
("MINOR","BRANDS",10,"Ten of Brands","ten-of-wands-em",["completion","celebration","travel"],["burdens","oppression","carrying too much"]),
("MIDDLE","BRANDS",11,"Maid of Brands","maid-of-wands-em",["exploration","enthusiasm","adventure"],["hasty decisions","scattered energy","immaturity"]),
("MIDDLE","BRANDS",12,"Jack of Brands","jack-of-wands-em",["energy","passion","adventure"],["scattered energy","frustration","aggression"]),
("MIDDLE","BRANDS",13,"Queen of Brands","queen-of-wands-em",["confidence","independence","courage"],["selfishness","jealousy","insecurity"]),
("MIDDLE","BRANDS",14,"King of Brands","king-of-wands-em",["big picture","leadership","vision"],["impulsiveness","haste","overconfidence"]),
("MINOR","GRAILS",1,"Ace of Grails","ace-of-cups-em",["new feelings","intuition","opportunity"],["blocked creativity","emptiness","hesitation"]),
("MINOR","GRAILS",2,"Two of Grails","two-of-cups-em",["partnership","unity","celebration"],["imbalance","broken bonds","misalignment"]),
("MINOR","GRAILS",3,"Three of Grails","three-of-cups-em",["creativity","community","abundance"],["independence","isolation","looking inward"]),
("MINOR","GRAILS",4,"Four of Grails","four-of-cups-em",["contemplation","apathy","reevaluation"],["withdrawal","boredom","seeking motivation"]),
("MINOR","GRAILS",5,"Five of Grails","five-of-cups-em",["loss","grief","disappointment"],["acceptance","moving on","forgiveness"]),
("MINOR","GRAILS",6,"Six of Grails","six-of-cups-em",["nostalgia","reunion","joy"],["living in the past","naivety","unrealistic"]),
("MINOR","GRAILS",7,"Seven of Grails","seven-of-cups-em",["illusion","fantasy","wishful thinking"],["alignment","clarity","sobriety"]),
("MINOR","GRAILS",8,"Eight of Grails","eight-of-cups-em",["disappointment","abandonment","walking away"],["hopelessness","aimlessness","stagnation"]),
("MINOR","GRAILS",9,"Nine of Grails","nine-of-cups-em",["contentment","fulfilment","satisfaction"],["inner happiness","materialism","indulgence"]),
("MINOR","GRAILS",10,"Ten of Grails","ten-of-cups-em",["divine love","bliss","fulfilment"],["inner happiness","alignment","personal values"]),
("MIDDLE","GRAILS",11,"Maid of Grails","maid-of-cups-em",["sensitivity","creativity","intuition"],["insecurity","emotional immaturity","creative blocks"]),
("MIDDLE","GRAILS",12,"Jack of Grails","jack-of-cups-em",["compassion","romanticism","diplomacy"],["moodiness","emotional manipulation","deception"]),
("MIDDLE","GRAILS",13,"Queen of Grails","queen-of-cups-em",["compassion","empathy","nurturing"],["emotional insecurity","over-giving","neglect"]),
("MIDDLE","GRAILS",14,"King of Grails","king-of-cups-em",["emotional maturity","diplomacy","wisdom"],["manipulation","moodiness","coldness"]),
("MINOR","BLADES",1,"Ace of Blades","ace-of-swords-em",["raw power","breakthrough","clarity"],["confusion","brutality","mental chaos"]),
("MINOR","BLADES",2,"Two of Blades","two-of-swords-em",["difficult choices","stalemate","truce"],["indecision","lies","confusion"]),
("MINOR","BLADES",3,"Three of Blades","three-of-swords-em",["heartbreak","sorrow","grief"],["recovery","forgiveness","moving on"]),
("MINOR","BLADES",4,"Four of Blades","four-of-swords-em",["rest","restoration","retreat"],["restlessness","burnout","illness"]),
("MINOR","BLADES",5,"Five of Blades","five-of-swords-em",["defeat","change","transition"],["resistance to change","inability to move"]),
("MINOR","BLADES",6,"Six of Blades","six-of-swords-em",["victory","success","ambition"],["an eye for an eye","dishonour","manipulation"]),
("MINOR","BLADES",7,"Seven of Blades","seven-of-swords-em",["deception","trickery","tactics"],["imposter syndrome","coming clean","rethinking"]),
("MINOR","BLADES",8,"Eight of Blades","eight-of-swords-em",["restriction","isolation","imprisonment"],["self-limiting beliefs","inner critic","opening up"]),
("MINOR","BLADES",9,"Nine of Blades","nine-of-swords-em",["anxiety","worry","fear"],["recovery from anxiety","inner turmoil","secrets"]),
("MINOR","BLADES",10,"Ten of Blades","ten-of-swords-em",["ruin","painful endings","loss"],["recovery","regeneration","resisting an end"]),
("MIDDLE","BLADES",11,"Maid of Blades","maid-of-swords-em",["new ideas","mental agility","curiosity"],["manipulation","all talk no action","ruthlessness"]),
("MIDDLE","BLADES",12,"Jack of Blades","jack-of-swords-em",["action","impulsiveness","ambition"],["no direction","disregard for consequences"]),
("MIDDLE","BLADES",13,"Queen of Blades","queen-of-swords-em",["clarity","directness","structure"],["coldness","cruelty","manipulation"]),
("MIDDLE","BLADES",14,"King of Blades","king-of-swords-em",["mental clarity","truth","authority"],["abuse of power","manipulation","coldness"]),
("MINOR","CROWNS",1,"Ace of Crowns","ace-of-crowns-em",["opportunity","new venture","manifestation"],["lost opportunity","lack of planning","scarcity"]),
("MINOR","CROWNS",2,"Two of Crowns","two-of-crowns-em",["juggling resources","flexibility","fun"],["imbalance","disorganisation","overwhelm"]),
("MINOR","CROWNS",3,"Three of Crowns","three-of-crowns-em",["teamwork","building","apprenticeship"],["lack of teamwork","disharmony","misalignment"]),
("MINOR","CROWNS",4,"Four of Crowns","four-of-crowns-em",["stability","security","conservation"],["greed","stinginess","possessiveness"]),
("MINOR","CROWNS",5,"Five of Crowns","five-of-crowns-em",["isolation","insecurity","worry"],["recovery from loss","overcoming hardship"]),
("MINOR","CROWNS",6,"Six of Crowns","six-of-crowns-em",["generosity","charity","community"],["strings attached","power dynamics","inequality"]),
("MINOR","CROWNS",7,"Seven of Crowns","seven-of-crowns-em",["hard work","perseverance","diligence"],["lack of reward","laziness","low quality"]),
("MINOR","CROWNS",8,"Eight of Crowns","eight-of-crowns-em",["apprenticeship","education","skill"],["perfectionism","misdirected activity","misuse"]),
("MINOR","CROWNS",9,"Nine of Crowns","nine-of-crowns-em",["abundance","luxury","self-sufficiency"],["overindulgence","superficiality","materialism"]),
("MINOR","CROWNS",10,"Ten of Crowns","ten-of-crowns-em",["wealth","financial security","achievement"],["financial failure","greed","lost success"]),
("MIDDLE","CROWNS",11,"Maid of Crowns","maid-of-crowns-em",["ambition","diligence","management"],["underhandedness","greediness","unethical"]),
("MIDDLE","CROWNS",12,"Jack of Crowns","jack-of-crowns-em",["hard work","productivity","routine"],["laziness","obsession with work","burnout"]),
("MIDDLE","CROWNS",13,"Queen of Crowns","queen-of-crowns-em",["nurturing","practical","abundance"],["financial dependence","smothering","insecurity"]),
("MIDDLE","CROWNS",14,"King of Crowns","king-of-crowns-em",["abundance","prosperity","security"],["greed","indulgence","sensual obsession"]),
]
def seed(apps, schema_editor):
DeckVariant = apps.get_model('epic', 'DeckVariant')
TarotCard = apps.get_model('epic', 'TarotCard')
deck = DeckVariant.objects.create(
name='Earthman',
slug='earthman',
card_count=106,
is_default=True,
)
for (number, name, slug, group, correspondence, icon,
reversal, levity_qualifier, gravity_qualifier,
levity_emanation, gravity_emanation, levity_reversal, gravity_reversal,
mechanisms, articulations, kw_up, kw_rev, cautions) in EM_MAJOR:
TarotCard.objects.create(
deck_variant=deck,
arcana='MAJOR',
suit=None,
number=number,
name=name,
slug=slug,
group=group,
correspondence=correspondence,
icon=icon,
reversal=reversal,
levity_qualifier=levity_qualifier,
gravity_qualifier=gravity_qualifier,
levity_emanation=levity_emanation,
gravity_emanation=gravity_emanation,
levity_reversal=levity_reversal,
gravity_reversal=gravity_reversal,
mechanisms=mechanisms,
articulations=articulations,
keywords_upright=kw_up,
keywords_reversed=kw_rev,
cautions=cautions,
)
for arcana, suit, number, name, slug, kw_up, kw_rev in EM_MINOR:
TarotCard.objects.create(
deck_variant=deck,
arcana=arcana,
suit=suit,
number=number,
name=name,
slug=slug,
keywords_upright=kw_up,
keywords_reversed=kw_rev,
)
class Migration(migrations.Migration):
dependencies = [
('epic', '0003_seed_fiorentine_deck'),
]
operations = [
migrations.RunPython(seed, migrations.RunPython.noop),
]

View File

@@ -1,21 +0,0 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0004_alter_room_gate_status'),
]
operations = [
migrations.AddField(
model_name='gateslot',
name='debited_token_type',
field=models.CharField(max_length=8, null=True, blank=True),
),
migrations.AddField(
model_name='gateslot',
name='debited_token_expires_at',
field=models.DateTimeField(null=True, blank=True),
),
]

View File

@@ -0,0 +1,92 @@
"""Seed Sign, Planet, AspectType, and HouseLabel reference tables."""
from django.db import migrations
# (order, name, symbol, element, modality, start_degree)
SIGNS = [
(0, 'Aries', '', 'Fire', 'Cardinal', 0.0),
(1, 'Taurus', '', 'Earth', 'Fixed', 30.0),
(2, 'Gemini', '', 'Air', 'Mutable', 60.0),
(3, 'Cancer', '', 'Water', 'Cardinal', 90.0),
(4, 'Leo', '', 'Fire', 'Fixed', 120.0),
(5, 'Virgo', '', 'Earth', 'Mutable', 150.0),
(6, 'Libra', '', 'Air', 'Cardinal', 180.0),
(7, 'Scorpio', '', 'Water', 'Fixed', 210.0),
(8, 'Sagittarius', '', 'Fire', 'Mutable', 240.0),
(9, 'Capricorn', '', 'Earth', 'Cardinal', 270.0),
(10, 'Aquarius', '', 'Air', 'Fixed', 300.0),
(11, 'Pisces', '', 'Water', 'Mutable', 330.0),
]
# (order, name, symbol)
PLANETS = [
(0, 'Sun', ''),
(1, 'Moon', ''),
(2, 'Mercury', ''),
(3, 'Venus', ''),
(4, 'Mars', ''),
(5, 'Jupiter', ''),
(6, 'Saturn', ''),
(7, 'Uranus', ''),
(8, 'Neptune', ''),
(9, 'Pluto', ''),
]
# (name, symbol, angle, orb) — mirrors ASPECTS in pyswiss/apps/charts/calc.py
ASPECT_TYPES = [
('Conjunction', '', 0, 8.0),
('Semisextile', '', 30, 4.0),
('Semisquare', '', 45, 4.0),
('Sextile', '', 60, 6.0),
('Square', '', 90, 8.0),
('Trine', '', 120, 8.0),
('Sesquiquadrate', '', 135, 4.0),
('Quincunx', '', 150, 5.0),
('Opposition', '', 180, 10.0),
]
# (number, name, keywords)
HOUSE_LABELS = [
(1, 'Self', 'identity, appearance, first impressions'),
(2, 'Worth', 'possessions, values, finances'),
(3, 'Education', 'communication, siblings, short journeys'),
(4, 'Family', 'home, roots, ancestry'),
(5, 'Creation', 'creativity, romance, children, pleasure'),
(6, 'Ritual', 'service, health, daily routines'),
(7, 'Cooperation', 'partnerships, marriage, open enemies'),
(8, 'Regeneration', 'transformation, shared resources, death'),
(9, 'Enterprise', 'philosophy, travel, higher learning'),
(10, 'Career', 'public life, reputation, authority'),
(11, 'Reward', 'friends, groups, aspirations'),
(12, 'Reprisal', 'hidden matters, karma, self-undoing'),
]
def seed(apps, schema_editor):
Sign = apps.get_model('epic', 'Sign')
Planet = apps.get_model('epic', 'Planet')
AspectType = apps.get_model('epic', 'AspectType')
HouseLabel = apps.get_model('epic', 'HouseLabel')
for order, name, symbol, element, modality, start_degree in SIGNS:
Sign.objects.create(order=order, name=name, symbol=symbol,
element=element, modality=modality, start_degree=start_degree)
for order, name, symbol in PLANETS:
Planet.objects.create(order=order, name=name, symbol=symbol)
for name, symbol, angle, orb in ASPECT_TYPES:
AspectType.objects.create(name=name, symbol=symbol, angle=angle, orb=orb)
for number, name, keywords in HOUSE_LABELS:
HouseLabel.objects.create(number=number, name=name, keywords=keywords)
class Migration(migrations.Migration):
dependencies = [
('epic', '0004_seed_earthman_deck'),
]
operations = [
migrations.RunPython(seed, migrations.RunPython.noop),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 6.0 on 2026-03-17 00:14
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0005_gateslot_debited_token_fields'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='room',
name='table_status',
field=models.CharField(blank=True, choices=[('ROLE_SELECT', 'Role Select'), ('SIG_SELECT', 'Significator Select'), ('IN_GAME', 'In Game')], max_length=20, null=True),
),
migrations.CreateModel(
name='TableSeat',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slot_number', models.IntegerField()),
('role', models.CharField(blank=True, choices=[('PC', 'Player'), ('BC', 'Builder'), ('SC', 'Shepherd'), ('AC', 'Alchemist'), ('NC', 'Narrator'), ('EC', 'Economist')], max_length=2, null=True)),
('role_revealed', models.BooleanField(default=False)),
('seat_position', models.IntegerField(blank=True, null=True)),
('gamer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='table_seats', to=settings.AUTH_USER_MODEL)),
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='table_seats', to='epic.room')),
],
),
]

View File

@@ -1,39 +0,0 @@
# 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')),
],
),
]

View File

@@ -1,164 +0,0 @@
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),
]

View File

@@ -1,68 +0,0 @@
# 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')},
),
]

View File

@@ -1,202 +0,0 @@
"""
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 051) ──────────────────────────
# (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),
]

View File

@@ -1,82 +0,0 @@
"""
Data migration: rename Earthman court cards at positions 11 and 12.
Old naming (from 0010): Jack (11) / Cavalier (12)
New naming: Maid (11) / Jack (12)
Must rename 11 → Maid first so the "jack-of-*-em" slugs are free
before the 12s claim them.
"""
from django.db import migrations
SUITS = ["Wands", "Cups", "Swords", "Coins"]
def rename_court_cards(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
# Step 1: Jack (11) → Maid — frees up jack-of-*-em slugs
for suit in SUITS:
suit_slug = suit.lower()
TarotCard.objects.filter(
deck_variant=earthman, number=11, slug=f"jack-of-{suit_slug}-em"
).update(
name=f"Maid of {suit}",
slug=f"maid-of-{suit_slug}-em",
)
# Step 2: Cavalier (12) → Jack — takes the now-free jack-of-*-em slugs
for suit in SUITS:
suit_slug = suit.lower()
TarotCard.objects.filter(
deck_variant=earthman, number=12, slug=f"cavalier-of-{suit_slug}-em"
).update(
name=f"Jack of {suit}",
slug=f"jack-of-{suit_slug}-em",
)
def reverse_court_cards(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
# Step 1: Jack (12) → Cavalier — frees up jack-of-*-em slugs
for suit in SUITS:
suit_slug = suit.lower()
TarotCard.objects.filter(
deck_variant=earthman, number=12, slug=f"jack-of-{suit_slug}-em"
).update(
name=f"Cavalier of {suit}",
slug=f"cavalier-of-{suit_slug}-em",
)
# Step 2: Maid (11) → Jack
for suit in SUITS:
suit_slug = suit.lower()
TarotCard.objects.filter(
deck_variant=earthman, number=11, slug=f"maid-of-{suit_slug}-em"
).update(
name=f"Jack of {suit}",
slug=f"jack-of-{suit_slug}-em",
)
class Migration(migrations.Migration):
dependencies = [
("epic", "0010_seed_deck_variants_and_earthman"),
]
operations = [
migrations.RunPython(rename_court_cards, reverse_code=reverse_court_cards),
]

View File

@@ -1,162 +0,0 @@
"""
Data migration:
1. Rename grouped Earthman major arcana to use group-relative ordinals
(e.g. "Virtue VI: Controlled Folly""Implicit Virtue 1: Controlled Folly").
2. Spell out Earthman minor arcana pip names 210
(e.g. "2 of Wands""Two of Wands").
Corner ranks (Roman numerals of absolute card number) are a property on the model
and are unchanged — this only affects the stored name / slug fields.
"""
from django.db import migrations
# ── Major arcana: (new_name, new_slug) keyed by card number ─────────────────
MAJOR_RENAMES = {
# Implicit Virtues (cards 69)
6: ("Implicit Virtue 1: Controlled Folly", "implicit-virtue-1-controlled-folly"),
7: ("Implicit Virtue 2: Not-Doing", "implicit-virtue-2-not-doing"),
8: ("Implicit Virtue 3: Losing Self-Importance", "implicit-virtue-3-losing-self-importance"),
9: ("Implicit Virtue 4: Erasing Personal History", "implicit-virtue-4-erasing-personal-history"),
# Explicit Virtues (cards 1820)
18: ("Explicit Virtue 1: Stalking", "explicit-virtue-1-stalking"),
19: ("Explicit Virtue 2: Intent", "explicit-virtue-2-intent"),
20: ("Explicit Virtue 3: Dreaming", "explicit-virtue-3-dreaming"),
# Classical Elements (cards 2124)
21: ("Classical Element 1: Fire", "classical-element-1-fire"),
22: ("Classical Element 2: Earth", "classical-element-2-earth"),
23: ("Classical Element 3: Air", "classical-element-3-air"),
24: ("Classical Element 4: Water", "classical-element-4-water"),
# Zodiac (cards 2536)
25: ("Zodiac 1: Aries", "zodiac-1-aries"),
26: ("Zodiac 2: Taurus", "zodiac-2-taurus"),
27: ("Zodiac 3: Gemini", "zodiac-3-gemini"),
28: ("Zodiac 4: Cancer", "zodiac-4-cancer"),
29: ("Zodiac 5: Leo", "zodiac-5-leo"),
30: ("Zodiac 6: Virgo", "zodiac-6-virgo"),
31: ("Zodiac 7: Libra", "zodiac-7-libra"),
32: ("Zodiac 8: Scorpio", "zodiac-8-scorpio"),
33: ("Zodiac 9: Sagittarius", "zodiac-9-sagittarius"),
34: ("Zodiac 10: Capricorn", "zodiac-10-capricorn"),
35: ("Zodiac 11: Aquarius", "zodiac-11-aquarius"),
36: ("Zodiac 12: Pisces", "zodiac-12-pisces"),
# Absolute Elements (cards 3738)
37: ("Absolute Element 1: Time", "absolute-element-1-time"),
38: ("Absolute Element 2: Space", "absolute-element-2-space"),
# Wanderers (cards 3949)
39: ("Wanderer 1: The Polestar", "wanderer-1-polestar"),
40: ("Wanderer 2: The Antichthon", "wanderer-2-antichthon"),
41: ("Wanderer 3: The Corestar", "wanderer-3-corestar"),
42: ("Wanderer 4: Mercury", "wanderer-4-mercury"),
43: ("Wanderer 5: Venus", "wanderer-5-venus"),
44: ("Wanderer 6: Mars", "wanderer-6-mars"),
45: ("Wanderer 7: Jupiter", "wanderer-7-jupiter"),
46: ("Wanderer 8: Saturn", "wanderer-8-saturn"),
47: ("Wanderer 9: Uranus", "wanderer-9-uranus"),
48: ("Wanderer 10: Neptune", "wanderer-10-neptune"),
49: ("Wanderer 11: The King & Queen of Hades", "wanderer-11-king-queen-hades"),
}
# Original (name, slug) pairs for reversal
MAJOR_ORIGINALS = {
6: ("Virtue VI: Controlled Folly", "virtue-vi-controlled-folly"),
7: ("Virtue VII: Not-Doing", "virtue-vii-not-doing"),
8: ("Virtue VIII: Losing Self-Importance", "virtue-viii-losing-self-importance"),
9: ("Virtue IX: Erasing Personal History", "virtue-ix-erasing-personal-history"),
18: ("Virtue XVIII: Stalking", "virtue-xviii-stalking"),
19: ("Virtue XIX: Intent", "virtue-xix-intent"),
20: ("Virtue XX: Dreaming", "virtue-xx-dreaming"),
21: ("Element XXI: Fire", "element-xxi-fire"),
22: ("Element XXII: Earth", "element-xxii-earth"),
23: ("Element XXIII: Air", "element-xxiii-air"),
24: ("Element XXIV: Water", "element-xxiv-water"),
25: ("Zodiac XXV: Aries", "zodiac-xxv-aries"),
26: ("Zodiac XXVI: Taurus", "zodiac-xxvi-taurus"),
27: ("Zodiac XXVII: Gemini", "zodiac-xxvii-gemini"),
28: ("Zodiac XXVIII: Cancer", "zodiac-xxviii-cancer"),
29: ("Zodiac XXIX: Leo", "zodiac-xxix-leo"),
30: ("Zodiac XXX: Virgo", "zodiac-xxx-virgo"),
31: ("Zodiac XXXI: Libra", "zodiac-xxxi-libra"),
32: ("Zodiac XXXII: Scorpio", "zodiac-xxxii-scorpio"),
33: ("Zodiac XXXIII: Sagittarius", "zodiac-xxxiii-sagittarius"),
34: ("Zodiac XXXIV: Capricorn", "zodiac-xxxiv-capricorn"),
35: ("Zodiac XXXV: Aquarius", "zodiac-xxxv-aquarius"),
36: ("Zodiac XXXVI: Pisces", "zodiac-xxxvi-pisces"),
37: ("Element XXXVII: Time", "element-xxxvii-time"),
38: ("Element XXXVIII: Space", "element-xxxviii-space"),
39: ("Wanderer XXXIX: The Polestar", "wanderer-xxxix-polestar"),
40: ("Wanderer XL: The Antichthon", "wanderer-xl-antichthon"),
41: ("Wanderer XLI: The Corestar", "wanderer-xli-corestar"),
42: ("Wanderer XLII: Mercury", "wanderer-xlii-mercury"),
43: ("Wanderer XLIII: Venus", "wanderer-xliii-venus"),
44: ("Wanderer XLIV: Mars", "wanderer-xliv-mars"),
45: ("Wanderer XLV: Jupiter", "wanderer-xlv-jupiter"),
46: ("Wanderer XLVI: Saturn", "wanderer-xlvi-saturn"),
47: ("Wanderer XLVII: Uranus", "wanderer-xlvii-uranus"),
48: ("Wanderer XLVIII: Neptune", "wanderer-xlviii-neptune"),
49: ("Wanderer XLIX: The King & Queen of Hades", "wanderer-xlix-king-queen-hades"),
}
# Pip number → spelled-out word (slugs already use the word form, only name changes)
PIP_SPELLINGS = {
2: "Two", 3: "Three", 4: "Four", 5: "Five",
6: "Six", 7: "Seven", 8: "Eight", 9: "Nine", 10: "Ten",
}
SUITS = ["WANDS", "CUPS", "SWORDS", "COINS"]
def rename_forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
# 1. Rename grouped major arcana to group-relative ordinals
for number, (new_name, new_slug) in MAJOR_RENAMES.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=new_name, slug=new_slug)
# 2. Spell out pip names 210
for number, word in PIP_SPELLINGS.items():
for suit in SUITS:
TarotCard.objects.filter(
deck_variant=earthman, arcana="MINOR", suit=suit, number=number
).update(name=f"{word} of {suit.capitalize()}")
def rename_reverse(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
# 1. Restore original major arcana names
for number, (old_name, old_slug) in MAJOR_ORIGINALS.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=old_name, slug=old_slug)
# 2. Restore numeric pip names (slugs unchanged)
for number, _word in PIP_SPELLINGS.items():
for suit in SUITS:
TarotCard.objects.filter(
deck_variant=earthman, arcana="MINOR", suit=suit, number=number
).update(name=f"{number} of {suit.capitalize()}")
class Migration(migrations.Migration):
dependencies = [
("epic", "0011_rename_earthman_court_cards"),
]
operations = [
migrations.RunPython(rename_forward, reverse_code=rename_reverse),
]

View File

@@ -1,55 +0,0 @@
"""
Data migration: rename Earthman 4th-suit cards from COINS → PENTACLES.
Updates:
- suit field: "COINS""PENTACLES"
- name: "X of Coins""X of Pentacles"
- slug: "x-of-coins-em""x-of-pentacles-em"
"""
from django.db import migrations
def coins_to_pentacles(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
cards = TarotCard.objects.filter(deck_variant=earthman, suit="COINS")
for card in cards:
card.suit = "PENTACLES"
card.name = card.name.replace(" of Coins", " of Pentacles")
card.slug = card.slug.replace("-of-coins-em", "-of-pentacles-em")
card.save(update_fields=["suit", "name", "slug"])
def pentacles_to_coins(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
# Only reverse cards that came from Earthman (identified by -em slug suffix)
cards = TarotCard.objects.filter(
deck_variant=earthman, suit="PENTACLES", slug__endswith="-em"
)
for card in cards:
card.suit = "COINS"
card.name = card.name.replace(" of Pentacles", " of Coins")
card.slug = card.slug.replace("-of-pentacles-em", "-of-coins-em")
card.save(update_fields=["suit", "name", "slug"])
class Migration(migrations.Migration):
dependencies = [
("epic", "0012_rename_earthman_major_groups_and_pip_spellings"),
]
operations = [
migrations.RunPython(coins_to_pentacles, reverse_code=pentacles_to_coins),
]

View File

@@ -1,65 +0,0 @@
"""
Data migration: rename the five Pope cards to use Arabic group-relative ordinals,
matching the convention set for other grouped major arcana.
"Pope I: President""Pope 1: President"
"Pope II: Tsar""Pope 2: Tsar"
etc.
"""
from django.db import migrations
POPE_RENAMES = {
1: ("Pope 1: President", "pope-1-president"),
2: ("Pope 2: Tsar", "pope-2-tsar"),
3: ("Pope 3: Chairman", "pope-3-chairman"),
4: ("Pope 4: Emperor", "pope-4-emperor"),
5: ("Pope 5: Chancellor", "pope-5-chancellor"),
}
POPE_ORIGINALS = {
1: ("Pope I: President", "pope-i-president"),
2: ("Pope II: Tsar", "pope-ii-tsar"),
3: ("Pope III: Chairman", "pope-iii-chairman"),
4: ("Pope IV: Emperor", "pope-iv-emperor"),
5: ("Pope V: Chancellor", "pope-v-chancellor"),
}
def rename_forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (new_name, new_slug) in POPE_RENAMES.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=new_name, slug=new_slug)
def rename_reverse(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (old_name, old_slug) in POPE_ORIGINALS.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=old_name, slug=old_slug)
class Migration(migrations.Migration):
dependencies = [
("epic", "0013_earthman_coins_to_pentacles"),
]
operations = [
migrations.RunPython(rename_forward, reverse_code=rename_reverse),
]

View File

@@ -1,42 +0,0 @@
"""
Data migration: rename Earthman card 22 from "Classical Element 2: Earth"
to "Classical Element 2: Stone" (Stone = Ossum, the Earthman name for Earth).
"""
from django.db import migrations
def rename_forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=22
).update(name="Classical Element 2: Stone", slug="classical-element-2-stone")
def rename_reverse(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=22
).update(name="Classical Element 2: Earth", slug="classical-element-2-earth")
class Migration(migrations.Migration):
dependencies = [
("epic", "0014_rename_earthman_popes_arabic_ordinals"),
]
operations = [
migrations.RunPython(rename_forward, reverse_code=rename_reverse),
]

View File

@@ -1,63 +0,0 @@
"""
Data migration: reorder the five Pope cards.
New assignment (card number → title):
1 → Chancellor 2 → President 3 → Tsar 4 → Chairman 5 → Emperor
"""
from django.db import migrations
POPE_RENAMES = {
1: ("Pope 1: Chancellor", "pope-1-chancellor"),
2: ("Pope 2: President", "pope-2-president"),
3: ("Pope 3: Tsar", "pope-3-tsar"),
4: ("Pope 4: Chairman", "pope-4-chairman"),
5: ("Pope 5: Emperor", "pope-5-emperor"),
}
POPE_ORIGINALS = {
1: ("Pope 1: President", "pope-1-president"),
2: ("Pope 2: Tsar", "pope-2-tsar"),
3: ("Pope 3: Chairman", "pope-3-chairman"),
4: ("Pope 4: Emperor", "pope-4-emperor"),
5: ("Pope 5: Chancellor", "pope-5-chancellor"),
}
def rename_forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (new_name, new_slug) in POPE_RENAMES.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=new_name, slug=new_slug)
def rename_reverse(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (old_name, old_slug) in POPE_ORIGINALS.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=old_name, slug=old_slug)
class Migration(migrations.Migration):
dependencies = [
("epic", "0015_rename_classical_element_earth_to_stone"),
]
operations = [
migrations.RunPython(rename_forward, reverse_code=rename_reverse),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 6.0 on 2026-03-25 05:46
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0016_reorder_earthman_popes'),
]
operations = [
migrations.AddField(
model_name='tableseat',
name='significator',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='significator_seats', to='epic.tarotcard'),
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 6.0 on 2026-04-01 17:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0017_tableseat_significator_fk'),
]
operations = [
migrations.AlterField(
model_name='tarotcard',
name='suit',
field=models.CharField(blank=True, choices=[('WANDS', 'Wands'), ('CUPS', 'Cups'), ('SWORDS', 'Swords'), ('PENTACLES', 'Pentacles')], max_length=10, null=True),
),
]

View File

@@ -1,70 +0,0 @@
"""
Data migration: rename The Schiz (card 0) and the five Pope cards (cards 15)
in the Earthman deck.
0: "The Schiz""The Nomad"
1: "Pope 1: Chancellor""Pope 1: The Schizo"
2: "Pope 2: President""Pope 2: The Despot"
3: "Pope 3: Tsar""Pope 3: The Capitalist"
4: "Pope 4: Chairman""Pope 4: The Fascist"
5: "Pope 5: Emperor""Pope 5: The War Machine"
"""
from django.db import migrations
NEW_NAMES = {
0: ("The Nomad", "the-nomad"),
1: ("Pope 1: The Schizo", "pope-1-the-schizo"),
2: ("Pope 2: The Despot", "pope-2-the-despot"),
3: ("Pope 3: The Capitalist", "pope-3-the-capitalist"),
4: ("Pope 4: The Fascist", "pope-4-the-fascist"),
5: ("Pope 5: The War Machine","pope-5-the-war-machine"),
}
OLD_NAMES = {
0: ("The Schiz", "the-schiz"),
1: ("Pope 1: Chancellor", "pope-1-chancellor"),
2: ("Pope 2: President", "pope-2-president"),
3: ("Pope 3: Tsar", "pope-3-tsar"),
4: ("Pope 4: Chairman", "pope-4-chairman"),
5: ("Pope 5: Emperor", "pope-5-emperor"),
}
def rename_forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (new_name, new_slug) in NEW_NAMES.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=new_name, slug=new_slug)
def rename_reverse(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (old_name, old_slug) in OLD_NAMES.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=old_name, slug=old_slug)
class Migration(migrations.Migration):
dependencies = [
("epic", "0018_alter_tarotcard_suit"),
]
operations = [
migrations.RunPython(rename_forward, reverse_code=rename_reverse),
]

View File

@@ -1,63 +0,0 @@
"""
Data migration: rename Pope cards 25 in the Earthman deck.
2: "Pope 2: The Despot""Pope 2: The Occultist"
3: "Pope 3: The Capitalist""Pope 3: The Despot"
4: "Pope 4: The Fascist""Pope 4: The Capitalist"
5: "Pope 5: The War Machine""Pope 5: The Fascist"
"""
from django.db import migrations
NEW_NAMES = {
2: ("Pope 2: The Occultist", "pope-2-the-occultist"),
3: ("Pope 3: The Despot", "pope-3-the-despot"),
4: ("Pope 4: The Capitalist","pope-4-the-capitalist"),
5: ("Pope 5: The Fascist", "pope-5-the-fascist"),
}
OLD_NAMES = {
2: ("Pope 2: The Despot", "pope-2-the-despot"),
3: ("Pope 3: The Capitalist", "pope-3-the-capitalist"),
4: ("Pope 4: The Fascist", "pope-4-the-fascist"),
5: ("Pope 5: The War Machine", "pope-5-the-war-machine"),
}
def rename_forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (new_name, new_slug) in NEW_NAMES.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=new_name, slug=new_slug)
def rename_reverse(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for number, (old_name, old_slug) in OLD_NAMES.items():
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(name=old_name, slug=old_slug)
class Migration(migrations.Migration):
dependencies = [
("epic", "0019_rename_earthman_schiz_and_popes"),
]
operations = [
migrations.RunPython(rename_forward, reverse_code=rename_reverse),
]

View File

@@ -1,56 +0,0 @@
"""
Data migration: rename/update six Earthman Major Arcana cards.
13 name: "Death""King Death & the Cosmic Tree"
14 name: "The Traitor""The Great Hunt"
15 correspondence: "The Tower / La Torre""The House of the Devil / Inferno"
16 correspondence: "Purgatorio""The Tower / La Torre / Purgatorio"
50 name/slug: "The Eagle""The Mould of Man"
51 name/slug: "Divine Calculus""The Eagle"
"""
from django.db import migrations
FORWARD = {
13: dict(name="King Death & the Cosmic Tree", slug="king-death-and-the-cosmic-tree"),
14: dict(name="The Great Hunt", slug="the-great-hunt"),
15: dict(correspondence="The House of the Devil / Inferno"),
16: dict(correspondence="The Tower / La Torre / Purgatorio"),
50: dict(name="The Mould of Man", slug="the-mould-of-man"),
51: dict(name="The Eagle", slug="the-eagle"),
}
REVERSE = {
13: dict(name="Death", slug="death-em"),
14: dict(name="The Traitor", slug="the-traitor"),
15: dict(correspondence="The Tower / La Torre"),
16: dict(correspondence="Purgatorio"),
50: dict(name="The Eagle", slug="the-eagle"),
51: dict(name="Divine Calculus",slug="divine-calculus"),
}
def apply(changes):
def fn(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
# Process in sorted order so card 50 vacates "the-eagle" slug before card 51 claims it.
for number in sorted(changes):
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=number
).update(**changes[number])
return fn
class Migration(migrations.Migration):
dependencies = [
("epic", "0020_rename_earthman_pope_cards_2_5"),
]
operations = [
migrations.RunPython(apply(FORWARD), reverse_code=apply(REVERSE)),
]

View File

@@ -1,31 +0,0 @@
# Generated by Django 6.0 on 2026-04-06 00:02
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0021_rename_earthman_major_arcana_batch_2'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='SigReservation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(max_length=2)),
('polarity', models.CharField(choices=[('levity', 'Levity'), ('gravity', 'Gravity')], max_length=7)),
('reserved_at', models.DateTimeField(auto_now_add=True)),
('card', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sig_reservations', to='epic.tarotcard')),
('gamer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sig_reservations', to=settings.AUTH_USER_MODEL)),
('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sig_reservations', to='epic.room')),
],
options={
'constraints': [models.UniqueConstraint(fields=('room', 'gamer'), name='one_sig_reservation_per_gamer_per_room'), models.UniqueConstraint(fields=('room', 'card', 'polarity'), name='one_reservation_per_card_per_polarity_per_room')],
},
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 6.0 on 2026-04-06 02:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0022_sig_reservation'),
]
operations = [
migrations.AddField(
model_name='tarotcard',
name='icon',
field=models.CharField(blank=True, default='', max_length=50),
),
migrations.AlterField(
model_name='tarotcard',
name='arcana',
field=models.CharField(choices=[('MAJOR', 'Major Arcana'), ('MINOR', 'Minor Arcana'), ('MIDDLE', 'Middle Arcana')], max_length=6),
),
migrations.AlterField(
model_name='tarotcard',
name='suit',
field=models.CharField(blank=True, choices=[('WANDS', 'Wands'), ('CUPS', 'Cups'), ('SWORDS', 'Swords'), ('PENTACLES', 'Pentacles'), ('CROWNS', 'Crowns')], max_length=10, null=True),
),
]

View File

@@ -1,46 +0,0 @@
"""
Data migration: rename Earthman 4th-suit cards from PENTACLES → CROWNS.
Updates for every Earthman card where suit="PENTACLES":
- suit: "PENTACLES""CROWNS"
- name: " of Pentacles"" of Crowns"
- slug: "pentacles""crowns"
"""
from django.db import migrations
def pentacles_to_crowns(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for card in TarotCard.objects.filter(deck_variant=earthman, suit="PENTACLES"):
card.suit = "CROWNS"
card.name = card.name.replace(" of Pentacles", " of Crowns")
card.slug = card.slug.replace("pentacles", "crowns")
card.save(update_fields=["suit", "name", "slug"])
def crowns_to_pentacles(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
for card in TarotCard.objects.filter(deck_variant=earthman, suit="CROWNS"):
card.suit = "PENTACLES"
card.name = card.name.replace(" of Crowns", " of Pentacles")
card.slug = card.slug.replace("crowns", "pentacles")
card.save(update_fields=["suit", "name", "slug"])
class Migration(migrations.Migration):
dependencies = [
("epic", "0023_tarotcard_icon_alter_tarotcard_arcana_and_more"),
]
operations = [
migrations.RunPython(pentacles_to_crowns, reverse_code=crowns_to_pentacles),
]

View File

@@ -1,62 +0,0 @@
"""
Data migration: Earthman deck — court cards and major arcana icons.
1. Court cards (numbers 1114, all suits): arcana "MINOR""MIDDLE"
2. Major arcana icons (stored in TarotCard.icon):
0 (Nomad) → fa-hat-cowboy-side
1 (Schizo) → fa-hat-wizard
251 (rest) → fa-hand-dots
"""
from django.db import migrations
MAJOR_ICONS = {
0: "fa-hat-cowboy-side",
1: "fa-hat-wizard",
}
DEFAULT_MAJOR_ICON = "fa-hand-dots"
def forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
# Court cards → MIDDLE
TarotCard.objects.filter(
deck_variant=earthman, arcana="MINOR", number__in=[11, 12, 13, 14]
).update(arcana="MIDDLE")
# Major arcana icons
for card in TarotCard.objects.filter(deck_variant=earthman, arcana="MAJOR"):
card.icon = MAJOR_ICONS.get(card.number, DEFAULT_MAJOR_ICON)
card.save(update_fields=["icon"])
def backward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
TarotCard.objects.filter(
deck_variant=earthman, arcana="MIDDLE", number__in=[11, 12, 13, 14]
).update(arcana="MINOR")
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR"
).update(icon="")
class Migration(migrations.Migration):
dependencies = [
("epic", "0024_earthman_pentacles_to_crowns"),
]
operations = [
migrations.RunPython(forward, reverse_code=backward),
]

View File

@@ -1,154 +0,0 @@
"""
Data migration — Earthman deck:
1. Rename three suit codes (and card names) for Earthman cards:
WANDS → BRANDS (Wands → Brands)
CUPS → GRAILS (Cups → Grails)
SWORDS → BLADES (Swords → Blades)
CROWNS stays CROWNS.
2. Copy keywords_upright / keywords_reversed from the Fiorentine Minchiate
deck to corresponding Earthman cards:
• Major: explicit number-to-number map based on card correspondences.
• Minor/Middle: same number, suit mapped (BRANDS→WANDS, GRAILS→CUPS,
BLADES→SWORDS, CROWNS→PENTACLES). Cards with no Fiorentine counterpart
stay with empty keyword lists.
"""
from django.db import migrations
# ── 1. Suit rename map ────────────────────────────────────────────────────────
SUIT_RENAMES = {
"WANDS": "BRANDS",
"CUPS": "GRAILS",
"SWORDS": "BLADES",
}
# ── 2. Major arcana: Earthman number → Fiorentine number ─────────────────────
# Cards without a Fiorentine counterpart are omitted (keywords stay empty).
MAJOR_KEYWORD_MAP = {
0: 0, # The Schiz → The Fool
1: 1, # Pope I (President) → The Magician
2: 2, # Pope II (Tsar) → The High Priestess
3: 3, # Pope III (Chairman) → The Empress
4: 4, # Pope IV (Emperor) → The Emperor
5: 5, # Pope V (Chancellor) → The Hierophant
6: 8, # Virtue VI (Controlled Folly) → Strength
7: 11, # Virtue VII (Not-Doing) → Justice
8: 14, # Virtue VIII (Losing Self-Importance) → Temperance
# 9: Prudence — no Fiorentine equivalent
10: 10, # Wheel of Fortune → Wheel of Fortune
11: 7, # The Junkboat → The Chariot
12: 12, # The Junkman → The Hanged Man
13: 13, # Death → Death
14: 15, # The Traitor → The Devil
15: 16, # Disco Inferno → The Tower
# 16: Torre Terrestre (Purgatory) — no equivalent
# 17: Fantasia Celestia (Paradise) — no equivalent
18: 6, # Virtue XVIII (Stalking) → The Lovers
# 19: Virtue XIX (Intent / Hope) — no equivalent
# 20: Virtue XX (Dreaming / Faith)— no equivalent
# 2138: Classical Elements + Zodiac — no equivalents
39: 17, # Wanderer XXXIX (Polestar) → The Star
40: 18, # Wanderer XL (Antichthon) → The Moon
41: 19, # Wanderer XLI (Corestar) → The Sun
# 4249: Planets + The Binary — no equivalents
50: 20, # The Eagle → Judgement
51: 21, # Divine Calculus → The World
}
# ── 3. Minor suit map: Earthman (post-rename) → Fiorentine ───────────────────
MINOR_SUIT_MAP = {
"BRANDS": "WANDS",
"GRAILS": "CUPS",
"BLADES": "SWORDS",
"CROWNS": "PENTACLES",
}
def forward(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
try:
earthman = DeckVariant.objects.get(slug="earthman")
fiorentine = DeckVariant.objects.get(slug="fiorentine-minchiate")
except DeckVariant.DoesNotExist:
return # decks not seeded — nothing to do
# ── Step 1: rename Earthman suit codes + card names ───────────────────────
for old_suit, new_suit in SUIT_RENAMES.items():
old_display = old_suit.capitalize() # e.g. "Wands"
new_display = new_suit.capitalize() # e.g. "Brands"
cards = TarotCard.objects.filter(deck_variant=earthman, suit=old_suit)
for card in cards:
card.name = card.name.replace(f" of {old_display}", f" of {new_display}")
card.suit = new_suit
card.save()
# ── Step 2: copy major arcana keywords ───────────────────────────────────
fio_major = {
card.number: card
for card in TarotCard.objects.filter(deck_variant=fiorentine, arcana="MAJOR")
}
for em_num, fio_num in MAJOR_KEYWORD_MAP.items():
fio_card = fio_major.get(fio_num)
if not fio_card:
continue
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=em_num
).update(
keywords_upright=fio_card.keywords_upright,
keywords_reversed=fio_card.keywords_reversed,
)
# ── Step 3: copy minor/middle arcana keywords ─────────────────────────────
for em_suit, fio_suit in MINOR_SUIT_MAP.items():
fio_by_number = {
card.number: card
for card in TarotCard.objects.filter(deck_variant=fiorentine, suit=fio_suit)
}
for em_card in TarotCard.objects.filter(deck_variant=earthman, suit=em_suit):
fio_card = fio_by_number.get(em_card.number)
if fio_card:
em_card.keywords_upright = fio_card.keywords_upright
em_card.keywords_reversed = fio_card.keywords_reversed
em_card.save()
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
# Reverse suit renames
reverse_renames = {new: old for old, new in SUIT_RENAMES.items()}
for new_suit, old_suit in reverse_renames.items():
new_display = new_suit.capitalize()
old_display = old_suit.capitalize()
cards = TarotCard.objects.filter(deck_variant=earthman, suit=new_suit)
for card in cards:
card.name = card.name.replace(f" of {new_display}", f" of {old_display}")
card.suit = old_suit
card.save()
# Clear all Earthman keywords
TarotCard.objects.filter(deck_variant=earthman).update(
keywords_upright=[],
keywords_reversed=[],
)
class Migration(migrations.Migration):
dependencies = [
("epic", "0025_earthman_middle_arcana_and_major_icons"),
]
operations = [
migrations.RunPython(forward, reverse_code=reverse),
]

View File

@@ -1,65 +0,0 @@
"""
Schema + data migration:
1. Add `cautions` JSONField (list, default=[]) to TarotCard.
2. Seed The Schizo (Earthman MAJOR #1) with 4 rival-interaction cautions.
All other cards default to [] — the UI shows a placeholder when empty.
"""
from django.db import migrations, models
SCHIZO_CAUTIONS = [
'This card will reverse into <span class="card-ref">The Pervert</span> when it'
' comes under dominion of <span class="card-ref">The Occultist</span>, which in turn'
' reverses into <span class="card-ref">Pestilence</span>.',
'This card will reverse into <span class="card-ref">The Paranoiac</span> when it'
' comes under dominion of <span class="card-ref">The Despot</span>, which in turn'
' reverses into <span class="card-ref">War</span>.',
'This card will reverse into <span class="card-ref">The Neurotic</span> when it'
' comes under dominion of <span class="card-ref">The Capitalist</span>, which in turn'
' reverses into <span class="card-ref">Famine</span>.',
'This card will reverse into <span class="card-ref">The Suicidal</span> when it'
' comes under dominion of <span class="card-ref">The Fascist</span>, which in turn'
' reverses into <span class="card-ref">Death</span>.',
]
def seed_schizo_cautions(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(cautions=SCHIZO_CAUTIONS)
def clear_schizo_cautions(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(cautions=[])
class Migration(migrations.Migration):
dependencies = [
("epic", "0026_earthman_suit_renames_and_keywords"),
]
operations = [
migrations.AddField(
model_name="tarotcard",
name="cautions",
field=models.JSONField(default=list),
),
migrations.RunPython(seed_schizo_cautions, reverse_code=clear_schizo_cautions),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 6.0 on 2026-04-07 03:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0027_tarotcard_cautions'),
]
operations = [
migrations.AlterField(
model_name='tarotcard',
name='suit',
field=models.CharField(blank=True, choices=[('WANDS', 'Wands'), ('CUPS', 'Cups'), ('SWORDS', 'Swords'), ('PENTACLES', 'Pentacles'), ('CROWNS', 'Crowns'), ('BRANDS', 'Brands'), ('GRAILS', 'Grails'), ('BLADES', 'Blades')], max_length=10, null=True),
),
]

View File

@@ -1,61 +0,0 @@
"""
Data fix: clear Schizo cautions from The Nomad (number=0) if present,
and ensure they land on The Schizo (number=1).
"""
from django.db import migrations
SCHIZO_CAUTIONS = [
'This card will reverse into <span class="card-ref">I. The Pervert</span> when it'
' comes under dominion of <span class="card-ref">II. The Occultist</span>, which in turn'
' reverses into <span class="card-ref">II. Pestilence</span>.',
'This card will reverse into <span class="card-ref">I. The Paranoiac</span> when it'
' comes under dominion of <span class="card-ref">III. The Despot</span>, which in turn'
' reverses into <span class="card-ref">III. War</span>.',
'This card will reverse into <span class="card-ref">I. The Neurotic</span> when it'
' comes under dominion of <span class="card-ref">IV. The Capitalist</span>, which in turn'
' reverses into <span class="card-ref">IV. Famine</span>.',
'This card will reverse into <span class="card-ref">I. The Suicidal</span> when it'
' comes under dominion of <span class="card-ref">V. The Fascist</span>, which in turn'
' reverses into <span class="card-ref">V. Death</span>.',
]
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=0
).update(cautions=[])
TarotCard.objects.filter(
deck_variant=earthman, arcana="MAJOR", number=1
).update(cautions=SCHIZO_CAUTIONS)
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=1
).update(cautions=[])
class Migration(migrations.Migration):
dependencies = [
("epic", "0028_alter_tarotcard_suit"),
]
operations = [
migrations.RunPython(forward, reverse_code=reverse),
]

View File

@@ -1,23 +0,0 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0029_fix_schizo_cautions'),
]
operations = [
migrations.AddField(
model_name='sigreservation',
name='seat',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='sig_reservation',
to='epic.tableseat',
),
),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 6.0 on 2026-04-09 04:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0030_sigreservation_seat_fk'),
]
operations = [
migrations.AddField(
model_name='room',
name='sig_select_started_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='sigreservation',
name='countdown_remaining',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='sigreservation',
name='ready',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='room',
name='table_status',
field=models.CharField(blank=True, choices=[('ROLE_SELECT', 'Role Select'), ('SIG_SELECT', 'Significator Select'), ('SKY_SELECT', 'Sky Select'), ('IN_GAME', 'In Game')], max_length=20, null=True),
),
]

View File

@@ -1,65 +0,0 @@
# Generated by Django 6.0 on 2026-04-14 05:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0031_sig_ready_sky_select'),
]
operations = [
migrations.CreateModel(
name='AspectType',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('symbol', models.CharField(max_length=5)),
('angle', models.PositiveSmallIntegerField()),
('orb', models.FloatField()),
],
options={
'ordering': ['angle'],
},
),
migrations.CreateModel(
name='HouseLabel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.PositiveSmallIntegerField(unique=True)),
('name', models.CharField(max_length=30)),
('keywords', models.CharField(blank=True, max_length=100)),
],
options={
'ordering': ['number'],
},
),
migrations.CreateModel(
name='Planet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('symbol', models.CharField(max_length=5)),
('order', models.PositiveSmallIntegerField(unique=True)),
],
options={
'ordering': ['order'],
},
),
migrations.CreateModel(
name='Sign',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('symbol', models.CharField(max_length=5)),
('element', models.CharField(choices=[('Fire', 'Fire'), ('Earth', 'Earth'), ('Air', 'Air'), ('Water', 'Water')], max_length=5)),
('modality', models.CharField(choices=[('Cardinal', 'Cardinal'), ('Fixed', 'Fixed'), ('Mutable', 'Mutable')], max_length=8)),
('order', models.PositiveSmallIntegerField(unique=True)),
('start_degree', models.FloatField()),
],
options={
'ordering': ['order'],
},
),
]

View File

@@ -1,106 +0,0 @@
"""
Data migration: seed Sign, Planet, AspectType, and HouseLabel tables.
These are stable astrological reference rows — never user-edited.
The data matches the constants in pyswiss/apps/charts/calc.py so that
the proxy view and D3 wheel share a single source of truth.
"""
from django.db import migrations
# ── Signs ────────────────────────────────────────────────────────────────────
# (order, name, symbol, element, modality, start_degree)
SIGNS = [
(0, 'Aries', '', 'Fire', 'Cardinal', 0.0),
(1, 'Taurus', '', 'Earth', 'Fixed', 30.0),
(2, 'Gemini', '', 'Air', 'Mutable', 60.0),
(3, 'Cancer', '', 'Water', 'Cardinal', 90.0),
(4, 'Leo', '', 'Fire', 'Fixed', 120.0),
(5, 'Virgo', '', 'Earth', 'Mutable', 150.0),
(6, 'Libra', '', 'Air', 'Cardinal', 180.0),
(7, 'Scorpio', '', 'Water', 'Fixed', 210.0),
(8, 'Sagittarius', '', 'Fire', 'Mutable', 240.0),
(9, 'Capricorn', '', 'Earth', 'Cardinal', 270.0),
(10, 'Aquarius', '', 'Air', 'Fixed', 300.0),
(11, 'Pisces', '', 'Water', 'Mutable', 330.0),
]
# ── Planets ───────────────────────────────────────────────────────────────────
# (order, name, symbol)
PLANETS = [
(0, 'Sun', ''),
(1, 'Moon', ''),
(2, 'Mercury', ''),
(3, 'Venus', ''),
(4, 'Mars', ''),
(5, 'Jupiter', ''),
(6, 'Saturn', ''),
(7, 'Uranus', ''),
(8, 'Neptune', ''),
(9, 'Pluto', ''),
]
# ── Aspect types ──────────────────────────────────────────────────────────────
# (name, symbol, angle, orb) — mirrors ASPECTS constant in pyswiss calc.py
ASPECT_TYPES = [
('Conjunction', '', 0, 8.0),
('Sextile', '', 60, 6.0),
('Square', '', 90, 8.0),
('Trine', '', 120, 8.0),
('Opposition', '', 180, 10.0),
]
# ── House labels (distinctions) ───────────────────────────────────────────────
# (number, name, keywords)
HOUSE_LABELS = [
(1, 'Self', 'identity, appearance, first impressions'),
(2, 'Worth', 'possessions, values, finances'),
(3, 'Education', 'communication, siblings, short journeys'),
(4, 'Family', 'home, roots, ancestry'),
(5, 'Creation', 'creativity, romance, children, pleasure'),
(6, 'Ritual', 'service, health, daily routines'),
(7, 'Cooperation', 'partnerships, marriage, open enemies'),
(8, 'Regeneration', 'transformation, shared resources, death'),
(9, 'Enterprise', 'philosophy, travel, higher learning'),
(10, 'Career', 'public life, reputation, authority'),
(11, 'Reward', 'friends, groups, aspirations'),
(12, 'Reprisal', 'hidden matters, karma, self-undoing'),
]
def forward(apps, schema_editor):
Sign = apps.get_model('epic', 'Sign')
Planet = apps.get_model('epic', 'Planet')
AspectType = apps.get_model('epic', 'AspectType')
HouseLabel = apps.get_model('epic', 'HouseLabel')
for order, name, symbol, element, modality, start_degree in SIGNS:
Sign.objects.create(
order=order, name=name, symbol=symbol,
element=element, modality=modality, start_degree=start_degree,
)
for order, name, symbol in PLANETS:
Planet.objects.create(order=order, name=name, symbol=symbol)
for name, symbol, angle, orb in ASPECT_TYPES:
AspectType.objects.create(name=name, symbol=symbol, angle=angle, orb=orb)
for number, name, keywords in HOUSE_LABELS:
HouseLabel.objects.create(number=number, name=name, keywords=keywords)
def reverse(apps, schema_editor):
for model_name in ('Sign', 'Planet', 'AspectType', 'HouseLabel'):
apps.get_model('epic', model_name).objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('epic', '0032_astro_reference_tables'),
]
operations = [
migrations.RunPython(forward, reverse_code=reverse),
]

View File

@@ -1,35 +0,0 @@
# Generated by Django 6.0 on 2026-04-14 05:29
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epic', '0033_seed_astro_reference_tables'),
]
operations = [
migrations.CreateModel(
name='Character',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('birth_dt', models.DateTimeField(blank=True, null=True)),
('birth_lat', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
('birth_lon', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
('birth_place', models.CharField(blank=True, max_length=200)),
('house_system', models.CharField(choices=[('O', 'Porphyry'), ('P', 'Placidus'), ('K', 'Koch'), ('W', 'Whole Sign')], default='O', max_length=1)),
('chart_data', models.JSONField(blank=True, null=True)),
('celtic_cross', models.JSONField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('confirmed_at', models.DateTimeField(blank=True, null=True)),
('retired_at', models.DateTimeField(blank=True, null=True)),
('seat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='characters', to='epic.tableseat')),
('significator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='character_significators', to='epic.tarotcard')),
],
options={
'ordering': ['-created_at'],
},
),
]

View File

@@ -1,58 +0,0 @@
# 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),
),
]

View File

@@ -1,307 +0,0 @@
"""
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),
]

View File

@@ -1,24 +0,0 @@
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),
]

View File

@@ -1,60 +0,0 @@
"""
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),
]

View File

@@ -1,59 +0,0 @@
"""
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),
]

View File

@@ -1,24 +0,0 @@
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),
]

View File

@@ -1,31 +0,0 @@
"""
Data migration: delete 4 stray Earthman court cards that were never cleaned up.
Maid/Jack/Queen/King of Pentacles (suit=PENTACLES, arcana=MINOR, numbers 11-14)
are duplicates of the correct Maid/Jack/Queen/King of Crowns (suit=CROWNS,
arcana=MIDDLE) that already exist. The PENTACLES rows appeared before migration
0024 could catch them and were never removed.
"""
from django.db import migrations
def delete_stray_pentacles(apps, schema_editor):
TarotCard = apps.get_model("epic", "TarotCard")
DeckVariant = apps.get_model("epic", "DeckVariant")
earthman = DeckVariant.objects.filter(slug="earthman").first()
if not earthman:
return
TarotCard.objects.filter(
deck_variant=earthman, suit="PENTACLES", number__in=[11, 12, 13, 14],
).delete()
class Migration(migrations.Migration):
dependencies = [
("epic", "0040_rename_jovent_to_intent"),
]
operations = [
migrations.RunPython(delete_stray_pentacles, migrations.RunPython.noop),
]