diff --git a/src/apps/applets/migrations/0001_initial.py b/src/apps/applets/migrations/0001_initial.py
index d0fef9f..5135e20 100644
--- a/src/apps/applets/migrations/0001_initial.py
+++ b/src/apps/applets/migrations/0001_initial.py
@@ -1,13 +1,14 @@
-import django.db.models.deletion
+# Generated by Django 6.0 on 2026-04-28 00:59
-from django.conf import settings
+import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
+
initial = True
+
dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@@ -17,7 +18,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(unique=True)),
('name', models.CharField(max_length=100)),
- ('context', models.CharField(choices=[('dashboard', 'Dashboard'), ('gameboard', 'Gameboard')], default='dashboard', max_length=20)),
+ ('context', models.CharField(choices=[('dashboard', 'Dashboard'), ('gameboard', 'Gameboard'), ('wallet', 'Wallet'), ('billboard', 'Billboard')], default='dashboard', max_length=20)),
('default_visible', models.BooleanField(default=True)),
('grid_cols', models.PositiveSmallIntegerField(default=12)),
('grid_rows', models.PositiveSmallIntegerField(default=3)),
@@ -29,8 +30,6 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('visible', models.BooleanField(default=True)),
('applet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='applets.applet')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_applets', to=settings.AUTH_USER_MODEL)),
],
- options={'unique_together': {('user', 'applet')}},
),
]
diff --git a/src/apps/applets/migrations/0002_initial.py b/src/apps/applets/migrations/0002_initial.py
new file mode 100644
index 0000000..61c539b
--- /dev/null
+++ b/src/apps/applets/migrations/0002_initial.py
@@ -0,0 +1,27 @@
+# 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 = [
+ ('applets', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userapplet',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_applets', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterUniqueTogether(
+ name='userapplet',
+ unique_together={('user', 'applet')},
+ ),
+ ]
diff --git a/src/apps/applets/migrations/0002_seed_applets.py b/src/apps/applets/migrations/0002_seed_applets.py
deleted file mode 100644
index 83450a6..0000000
--- a/src/apps/applets/migrations/0002_seed_applets.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from django.db import migrations
-
-
-def seed_applets(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- for slug, name, cols, rows, context in [
- ('wallet', 'Wallet', 12, 3, 'dashboard'),
- ('new-list', 'New List', 9, 3, 'dashboard'),
- ('my-lists', 'My Lists', 3, 3, 'dashboard'),
- ('username', 'Username', 6, 3, 'dashboard'),
- ('palette', 'Palette', 6, 3, 'dashboard'),
- ('new-game', 'New Game', 4, 2, 'gameboard'),
- ('my-games', 'My Games', 4, 4, 'gameboard'),
- ('game-kit', 'Game Kit', 4, 2, 'gameboard'),
- ]:
- Applet.objects.get_or_create(
- slug=slug,
- defaults={'name': name, 'grid_cols': cols, 'grid_rows': rows, 'context': context},
- )
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('applets', '0001_initial')
- ]
-
- operations = [
- migrations.RunPython(seed_applets, migrations.RunPython.noop)
- ]
diff --git a/src/apps/applets/migrations/0003_seed_applets.py b/src/apps/applets/migrations/0003_seed_applets.py
new file mode 100644
index 0000000..7133d13
--- /dev/null
+++ b/src/apps/applets/migrations/0003_seed_applets.py
@@ -0,0 +1,47 @@
+"""Seed all Applet rows."""
+from django.db import migrations
+
+APPLETS = [
+ # (slug, name, context, default_visible, grid_cols, grid_rows)
+ ('wallet', 'Wallet', 'dashboard', True, 12, 3),
+ ('new-post', 'New Post', 'billboard', True, 9, 3),
+ ('my-posts', 'My Posts', 'billboard', True, 3, 3),
+ ('username', 'Username', 'dashboard', True, 6, 3),
+ ('palette', 'Palette', 'dashboard', True, 6, 3),
+ ('new-game', 'New Game', 'gameboard', True, 4, 3),
+ ('my-games', 'My Games', 'gameboard', True, 4, 4),
+ ('game-kit', 'Game Kit', 'gameboard', True, 4, 3),
+ ('wallet-balances', 'Wallet Balances', 'wallet', True, 3, 3),
+ ('wallet-tokens', 'Wallet Tokens', 'wallet', True, 3, 3),
+ ('wallet-payment', 'Payment Methods', 'wallet', True, 6, 3),
+ ('billboard-my-scrolls', 'My Scrolls', 'billboard', True, 4, 3),
+ ('billboard-my-contacts', 'Contacts', 'billboard', True, 4, 3),
+ ('billboard-most-recent', 'Most Recent', 'billboard', True, 8, 6),
+ ('gk-trinkets', 'Trinkets', 'game-kit', True, 3, 3),
+ ('gk-tokens', 'Tokens', 'game-kit', True, 3, 3),
+ ('gk-decks', 'Card Decks', 'game-kit', True, 3, 3),
+ ('gk-dice', 'Dice Sets', 'game-kit', True, 3, 3),
+ ('my-sky', 'My Sky', 'dashboard', True, 6, 6),
+ ('billboard-notes', 'My Notes', 'billboard', True, 4, 4),
+]
+
+
+def seed(apps, schema_editor):
+ Applet = apps.get_model('applets', 'Applet')
+ for slug, name, context, default_visible, grid_cols, grid_rows in APPLETS:
+ Applet.objects.create(
+ slug=slug, name=name, context=context,
+ default_visible=default_visible,
+ grid_cols=grid_cols, grid_rows=grid_rows,
+ )
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('applets', '0002_initial'),
+ ]
+
+ operations = [
+ migrations.RunPython(seed, migrations.RunPython.noop),
+ ]
diff --git a/src/apps/applets/migrations/0003_wallet_applets.py b/src/apps/applets/migrations/0003_wallet_applets.py
deleted file mode 100644
index ff77edc..0000000
--- a/src/apps/applets/migrations/0003_wallet_applets.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from django.db import migrations, models
-
-
-def seed_wallet_applets(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- for slug, name, cols, rows in [
- ('wallet-balances', 'Wallet Balances', 3, 3),
- ('wallet-tokens', 'Wallet Tokens', 3, 3),
- ('wallet-payment', 'Payment Methods', 6, 2),
- ]:
- Applet.objects.get_or_create(
- slug=slug,
- defaults={'name': name, 'grid_cols': cols, 'grid_rows': rows, 'context': 'wallet'},
- )
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('applets', '0002_seed_applets'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='applet',
- name='context',
- field=models.CharField(
- choices=[
- ('dashboard', 'Dashboard'),
- ('gameboard', 'Gameboard'),
- ('wallet', 'Wallet'),
- ],
- default='dashboard',
- max_length=20,
- ),
- ),
- migrations.RunPython(seed_wallet_applets, migrations.RunPython.noop),
- ]
diff --git a/src/apps/applets/migrations/0004_rename_list_applet_slugs.py b/src/apps/applets/migrations/0004_rename_list_applet_slugs.py
deleted file mode 100644
index 4d5352b..0000000
--- a/src/apps/applets/migrations/0004_rename_list_applet_slugs.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.db import migrations
-
-
-def rename_list_slugs(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug='new-list').update(slug='new-note', name='New Note')
- Applet.objects.filter(slug='my-lists').update(slug='my-notes', name='My Notes')
-
-
-def reverse_rename_list_slugs(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug='new-note').update(slug='new-list', name='New List')
- Applet.objects.filter(slug='my-notes').update(slug='my-lists', name='My Lists')
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('applets', '0003_wallet_applets'),
- ]
-
- operations = [
- migrations.RunPython(rename_list_slugs, reverse_rename_list_slugs),
- ]
diff --git a/src/apps/applets/migrations/0005_gameboard_applet_heights.py b/src/apps/applets/migrations/0005_gameboard_applet_heights.py
deleted file mode 100644
index ae5044a..0000000
--- a/src/apps/applets/migrations/0005_gameboard_applet_heights.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.db import migrations
-
-
-def increase_gameboard_applet_heights(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug__in=['new-game', 'game-kit', 'wallet-payment']).update(grid_rows=3)
-
-
-def revert_gameboard_applet_heights(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug__in=['new-game', 'game-kit', 'wallet-payment']).update(grid_rows=2)
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('applets', '0004_rename_list_applet_slugs')
- ]
-
- operations = [
- migrations.RunPython(
- increase_gameboard_applet_heights,
- revert_gameboard_applet_heights,
- )
- ]
diff --git a/src/apps/applets/migrations/0006_billboard_applets.py b/src/apps/applets/migrations/0006_billboard_applets.py
deleted file mode 100644
index bd17d0f..0000000
--- a/src/apps/applets/migrations/0006_billboard_applets.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from django.db import migrations, models
-
-
-def seed_billboard_applets(apps, schema_editor):
- Applet = apps.get_model("applets", "Applet")
- for slug, name, cols, rows in [
- ("billboard-my-scrolls", "My Scrolls", 4, 3),
- ("billboard-my-contacts", "Contacts", 4, 3),
- ("billboard-most-recent", "Most Recent", 8, 6),
- ]:
- Applet.objects.get_or_create(
- slug=slug,
- defaults={"name": name, "grid_cols": cols, "grid_rows": rows, "context": "billboard"},
- )
-
-
-def remove_billboard_applets(apps, schema_editor):
- Applet = apps.get_model("applets", "Applet")
- Applet.objects.filter(slug__in=[
- "billboard-my-scrolls",
- "billboard-my-contacts",
- "billboard-most-recent",
- ]).delete()
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("applets", "0005_gameboard_applet_heights"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="applet",
- name="context",
- field=models.CharField(
- choices=[
- ("dashboard", "Dashboard"),
- ("gameboard", "Gameboard"),
- ("wallet", "Wallet"),
- ("billboard", "Billboard"),
- ],
- default="dashboard",
- max_length=20,
- ),
- ),
- migrations.RunPython(seed_billboard_applets, remove_billboard_applets),
- ]
diff --git a/src/apps/applets/migrations/0007_fix_billboard_applets.py b/src/apps/applets/migrations/0007_fix_billboard_applets.py
deleted file mode 100644
index fa31d32..0000000
--- a/src/apps/applets/migrations/0007_fix_billboard_applets.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from django.db import migrations
-
-
-def fix_billboard_applets(apps, schema_editor):
- Applet = apps.get_model("applets", "Applet")
- # billboard-scroll belongs only to the billscroll page template, not the grid
- Applet.objects.filter(slug="billboard-scroll").delete()
- # Rename "My Contacts" → "Contacts"
- Applet.objects.filter(slug="billboard-my-contacts").update(name="Contacts")
-
-
-def reverse_fix_billboard_applets(apps, schema_editor):
- Applet = apps.get_model("applets", "Applet")
- Applet.objects.get_or_create(
- slug="billboard-scroll",
- defaults={"name": "Billscroll", "grid_cols": 12, "grid_rows": 6, "context": "billboard"},
- )
- Applet.objects.filter(slug="billboard-my-contacts").update(name="My Contacts")
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("applets", "0006_billboard_applets"),
- ]
-
- operations = [
- migrations.RunPython(fix_billboard_applets, reverse_fix_billboard_applets),
- ]
diff --git a/src/apps/applets/migrations/0008_game_kit_applets.py b/src/apps/applets/migrations/0008_game_kit_applets.py
deleted file mode 100644
index ff4e320..0000000
--- a/src/apps/applets/migrations/0008_game_kit_applets.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from django.db import migrations
-
-
-def seed_game_kit_applets(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- for slug, name in [
- ('gk-trinkets', 'Trinkets'),
- ('gk-tokens', 'Tokens'),
- ('gk-decks', 'Card Decks'),
- ('gk-dice', 'Dice Sets'),
- ]:
- Applet.objects.get_or_create(
- slug=slug,
- defaults={'name': name, 'grid_cols': 3, 'grid_rows': 3, 'context': 'game-kit'},
- )
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('applets', '0007_fix_billboard_applets'),
- ]
-
- operations = [
- migrations.RunPython(seed_game_kit_applets, migrations.RunPython.noop)
- ]
diff --git a/src/apps/applets/migrations/0009_my_sky_applet.py b/src/apps/applets/migrations/0009_my_sky_applet.py
deleted file mode 100644
index 07ccd1f..0000000
--- a/src/apps/applets/migrations/0009_my_sky_applet.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.db import migrations
-
-
-def seed_my_sky_applet(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.get_or_create(
- slug='my-sky',
- defaults={
- 'name': 'My Sky',
- 'grid_cols': 6,
- 'grid_rows': 6,
- 'context': 'dashboard',
- },
- )
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('applets', '0008_game_kit_applets'),
- ]
-
- operations = [
- migrations.RunPython(seed_my_sky_applet, migrations.RunPython.noop)
- ]
diff --git a/src/apps/applets/migrations/0010_recognition_applet.py b/src/apps/applets/migrations/0010_recognition_applet.py
deleted file mode 100644
index 8cc5130..0000000
--- a/src/apps/applets/migrations/0010_recognition_applet.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from django.db import migrations
-
-
-def seed_recognition_applet(apps, schema_editor):
- Applet = apps.get_model("applets", "Applet")
- Applet.objects.get_or_create(
- slug="billboard-recognition",
- defaults={
- "name": "Recognition",
- "grid_cols": 4,
- "grid_rows": 4,
- "context": "billboard",
- },
- )
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("applets", "0009_my_sky_applet"),
- ]
-
- operations = [
- migrations.RunPython(seed_recognition_applet, migrations.RunPython.noop),
- ]
diff --git a/src/apps/applets/migrations/0011_rename_note_applets_to_post.py b/src/apps/applets/migrations/0011_rename_note_applets_to_post.py
deleted file mode 100644
index a333f8d..0000000
--- a/src/apps/applets/migrations/0011_rename_note_applets_to_post.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.db import migrations
-
-
-def rename_note_applets_to_post(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug='new-note').update(slug='new-post', name='New Post', context='billboard')
- Applet.objects.filter(slug='my-notes').update(slug='my-posts', name='My Posts', context='billboard')
-
-
-def reverse_rename_note_applets_to_post(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug='new-post').update(slug='new-note', name='New Note', context='dashboard')
- Applet.objects.filter(slug='my-posts').update(slug='my-notes', name='My Notes', context='dashboard')
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('applets', '0010_recognition_applet'),
- ]
-
- operations = [
- migrations.RunPython(rename_note_applets_to_post, reverse_rename_note_applets_to_post),
- ]
diff --git a/src/apps/applets/migrations/0012_rename_recognition_applet_to_notes.py b/src/apps/applets/migrations/0012_rename_recognition_applet_to_notes.py
deleted file mode 100644
index 38a6a92..0000000
--- a/src/apps/applets/migrations/0012_rename_recognition_applet_to_notes.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from django.db import migrations
-
-
-def rename_recognition_applet_to_notes(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug='billboard-recognition').update(
- slug='billboard-notes',
- name='My Notes',
- )
-
-
-def reverse_rename_recognition_applet_to_notes(apps, schema_editor):
- Applet = apps.get_model('applets', 'Applet')
- Applet.objects.filter(slug='billboard-notes').update(
- slug='billboard-recognition',
- name='Recognition',
- )
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('applets', '0011_rename_note_applets_to_post'),
- ]
-
- operations = [
- migrations.RunPython(
- rename_recognition_applet_to_notes,
- reverse_rename_recognition_applet_to_notes,
- ),
- ]
diff --git a/src/apps/dashboard/migrations/0001_initial.py b/src/apps/dashboard/migrations/0001_initial.py
index c5c3fd6..aba3fa1 100644
--- a/src/apps/dashboard/migrations/0001_initial.py
+++ b/src/apps/dashboard/migrations/0001_initial.py
@@ -1,8 +1,6 @@
-# Generated by Django 6.0 on 2026-02-23 04:30
+# Generated by Django 6.0 on 2026-04-28 00:59
-import django.db.models.deletion
import uuid
-from django.conf import settings
from django.db import migrations, models
@@ -11,28 +9,23 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
- name='List',
- fields=[
- ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
- ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL)),
- ('shared_with', models.ManyToManyField(blank=True, related_name='shared_lists', to=settings.AUTH_USER_MODEL)),
- ],
- ),
- migrations.CreateModel(
- name='Item',
+ name='Line',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(default='')),
- ('list', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='dashboard.list')),
],
options={
'ordering': ('id',),
- 'unique_together': {('list', 'text')},
},
),
+ migrations.CreateModel(
+ name='Post',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ],
+ ),
]
diff --git a/src/apps/dashboard/migrations/0005_alter_line_post_alter_post_owner_and_more.py b/src/apps/dashboard/migrations/0002_initial.py
similarity index 73%
rename from src/apps/dashboard/migrations/0005_alter_line_post_alter_post_owner_and_more.py
rename to src/apps/dashboard/migrations/0002_initial.py
index 89fa5be..0a74da9 100644
--- a/src/apps/dashboard/migrations/0005_alter_line_post_alter_post_owner_and_more.py
+++ b/src/apps/dashboard/migrations/0002_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 6.0 on 2026-04-23 05:42
+# Generated by Django 6.0 on 2026-04-28 00:59
import django.db.models.deletion
from django.conf import settings
@@ -7,25 +7,31 @@ from django.db import migrations, models
class Migration(migrations.Migration):
+ initial = True
+
dependencies = [
- ('dashboard', '0004_rename_note_to_post'),
+ ('dashboard', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
- migrations.AlterField(
- model_name='line',
- name='post',
- field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='lines', to='dashboard.post'),
- ),
- migrations.AlterField(
+ migrations.AddField(
model_name='post',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='posts', to=settings.AUTH_USER_MODEL),
),
- migrations.AlterField(
+ migrations.AddField(
model_name='post',
name='shared_with',
field=models.ManyToManyField(blank=True, related_name='shared_posts', to=settings.AUTH_USER_MODEL),
),
+ migrations.AddField(
+ model_name='line',
+ name='post',
+ field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='lines', to='dashboard.post'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='line',
+ unique_together={('post', 'text')},
+ ),
]
diff --git a/src/apps/dashboard/migrations/0002_rename_list_to_note.py b/src/apps/dashboard/migrations/0002_rename_list_to_note.py
deleted file mode 100644
index dbfb235..0000000
--- a/src/apps/dashboard/migrations/0002_rename_list_to_note.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('dashboard', '0001_initial'),
- ]
-
- operations = [
- migrations.RenameModel(
- old_name='List',
- new_name='Note',
- ),
- migrations.RenameField(
- model_name='Item',
- old_name='list',
- new_name='note',
- ),
- ]
diff --git a/src/apps/dashboard/migrations/0003_alter_note_owner_alter_note_shared_with.py b/src/apps/dashboard/migrations/0003_alter_note_owner_alter_note_shared_with.py
deleted file mode 100644
index 6cfe497..0000000
--- a/src/apps/dashboard/migrations/0003_alter_note_owner_alter_note_shared_with.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Generated by Django 6.0 on 2026-03-12 19:30
-
-import django.db.models.deletion
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('dashboard', '0002_rename_list_to_note'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='note',
- name='owner',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notes', to=settings.AUTH_USER_MODEL),
- ),
- migrations.AlterField(
- model_name='note',
- name='shared_with',
- field=models.ManyToManyField(blank=True, related_name='shared_notes', to=settings.AUTH_USER_MODEL),
- ),
- ]
diff --git a/src/apps/dashboard/migrations/0004_rename_note_to_post.py b/src/apps/dashboard/migrations/0004_rename_note_to_post.py
deleted file mode 100644
index e530f55..0000000
--- a/src/apps/dashboard/migrations/0004_rename_note_to_post.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('dashboard', '0003_alter_note_owner_alter_note_shared_with'),
- ]
-
- operations = [
- migrations.RenameModel('Note', 'Post'),
- migrations.RenameModel('Item', 'Line'),
- migrations.RenameField('Line', 'note', 'post'),
- migrations.AlterUniqueTogether(
- name='line',
- unique_together={('post', 'text')},
- ),
- ]
diff --git a/src/apps/drama/migrations/0001_initial.py b/src/apps/drama/migrations/0001_initial.py
index 73099ed..cea59ac 100644
--- a/src/apps/drama/migrations/0001_initial.py
+++ b/src/apps/drama/migrations/0001_initial.py
@@ -1,7 +1,5 @@
-# Generated by Django 6.0 on 2026-03-19 18:34
+# 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
@@ -10,8 +8,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- ('epic', '0006_table_status_and_table_seat'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@@ -19,14 +15,32 @@ class Migration(migrations.Migration):
name='GameEvent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('verb', models.CharField(choices=[('room_created', 'Room created'), ('slot_reserved', 'Gate slot reserved'), ('slot_filled', 'Gate slot filled'), ('slot_returned', 'Gate slot returned'), ('slot_released', 'Gate slot released'), ('invite_sent', 'Invite sent'), ('role_select_started', 'Role select started'), ('role_selected', 'Role selected'), ('roles_revealed', 'Roles revealed')], max_length=30)),
+ ('verb', models.CharField(choices=[('room_created', 'Room created'), ('slot_reserved', 'Gate slot reserved'), ('slot_filled', 'Gate slot filled'), ('slot_returned', 'Gate slot returned'), ('slot_released', 'Gate slot released'), ('invite_sent', 'Invite sent'), ('role_select_started', 'Role select started'), ('role_selected', 'Role selected'), ('roles_revealed', 'Roles revealed'), ('sig_ready', 'Sig claim staked'), ('sig_unready', 'Sig claim withdrawn')], max_length=30)),
('data', models.JSONField(default=dict)),
('timestamp', models.DateTimeField(auto_now_add=True)),
- ('actor', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='game_events', to=settings.AUTH_USER_MODEL)),
- ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='epic.room')),
],
options={
'ordering': ['timestamp'],
},
),
+ migrations.CreateModel(
+ name='Note',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('slug', models.SlugField(max_length=60)),
+ ('earned_at', models.DateTimeField()),
+ ('palette', models.CharField(blank=True, max_length=60, null=True)),
+ ],
+ options={
+ 'ordering': ['earned_at'],
+ },
+ ),
+ migrations.CreateModel(
+ name='ScrollPosition',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('position', models.PositiveIntegerField(default=0)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ],
+ ),
]
diff --git a/src/apps/drama/migrations/0002_initial.py b/src/apps/drama/migrations/0002_initial.py
new file mode 100644
index 0000000..449e047
--- /dev/null
+++ b/src/apps/drama/migrations/0002_initial.py
@@ -0,0 +1,52 @@
+# 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 = [
+ ('drama', '0001_initial'),
+ ('epic', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='gameevent',
+ name='actor',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='game_events', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AddField(
+ model_name='gameevent',
+ name='room',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='epic.room'),
+ ),
+ migrations.AddField(
+ model_name='note',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AddField(
+ model_name='scrollposition',
+ name='room',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scroll_positions', to='epic.room'),
+ ),
+ migrations.AddField(
+ model_name='scrollposition',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scroll_positions', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterUniqueTogether(
+ name='note',
+ unique_together={('user', 'slug')},
+ ),
+ migrations.AlterUniqueTogether(
+ name='scrollposition',
+ unique_together={('user', 'room')},
+ ),
+ ]
diff --git a/src/apps/drama/migrations/0002_scrollposition.py b/src/apps/drama/migrations/0002_scrollposition.py
deleted file mode 100644
index 29c431b..0000000
--- a/src/apps/drama/migrations/0002_scrollposition.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by Django 6.0 on 2026-03-24 21:36
-
-import django.db.models.deletion
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('drama', '0001_initial'),
- ('epic', '0006_table_status_and_table_seat'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.CreateModel(
- name='ScrollPosition',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('position', models.PositiveIntegerField(default=0)),
- ('updated_at', models.DateTimeField(auto_now=True)),
- ('room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scroll_positions', to='epic.room')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scroll_positions', to=settings.AUTH_USER_MODEL)),
- ],
- options={
- 'unique_together': {('user', 'room')},
- },
- ),
- ]
diff --git a/src/apps/drama/migrations/0003_alter_gameevent_verb.py b/src/apps/drama/migrations/0003_alter_gameevent_verb.py
deleted file mode 100644
index 001300f..0000000
--- a/src/apps/drama/migrations/0003_alter_gameevent_verb.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 6.0 on 2026-04-12 23:38
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('drama', '0002_scrollposition'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='gameevent',
- name='verb',
- field=models.CharField(choices=[('room_created', 'Room created'), ('slot_reserved', 'Gate slot reserved'), ('slot_filled', 'Gate slot filled'), ('slot_returned', 'Gate slot returned'), ('slot_released', 'Gate slot released'), ('invite_sent', 'Invite sent'), ('role_select_started', 'Role select started'), ('role_selected', 'Role selected'), ('roles_revealed', 'Roles revealed'), ('sig_ready', 'Sig claim staked'), ('sig_unready', 'Sig claim withdrawn')], max_length=30),
- ),
- ]
diff --git a/src/apps/drama/migrations/0004_recognition.py b/src/apps/drama/migrations/0004_recognition.py
deleted file mode 100644
index b816e29..0000000
--- a/src/apps/drama/migrations/0004_recognition.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by Django 6.0 on 2026-04-22 06:11
-
-import django.db.models.deletion
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('drama', '0003_alter_gameevent_verb'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Recognition',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('slug', models.SlugField(max_length=60)),
- ('earned_at', models.DateTimeField()),
- ('palette', models.CharField(blank=True, max_length=60, null=True)),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recognitions', to=settings.AUTH_USER_MODEL)),
- ],
- options={
- 'ordering': ['earned_at'],
- 'unique_together': {('user', 'slug')},
- },
- ),
- ]
diff --git a/src/apps/drama/migrations/0005_rename_recognition_to_note.py b/src/apps/drama/migrations/0005_rename_recognition_to_note.py
deleted file mode 100644
index 53b97f4..0000000
--- a/src/apps/drama/migrations/0005_rename_recognition_to_note.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import django.conf
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('drama', '0004_recognition'),
- migrations.swappable_dependency(django.conf.settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.RenameModel('Recognition', 'Note'),
- migrations.AlterField(
- model_name='note',
- name='user',
- field=models.ForeignKey(
- django.conf.settings.AUTH_USER_MODEL,
- on_delete=django.db.models.deletion.CASCADE,
- related_name='notes',
- ),
- ),
- ]
diff --git a/src/apps/epic/migrations/0001_initial.py b/src/apps/epic/migrations/0001_initial.py
index b2e760b..929ff85 100644
--- a/src/apps/epic/migrations/0001_initial.py
+++ b/src/apps/epic/migrations/0001_initial.py
@@ -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)),
],
),
]
diff --git a/src/apps/epic/migrations/0002_alter_room_renewal_period.py b/src/apps/epic/migrations/0002_alter_room_renewal_period.py
deleted file mode 100644
index dd7567f..0000000
--- a/src/apps/epic/migrations/0002_alter_room_renewal_period.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0002_initial.py b/src/apps/epic/migrations/0002_initial.py
new file mode 100644
index 0000000..d4606b1
--- /dev/null
+++ b/src/apps/epic/migrations/0002_initial.py
@@ -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'),
+ ),
+ ]
diff --git a/src/apps/epic/migrations/0003_roominvite.py b/src/apps/epic/migrations/0003_roominvite.py
deleted file mode 100644
index 4a5f9d6..0000000
--- a/src/apps/epic/migrations/0003_roominvite.py
+++ /dev/null
@@ -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')),
- ],
- ),
- ]
diff --git a/src/apps/epic/migrations/0003_seed_fiorentine_deck.py b/src/apps/epic/migrations/0003_seed_fiorentine_deck.py
new file mode 100644
index 0000000..1651103
--- /dev/null
+++ b/src/apps/epic/migrations/0003_seed_fiorentine_deck.py
@@ -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),
+ ]
diff --git a/src/apps/epic/migrations/0004_alter_room_gate_status.py b/src/apps/epic/migrations/0004_alter_room_gate_status.py
deleted file mode 100644
index c1e1143..0000000
--- a/src/apps/epic/migrations/0004_alter_room_gate_status.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0004_seed_earthman_deck.py b/src/apps/epic/migrations/0004_seed_earthman_deck.py
new file mode 100644
index 0000000..cba2f4a
--- /dev/null
+++ b/src/apps/epic/migrations/0004_seed_earthman_deck.py
@@ -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 I. The Pervert when it comes under dominion of II. The Occultist, which in turn reverses into II. Pestilence.","This card will reverse into I. The Paranoiac when it comes under dominion of III. The Despot, which in turn reverses into III. War.","This card will reverse into I. The Neurotic when it comes under dominion of IV. The Capitalist, which in turn reverses into IV. Famine.","This card will reverse into I. The Suicidal when it comes under dominion of V. The Fascist, which in turn reverses into V. Death."]),
+ (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),
+ ]
diff --git a/src/apps/epic/migrations/0005_gateslot_debited_token_fields.py b/src/apps/epic/migrations/0005_gateslot_debited_token_fields.py
deleted file mode 100644
index c98d026..0000000
--- a/src/apps/epic/migrations/0005_gateslot_debited_token_fields.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0005_seed_astro_reference_tables.py b/src/apps/epic/migrations/0005_seed_astro_reference_tables.py
new file mode 100644
index 0000000..1257714
--- /dev/null
+++ b/src/apps/epic/migrations/0005_seed_astro_reference_tables.py
@@ -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),
+ ]
diff --git a/src/apps/epic/migrations/0006_table_status_and_table_seat.py b/src/apps/epic/migrations/0006_table_status_and_table_seat.py
deleted file mode 100644
index 70ccbd3..0000000
--- a/src/apps/epic/migrations/0006_table_status_and_table_seat.py
+++ /dev/null
@@ -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')),
- ],
- ),
- ]
diff --git a/src/apps/epic/migrations/0007_tarotcard_tarotdeck.py b/src/apps/epic/migrations/0007_tarotcard_tarotdeck.py
deleted file mode 100644
index 0249f9a..0000000
--- a/src/apps/epic/migrations/0007_tarotcard_tarotdeck.py
+++ /dev/null
@@ -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')),
- ],
- ),
- ]
diff --git a/src/apps/epic/migrations/0008_seed_tarot_cards.py b/src/apps/epic/migrations/0008_seed_tarot_cards.py
deleted file mode 100644
index d2a6a89..0000000
--- a/src/apps/epic/migrations/0008_seed_tarot_cards.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0009_deckvariant_alter_tarotcard_options_and_more.py b/src/apps/epic/migrations/0009_deckvariant_alter_tarotcard_options_and_more.py
deleted file mode 100644
index a8c41bd..0000000
--- a/src/apps/epic/migrations/0009_deckvariant_alter_tarotcard_options_and_more.py
+++ /dev/null
@@ -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')},
- ),
- ]
diff --git a/src/apps/epic/migrations/0010_seed_deck_variants_and_earthman.py b/src/apps/epic/migrations/0010_seed_deck_variants_and_earthman.py
deleted file mode 100644
index 048ab18..0000000
--- a/src/apps/epic/migrations/0010_seed_deck_variants_and_earthman.py
+++ /dev/null
@@ -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 0–51) ──────────────────────────
-# (name, slug, group, correspondence)
-EARTHMAN_MAJOR = [
- # ── The Schiz ──────────────────────────────────────────────────────────
- (0, "The Schiz", "the-schiz", "", "The Fool / Il Matto"),
-
- # ── The Popes ──────────────────────────────────────────────────────────
- (1, "Pope I: President", "pope-i-president", "The Popes", "The Magician / Il Bagatto"),
- (2, "Pope II: Tsar", "pope-ii-tsar", "The Popes", "The Popess / La Papessa"),
- (3, "Pope III: Chairman", "pope-iii-chairman", "The Popes", "The Empress / L'Imperatrice"),
- (4, "Pope IV: Emperor", "pope-iv-emperor", "The Popes", "The Emperor / L'Imperatore"),
- (5, "Pope V: Chancellor", "pope-v-chancellor", "The Popes", "The Pope / Il Papa"),
-
- # ── The Virtues, Implicit (cardinal / acquired) ────────────────────────
- (6, "Virtue VI: Controlled Folly", "virtue-vi-controlled-folly", "The Virtues, Implicit", "Fortitude / La Fortezza"),
- (7, "Virtue VII: Not-Doing", "virtue-vii-not-doing", "The Virtues, Implicit", "Justice / La Giustizia"),
- (8, "Virtue VIII: Losing Self-Importance","virtue-viii-losing-self-importance","The Virtues, Implicit", "Temperance / La Temperanza"),
- (9, "Virtue IX: Erasing Personal History","virtue-ix-erasing-personal-history","The Virtues, Implicit", "Prudence / La Prudenza"),
-
- # ── Wheel ──────────────────────────────────────────────────────────────
- (10, "Wheel of Fortune", "wheel-of-fortune-em", "", "La Ruota della Fortuna"),
-
- # ── Solo cards ─────────────────────────────────────────────────────────
- (11, "The Junkboat", "the-junkboat", "", "The Chariot / Il Carro"),
- (12, "The Junkman", "the-junkman", "", "The Hanged Man / L'Appeso"),
- (13, "Death", "death-em", "", "La Morte"),
- (14, "The Traitor", "the-traitor", "", "The Devil / Il Diavolo"),
- (15, "Disco Inferno", "disco-inferno", "", "The Tower / La Torre"),
- (16, "Torre Terrestre", "torre-terrestre", "", "Purgatorio"),
- (17, "Fantasia Celestia", "fantasia-celestia", "", "Paradiso"),
-
- # ── The Virtues, Explicit (theological / infused) ─────────────────────
- (18, "Virtue XVIII: Stalking", "virtue-xviii-stalking", "The Virtues, Explicit", "Love / Charity / La Carità"),
- (19, "Virtue XIX: Intent", "virtue-xix-intent", "The Virtues, Explicit", "Hope / La Speranza"),
- (20, "Virtue XX: Dreaming", "virtue-xx-dreaming", "The Virtues, Explicit", "Faith / La Fede"),
-
- # ── The Elements, Classical ────────────────────────────────────────────
- (21, "Element XXI: Fire", "element-xxi-fire", "The Elements, Classical", "Ardor [Ar]"),
- (22, "Element XXII: Earth", "element-xxii-earth", "The Elements, Classical", "Ossum [Om]"),
- (23, "Element XXIII: Air", "element-xxiii-air", "The Elements, Classical", "Pneuma [Pn]"),
- (24, "Element XXIV: Water", "element-xxiv-water", "The Elements, Classical", "Humor [Hm]"),
-
- # ── The Zodiac ─────────────────────────────────────────────────────────
- (25, "Zodiac XXV: Aries", "zodiac-xxv-aries", "The Zodiac", "The Ram"),
- (26, "Zodiac XXVI: Taurus", "zodiac-xxvi-taurus", "The Zodiac", "The Bull"),
- (27, "Zodiac XXVII: Gemini", "zodiac-xxvii-gemini", "The Zodiac", "The Twins"),
- (28, "Zodiac XXVIII: Cancer", "zodiac-xxviii-cancer", "The Zodiac", "The Crab"),
- (29, "Zodiac XXIX: Leo", "zodiac-xxix-leo", "The Zodiac", "The Lion"),
- (30, "Zodiac XXX: Virgo", "zodiac-xxx-virgo", "The Zodiac", "The Maiden"),
- (31, "Zodiac XXXI: Libra", "zodiac-xxxi-libra", "The Zodiac", "The Scales"),
- (32, "Zodiac XXXII: Scorpio", "zodiac-xxxii-scorpio", "The Zodiac", "The Scorpion"),
- (33, "Zodiac XXXIII: Sagittarius", "zodiac-xxxiii-sagittarius", "The Zodiac", "The Archer"),
- (34, "Zodiac XXXIV: Capricorn", "zodiac-xxxiv-capricorn", "The Zodiac", "The Sea-Goat"),
- (35, "Zodiac XXXV: Aquarius", "zodiac-xxxv-aquarius", "The Zodiac", "The Water-Bearer"),
- (36, "Zodiac XXXVI: Pisces", "zodiac-xxxvi-pisces", "The Zodiac", "The Fish"),
-
- # ── The Elements, Absolute ─────────────────────────────────────────────
- (37, "Element XXXVII: Time", "element-xxxvii-time", "The Elements, Absolute", "Tempo [Tp]"),
- (38, "Element XXXVIII: Space", "element-xxxviii-space", "The Elements, Absolute", "Nexus [Nx]"),
-
- # ── The Wanderers ──────────────────────────────────────────────────────
- (39, "Wanderer XXXIX: The Polestar", "wanderer-xxxix-polestar", "The Wanderers", "The Star / Le Stelle"),
- (40, "Wanderer XL: The Antichthon", "wanderer-xl-antichthon", "The Wanderers", "The Moon / La Luna"),
- (41, "Wanderer XLI: The Corestar", "wanderer-xli-corestar", "The Wanderers", "The Sun / Il Sole"),
- (42, "Wanderer XLII: Mercury", "wanderer-xlii-mercury", "The Wanderers", "Mercurio"),
- (43, "Wanderer XLIII: Venus", "wanderer-xliii-venus", "The Wanderers", "Venere"),
- (44, "Wanderer XLIV: Mars", "wanderer-xliv-mars", "The Wanderers", "Marte"),
- (45, "Wanderer XLV: Jupiter", "wanderer-xlv-jupiter", "The Wanderers", "Giove"),
- (46, "Wanderer XLVI: Saturn", "wanderer-xlvi-saturn", "The Wanderers", "Saturno"),
- (47, "Wanderer XLVII: Uranus", "wanderer-xlvii-uranus", "The Wanderers", "Urano"),
- (48, "Wanderer XLVIII: Neptune", "wanderer-xlviii-neptune", "The Wanderers", "Nettuno"),
- (49, "Wanderer XLIX: The King & Queen of Hades", "wanderer-xlix-king-queen-hades", "The Wanderers", "The Binary / Plutone-Proserpina"),
-
- # ── Finale ─────────────────────────────────────────────────────────────
- (50, "The Eagle", "the-eagle", "", "Judgement / L'Angelo"),
- (51, "Divine Calculus", "divine-calculus", "", "The World / Il Mondo"),
-]
-
-# ── Earthman Minor Arcana ────────────────────────────────────────────────────
-# 4 suits × 14 cards. Suits: WANDS / CUPS / SWORDS / COINS
-# Court cards: Jack (11) / Cavalier (12) / Queen (13) / King (14)
-EARTHMAN_SUITS = [
- ("WANDS", "wands", "Ardor [Ar] — Fire"),
- ("CUPS", "cups", "Humor [Hm] — Water"),
- ("SWORDS","swords","Pneuma [Pn] — Air"),
- ("COINS", "coins", "Ossum [Om] — Stone"),
-]
-
-EARTHMAN_RANKS = [
- (1, "Ace", "ace"),
- (2, "2", "two"),
- (3, "3", "three"),
- (4, "4", "four"),
- (5, "5", "five"),
- (6, "6", "six"),
- (7, "7", "seven"),
- (8, "8", "eight"),
- (9, "9", "nine"),
- (10, "10", "ten"),
- (11, "Jack", "jack"),
- (12, "Cavalier", "cavalier"),
- (13, "Queen", "queen"),
- (14, "King", "king"),
-]
-
-
-def forward(apps, schema_editor):
- TarotCard = apps.get_model("epic", "TarotCard")
- DeckVariant = apps.get_model("epic", "DeckVariant")
-
- # ── 1. Create DeckVariant records ────────────────────────────────────
- fiorentine = DeckVariant.objects.create(
- name="Fiorentine Minchiate",
- slug="fiorentine-minchiate",
- card_count=78,
- description="Standard 78-card Minchiate deck. Alt / lite play mode.",
- is_default=False,
- )
- earthman = DeckVariant.objects.create(
- name="Earthman Deck",
- slug="earthman",
- card_count=108,
- description=(
- "Primary 108-card Earthman deck. "
- "52 Major Arcana (The Schiz through Divine Calculus) "
- "+ 56 Minor Arcana across Wands, Cups, Swords, Coins."
- ),
- is_default=True,
- )
-
- # ── 2. Backfill existing 78 Fiorentine cards ─────────────────────────
- TarotCard.objects.filter(deck_variant__isnull=True).update(
- deck_variant=fiorentine
- )
-
- # ── 3. Seed Earthman Major Arcana ────────────────────────────────────
- for number, name, slug, group, correspondence in EARTHMAN_MAJOR:
- TarotCard.objects.create(
- deck_variant=earthman,
- name=name,
- arcana="MAJOR",
- suit=None,
- number=number,
- slug=slug,
- group=group,
- correspondence=correspondence,
- keywords_upright=[],
- keywords_reversed=[],
- )
-
- # ── 4. Seed Earthman Minor Arcana ────────────────────────────────────
- for suit_code, suit_slug, _element in EARTHMAN_SUITS:
- for number, rank_name, rank_slug in EARTHMAN_RANKS:
- name = f"{rank_name} of {suit_code.capitalize()}"
- slug = f"{rank_slug}-of-{suit_slug}-em"
- TarotCard.objects.create(
- deck_variant=earthman,
- name=name,
- arcana="MINOR",
- suit=suit_code,
- number=number,
- slug=slug,
- group="",
- correspondence="",
- keywords_upright=[],
- keywords_reversed=[],
- )
-
-
-def reverse(apps, schema_editor):
- TarotCard = apps.get_model("epic", "TarotCard")
- DeckVariant = apps.get_model("epic", "DeckVariant")
-
- # Remove Earthman cards and clear FK from Fiorentine cards
- earthman = DeckVariant.objects.filter(slug="earthman").first()
- if earthman:
- TarotCard.objects.filter(deck_variant=earthman).delete()
-
- fiorentine = DeckVariant.objects.filter(slug="fiorentine-minchiate").first()
- if fiorentine:
- TarotCard.objects.filter(deck_variant=fiorentine).update(deck_variant=None)
-
- DeckVariant.objects.filter(slug__in=["earthman", "fiorentine-minchiate"]).delete()
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("epic", "0009_deckvariant_alter_tarotcard_options_and_more"),
- ]
-
- operations = [
- migrations.RunPython(forward, reverse_code=reverse),
- ]
diff --git a/src/apps/epic/migrations/0011_rename_earthman_court_cards.py b/src/apps/epic/migrations/0011_rename_earthman_court_cards.py
deleted file mode 100644
index dd09bd2..0000000
--- a/src/apps/epic/migrations/0011_rename_earthman_court_cards.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0012_rename_earthman_major_groups_and_pip_spellings.py b/src/apps/epic/migrations/0012_rename_earthman_major_groups_and_pip_spellings.py
deleted file mode 100644
index 6d199eb..0000000
--- a/src/apps/epic/migrations/0012_rename_earthman_major_groups_and_pip_spellings.py
+++ /dev/null
@@ -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 2–10
- (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 6–9)
- 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 18–20)
- 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 21–24)
- 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 25–36)
- 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 37–38)
- 37: ("Absolute Element 1: Time", "absolute-element-1-time"),
- 38: ("Absolute Element 2: Space", "absolute-element-2-space"),
- # Wanderers (cards 39–49)
- 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 2–10
- 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),
- ]
diff --git a/src/apps/epic/migrations/0013_earthman_coins_to_pentacles.py b/src/apps/epic/migrations/0013_earthman_coins_to_pentacles.py
deleted file mode 100644
index 7c84e18..0000000
--- a/src/apps/epic/migrations/0013_earthman_coins_to_pentacles.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0014_rename_earthman_popes_arabic_ordinals.py b/src/apps/epic/migrations/0014_rename_earthman_popes_arabic_ordinals.py
deleted file mode 100644
index 760d39a..0000000
--- a/src/apps/epic/migrations/0014_rename_earthman_popes_arabic_ordinals.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0015_rename_classical_element_earth_to_stone.py b/src/apps/epic/migrations/0015_rename_classical_element_earth_to_stone.py
deleted file mode 100644
index d4b7b5e..0000000
--- a/src/apps/epic/migrations/0015_rename_classical_element_earth_to_stone.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0016_reorder_earthman_popes.py b/src/apps/epic/migrations/0016_reorder_earthman_popes.py
deleted file mode 100644
index bb4a6f9..0000000
--- a/src/apps/epic/migrations/0016_reorder_earthman_popes.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0017_tableseat_significator_fk.py b/src/apps/epic/migrations/0017_tableseat_significator_fk.py
deleted file mode 100644
index 56233ce..0000000
--- a/src/apps/epic/migrations/0017_tableseat_significator_fk.py
+++ /dev/null
@@ -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'),
- ),
- ]
diff --git a/src/apps/epic/migrations/0018_alter_tarotcard_suit.py b/src/apps/epic/migrations/0018_alter_tarotcard_suit.py
deleted file mode 100644
index 5760b54..0000000
--- a/src/apps/epic/migrations/0018_alter_tarotcard_suit.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0019_rename_earthman_schiz_and_popes.py b/src/apps/epic/migrations/0019_rename_earthman_schiz_and_popes.py
deleted file mode 100644
index 2cbaae7..0000000
--- a/src/apps/epic/migrations/0019_rename_earthman_schiz_and_popes.py
+++ /dev/null
@@ -1,70 +0,0 @@
-"""
-Data migration: rename The Schiz (card 0) and the five Pope cards (cards 1–5)
-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),
- ]
diff --git a/src/apps/epic/migrations/0020_rename_earthman_pope_cards_2_5.py b/src/apps/epic/migrations/0020_rename_earthman_pope_cards_2_5.py
deleted file mode 100644
index 2a18b8e..0000000
--- a/src/apps/epic/migrations/0020_rename_earthman_pope_cards_2_5.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-Data migration: rename Pope cards 2–5 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),
- ]
diff --git a/src/apps/epic/migrations/0021_rename_earthman_major_arcana_batch_2.py b/src/apps/epic/migrations/0021_rename_earthman_major_arcana_batch_2.py
deleted file mode 100644
index d2022f2..0000000
--- a/src/apps/epic/migrations/0021_rename_earthman_major_arcana_batch_2.py
+++ /dev/null
@@ -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)),
- ]
diff --git a/src/apps/epic/migrations/0022_sig_reservation.py b/src/apps/epic/migrations/0022_sig_reservation.py
deleted file mode 100644
index fc4af3d..0000000
--- a/src/apps/epic/migrations/0022_sig_reservation.py
+++ /dev/null
@@ -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')],
- },
- ),
- ]
diff --git a/src/apps/epic/migrations/0023_tarotcard_icon_alter_tarotcard_arcana_and_more.py b/src/apps/epic/migrations/0023_tarotcard_icon_alter_tarotcard_arcana_and_more.py
deleted file mode 100644
index 241aaf4..0000000
--- a/src/apps/epic/migrations/0023_tarotcard_icon_alter_tarotcard_arcana_and_more.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0024_earthman_pentacles_to_crowns.py b/src/apps/epic/migrations/0024_earthman_pentacles_to_crowns.py
deleted file mode 100644
index 830072e..0000000
--- a/src/apps/epic/migrations/0024_earthman_pentacles_to_crowns.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0025_earthman_middle_arcana_and_major_icons.py b/src/apps/epic/migrations/0025_earthman_middle_arcana_and_major_icons.py
deleted file mode 100644
index 55b3d18..0000000
--- a/src/apps/epic/migrations/0025_earthman_middle_arcana_and_major_icons.py
+++ /dev/null
@@ -1,62 +0,0 @@
-"""
-Data migration: Earthman deck — court cards and major arcana icons.
-
- 1. Court cards (numbers 11–14, all suits): arcana "MINOR" → "MIDDLE"
- 2. Major arcana icons (stored in TarotCard.icon):
- 0 (Nomad) → fa-hat-cowboy-side
- 1 (Schizo) → fa-hat-wizard
- 2–51 (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),
- ]
diff --git a/src/apps/epic/migrations/0026_earthman_suit_renames_and_keywords.py b/src/apps/epic/migrations/0026_earthman_suit_renames_and_keywords.py
deleted file mode 100644
index 80f6f8e..0000000
--- a/src/apps/epic/migrations/0026_earthman_suit_renames_and_keywords.py
+++ /dev/null
@@ -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
- # 21–38: 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
- # 42–49: 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),
- ]
diff --git a/src/apps/epic/migrations/0027_tarotcard_cautions.py b/src/apps/epic/migrations/0027_tarotcard_cautions.py
deleted file mode 100644
index 9fc9a05..0000000
--- a/src/apps/epic/migrations/0027_tarotcard_cautions.py
+++ /dev/null
@@ -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 The Pervert when it'
- ' comes under dominion of The Occultist, which in turn'
- ' reverses into Pestilence.',
-
- 'This card will reverse into The Paranoiac when it'
- ' comes under dominion of The Despot, which in turn'
- ' reverses into War.',
-
- 'This card will reverse into The Neurotic when it'
- ' comes under dominion of The Capitalist, which in turn'
- ' reverses into Famine.',
-
- 'This card will reverse into The Suicidal when it'
- ' comes under dominion of The Fascist, which in turn'
- ' reverses into Death.',
-]
-
-
-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),
- ]
diff --git a/src/apps/epic/migrations/0028_alter_tarotcard_suit.py b/src/apps/epic/migrations/0028_alter_tarotcard_suit.py
deleted file mode 100644
index 84832ff..0000000
--- a/src/apps/epic/migrations/0028_alter_tarotcard_suit.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0029_fix_schizo_cautions.py b/src/apps/epic/migrations/0029_fix_schizo_cautions.py
deleted file mode 100644
index 48f0dea..0000000
--- a/src/apps/epic/migrations/0029_fix_schizo_cautions.py
+++ /dev/null
@@ -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 I. The Pervert when it'
- ' comes under dominion of II. The Occultist, which in turn'
- ' reverses into II. Pestilence.',
-
- 'This card will reverse into I. The Paranoiac when it'
- ' comes under dominion of III. The Despot, which in turn'
- ' reverses into III. War.',
-
- 'This card will reverse into I. The Neurotic when it'
- ' comes under dominion of IV. The Capitalist, which in turn'
- ' reverses into IV. Famine.',
-
- 'This card will reverse into I. The Suicidal when it'
- ' comes under dominion of V. The Fascist, which in turn'
- ' reverses into V. Death.',
-]
-
-
-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),
- ]
diff --git a/src/apps/epic/migrations/0030_sigreservation_seat_fk.py b/src/apps/epic/migrations/0030_sigreservation_seat_fk.py
deleted file mode 100644
index 2426ef2..0000000
--- a/src/apps/epic/migrations/0030_sigreservation_seat_fk.py
+++ /dev/null
@@ -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',
- ),
- ),
- ]
diff --git a/src/apps/epic/migrations/0031_sig_ready_sky_select.py b/src/apps/epic/migrations/0031_sig_ready_sky_select.py
deleted file mode 100644
index 5a4b8c0..0000000
--- a/src/apps/epic/migrations/0031_sig_ready_sky_select.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0032_astro_reference_tables.py b/src/apps/epic/migrations/0032_astro_reference_tables.py
deleted file mode 100644
index 2117838..0000000
--- a/src/apps/epic/migrations/0032_astro_reference_tables.py
+++ /dev/null
@@ -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'],
- },
- ),
- ]
diff --git a/src/apps/epic/migrations/0033_seed_astro_reference_tables.py b/src/apps/epic/migrations/0033_seed_astro_reference_tables.py
deleted file mode 100644
index 4113956..0000000
--- a/src/apps/epic/migrations/0033_seed_astro_reference_tables.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0034_character_model.py b/src/apps/epic/migrations/0034_character_model.py
deleted file mode 100644
index 38d5e09..0000000
--- a/src/apps/epic/migrations/0034_character_model.py
+++ /dev/null
@@ -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'],
- },
- ),
- ]
diff --git a/src/apps/epic/migrations/0035_earthman_deck_new_fields.py b/src/apps/epic/migrations/0035_earthman_deck_new_fields.py
deleted file mode 100644
index 8e33a97..0000000
--- a/src/apps/epic/migrations/0035_earthman_deck_new_fields.py
+++ /dev/null
@@ -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),
- ),
- ]
diff --git a/src/apps/epic/migrations/0036_earthman_deck_reseed.py b/src/apps/epic/migrations/0036_earthman_deck_reseed.py
deleted file mode 100644
index 2239caf..0000000
--- a/src/apps/epic/migrations/0036_earthman_deck_reseed.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0037_polestar_qualifiers.py b/src/apps/epic/migrations/0037_polestar_qualifiers.py
deleted file mode 100644
index a317a0e..0000000
--- a/src/apps/epic/migrations/0037_polestar_qualifiers.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0038_fix_correspondences.py b/src/apps/epic/migrations/0038_fix_correspondences.py
deleted file mode 100644
index 5c93c60..0000000
--- a/src/apps/epic/migrations/0038_fix_correspondences.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0039_fix_reversals.py b/src/apps/epic/migrations/0039_fix_reversals.py
deleted file mode 100644
index b31b189..0000000
--- a/src/apps/epic/migrations/0039_fix_reversals.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0040_rename_jovent_to_intent.py b/src/apps/epic/migrations/0040_rename_jovent_to_intent.py
deleted file mode 100644
index 452038b..0000000
--- a/src/apps/epic/migrations/0040_rename_jovent_to_intent.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/epic/migrations/0041_delete_stray_earthman_pentacles_courts.py b/src/apps/epic/migrations/0041_delete_stray_earthman_pentacles_courts.py
deleted file mode 100644
index fca616b..0000000
--- a/src/apps/epic/migrations/0041_delete_stray_earthman_pentacles_courts.py
+++ /dev/null
@@ -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),
- ]
diff --git a/src/apps/lyric/migrations/0001_initial.py b/src/apps/lyric/migrations/0001_initial.py
index cb9b75d..e2eb90f 100644
--- a/src/apps/lyric/migrations/0001_initial.py
+++ b/src/apps/lyric/migrations/0001_initial.py
@@ -1,6 +1,8 @@
-# Generated by Django 6.0 on 2026-02-20 00:48
+# Generated by Django 6.0 on 2026-04-28 00:59
+import django.db.models.deletion
import uuid
+from django.conf import settings
from django.db import migrations, models
@@ -9,9 +11,18 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
+ ('drama', '0001_initial'),
+ ('epic', '0001_initial'),
]
operations = [
+ migrations.CreateModel(
+ name='LoginToken',
+ fields=[
+ ('email', models.EmailField(max_length=254)),
+ ('uid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ],
+ ),
migrations.CreateModel(
name='User',
fields=[
@@ -19,18 +30,63 @@ class Migration(migrations.Migration):
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('email', models.EmailField(max_length=254, unique=True)),
+ ('username', models.CharField(blank=True, max_length=35, null=True, unique=True)),
+ ('searchable', models.BooleanField(default=False)),
+ ('palette', models.CharField(default='palette-default', max_length=32)),
+ ('stripe_customer_id', models.CharField(blank=True, max_length=255, null=True)),
+ ('ap_public_key', models.TextField(blank=True, default='')),
+ ('ap_private_key', models.TextField(blank=True, default='')),
+ ('sky_birth_dt', models.DateTimeField(blank=True, null=True)),
+ ('sky_birth_lat', models.DecimalField(blank=True, decimal_places=4, max_digits=9, null=True)),
+ ('sky_birth_lon', models.DecimalField(blank=True, decimal_places=4, max_digits=9, null=True)),
+ ('sky_birth_place', models.CharField(blank=True, max_length=255)),
+ ('sky_birth_tz', models.CharField(blank=True, max_length=64)),
+ ('sky_house_system', models.CharField(blank=True, default='O', max_length=1)),
+ ('sky_chart_data', models.JSONField(blank=True, null=True)),
('is_staff', models.BooleanField(default=False)),
('is_superuser', models.BooleanField(default=False)),
+ ('active_title', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='drama.note')),
+ ('equipped_deck', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='epic.deckvariant')),
+ ('unlocked_decks', models.ManyToManyField(blank=True, related_name='unlocked_by', to='epic.deckvariant')),
],
options={
'abstract': False,
},
),
+ migrations.CreateModel(
+ name='PaymentMethod',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('stripe_pm_id', models.CharField(max_length=255)),
+ ('last4', models.CharField(max_length=4)),
+ ('brand', models.CharField(max_length=32)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payment_methods', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
migrations.CreateModel(
name='Token',
fields=[
- ('email', models.EmailField(max_length=254)),
- ('uid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('token_type', models.CharField(choices=[('coin', 'Coin-on-a-String'), ('Free', 'Free Token'), ('tithe', 'Tithe Token'), ('pass', 'Backstage Pass'), ('carte', 'Carte Blanche')], max_length=8)),
+ ('expires_at', models.DateTimeField(blank=True, null=True)),
+ ('next_ready_at', models.DateTimeField(blank=True, null=True)),
+ ('slots_claimed', models.PositiveSmallIntegerField(blank=True, default=0)),
+ ('current_room', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='coin_tokens', to='epic.room')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='equipped_trinket',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='lyric.token'),
+ ),
+ migrations.CreateModel(
+ name='Wallet',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('writs', models.IntegerField(default=0)),
+ ('esteem', models.IntegerField(default=0)),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='wallet', to=settings.AUTH_USER_MODEL)),
],
),
]
diff --git a/src/apps/lyric/migrations/0002_user_searchable_user_username.py b/src/apps/lyric/migrations/0002_user_searchable_user_username.py
deleted file mode 100644
index fb4cd9c..0000000
--- a/src/apps/lyric/migrations/0002_user_searchable_user_username.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 6.0 on 2026-03-02 01:32
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0001_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='searchable',
- field=models.BooleanField(default=False),
- ),
- migrations.AddField(
- model_name='user',
- name='username',
- field=models.CharField(blank=True, max_length=35, null=True, unique=True),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0003_user_theme.py b/src/apps/lyric/migrations/0003_user_theme.py
deleted file mode 100644
index 318887d..0000000
--- a/src/apps/lyric/migrations/0003_user_theme.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 6.0 on 2026-03-02 04:15
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0002_user_searchable_user_username'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='theme',
- field=models.CharField(default='theme-default', max_length=32),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0004_remove_user_theme_user_palette.py b/src/apps/lyric/migrations/0004_remove_user_theme_user_palette.py
deleted file mode 100644
index d723d12..0000000
--- a/src/apps/lyric/migrations/0004_remove_user_theme_user_palette.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Generated by Django 6.0 on 2026-03-05 19:01
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0003_user_theme'),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name='user',
- name='theme',
- ),
- migrations.AddField(
- model_name='user',
- name='palette',
- field=models.CharField(default='palette-default', max_length=32),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0005_rename_logintoken.py b/src/apps/lyric/migrations/0005_rename_logintoken.py
deleted file mode 100644
index 8f09a20..0000000
--- a/src/apps/lyric/migrations/0005_rename_logintoken.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('lyric', '0004_remove_user_theme_user_palette'),
- ]
-
- operations = [
- migrations.RenameModel(
- old_name="Token",
- new_name="LoginToken",
- ),
- ]
diff --git a/src/apps/lyric/migrations/0006_token_wallet.py b/src/apps/lyric/migrations/0006_token_wallet.py
deleted file mode 100644
index f51bdc6..0000000
--- a/src/apps/lyric/migrations/0006_token_wallet.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Generated by Django 6.0 on 2026-03-08 18:19
-
-import django.db.models.deletion
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0005_rename_logintoken'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Token',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('token_type', models.CharField(choices=[('coin', 'Coin-on-a-String'), ('Free', 'Free Token'), ('tithe', 'Tithe Token')], max_length=8)),
- ('expires_at', models.DateTimeField(blank=True, null=True)),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to=settings.AUTH_USER_MODEL)),
- ],
- ),
- migrations.CreateModel(
- name='Wallet',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('writs', models.IntegerField(default=0)),
- ('esteem', models.IntegerField(default=0)),
- ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='wallet', to=settings.AUTH_USER_MODEL)),
- ],
- ),
- ]
diff --git a/src/apps/lyric/migrations/0007_user_stripe_customer_id_paymentmethod.py b/src/apps/lyric/migrations/0007_user_stripe_customer_id_paymentmethod.py
deleted file mode 100644
index d0c0419..0000000
--- a/src/apps/lyric/migrations/0007_user_stripe_customer_id_paymentmethod.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# Generated by Django 6.0 on 2026-03-08 20:26
-
-import django.db.models.deletion
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0006_token_wallet'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='stripe_customer_id',
- field=models.CharField(blank=True, max_length=255, null=True),
- ),
- migrations.CreateModel(
- name='PaymentMethod',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('stripe_pm_id', models.CharField(max_length=255)),
- ('last4', models.CharField(max_length=4)),
- ('brand', models.CharField(max_length=32)),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payment_methods', to=settings.AUTH_USER_MODEL)),
- ],
- ),
- ]
diff --git a/src/apps/lyric/migrations/0008_token_current_room_token_next_ready_at.py b/src/apps/lyric/migrations/0008_token_current_room_token_next_ready_at.py
deleted file mode 100644
index a3972a3..0000000
--- a/src/apps/lyric/migrations/0008_token_current_room_token_next_ready_at.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Generated by Django 6.0 on 2026-03-13 20:17
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('epic', '0001_initial'),
- ('lyric', '0007_user_stripe_customer_id_paymentmethod'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='token',
- name='current_room',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='coin_tokens', to='epic.room'),
- ),
- migrations.AddField(
- model_name='token',
- name='next_ready_at',
- field=models.DateTimeField(blank=True, null=True),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0009_alter_token_token_type.py b/src/apps/lyric/migrations/0009_alter_token_token_type.py
deleted file mode 100644
index 1433be3..0000000
--- a/src/apps/lyric/migrations/0009_alter_token_token_type.py
+++ /dev/null
@@ -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 = [
- ('lyric', '0008_token_current_room_token_next_ready_at'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='token',
- name='token_type',
- field=models.CharField(choices=[('coin', 'Coin-on-a-String'), ('Free', 'Free Token'), ('tithe', 'Tithe Token'), ('pass', 'Backstage Pass')], max_length=8),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0010_carte_blanche_token_type.py b/src/apps/lyric/migrations/0010_carte_blanche_token_type.py
deleted file mode 100644
index 1b9b5ff..0000000
--- a/src/apps/lyric/migrations/0010_carte_blanche_token_type.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 6.0 on 2026-03-15 23:42
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0009_alter_token_token_type'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='token',
- name='token_type',
- field=models.CharField(choices=[('coin', 'Coin-on-a-String'), ('Free', 'Free Token'), ('tithe', 'Tithe Token'), ('pass', 'Backstage Pass'), ('carte', 'Carte Blanche')], max_length=8),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0011_user_equipped_trinket_fk.py b/src/apps/lyric/migrations/0011_user_equipped_trinket_fk.py
deleted file mode 100644
index 20dcd83..0000000
--- a/src/apps/lyric/migrations/0011_user_equipped_trinket_fk.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 6.0 on 2026-03-15 23:44
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0010_carte_blanche_token_type'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='equipped_trinket',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='lyric.token'),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0012_carte_slots_claimed.py b/src/apps/lyric/migrations/0012_carte_slots_claimed.py
deleted file mode 100644
index 396244d..0000000
--- a/src/apps/lyric/migrations/0012_carte_slots_claimed.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 6.0 on 2026-03-16 03:28
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0011_user_equipped_trinket_fk'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='token',
- name='slots_claimed',
- field=models.PositiveSmallIntegerField(default=0),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0013_alter_token_slots_claimed.py b/src/apps/lyric/migrations/0013_alter_token_slots_claimed.py
deleted file mode 100644
index ceac2ce..0000000
--- a/src/apps/lyric/migrations/0013_alter_token_slots_claimed.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 6.0 on 2026-03-17 01:27
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0012_carte_slots_claimed'),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='token',
- name='slots_claimed',
- field=models.PositiveSmallIntegerField(blank=True, default=0),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0014_user_equipped_deck.py b/src/apps/lyric/migrations/0014_user_equipped_deck.py
deleted file mode 100644
index 1996f5a..0000000
--- a/src/apps/lyric/migrations/0014_user_equipped_deck.py
+++ /dev/null
@@ -1,20 +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', '0009_deckvariant_alter_tarotcard_options_and_more'),
- ('lyric', '0013_alter_token_slots_claimed'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='equipped_deck',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='epic.deckvariant'),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0015_user_unlocked_decks.py b/src/apps/lyric/migrations/0015_user_unlocked_decks.py
deleted file mode 100644
index be20c1f..0000000
--- a/src/apps/lyric/migrations/0015_user_unlocked_decks.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 6.0 on 2026-03-25 02:08
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('epic', '0010_seed_deck_variants_and_earthman'),
- ('lyric', '0014_user_equipped_deck'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='unlocked_decks',
- field=models.ManyToManyField(blank=True, related_name='unlocked_by', to='epic.deckvariant'),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0016_backfill_unlocked_decks.py b/src/apps/lyric/migrations/0016_backfill_unlocked_decks.py
deleted file mode 100644
index e4114c4..0000000
--- a/src/apps/lyric/migrations/0016_backfill_unlocked_decks.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from django.db import migrations
-
-
-def backfill_unlocked_decks(apps, schema_editor):
- User = apps.get_model("lyric", "User")
- DeckVariant = apps.get_model("epic", "DeckVariant")
- try:
- earthman = DeckVariant.objects.get(slug="earthman")
- except DeckVariant.DoesNotExist:
- return
- for user in User.objects.filter(unlocked_decks__isnull=True):
- user.unlocked_decks.add(earthman)
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("lyric", "0015_user_unlocked_decks"),
- ("epic", "0010_seed_deck_variants_and_earthman"),
- ]
-
- operations = [
- migrations.RunPython(backfill_unlocked_decks, migrations.RunPython.noop),
- ]
diff --git a/src/apps/lyric/migrations/0017_ap_keypair_fields.py b/src/apps/lyric/migrations/0017_ap_keypair_fields.py
deleted file mode 100644
index a392494..0000000
--- a/src/apps/lyric/migrations/0017_ap_keypair_fields.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 6.0 on 2026-04-02 19:12
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0016_backfill_unlocked_decks'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='ap_private_key',
- field=models.TextField(blank=True, default=''),
- ),
- migrations.AddField(
- model_name='user',
- name='ap_public_key',
- field=models.TextField(blank=True, default=''),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0018_user_sky_fields.py b/src/apps/lyric/migrations/0018_user_sky_fields.py
deleted file mode 100644
index 7468a17..0000000
--- a/src/apps/lyric/migrations/0018_user_sky_fields.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('lyric', '0017_ap_keypair_fields'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='sky_birth_dt',
- field=models.DateTimeField(blank=True, null=True),
- ),
- migrations.AddField(
- model_name='user',
- name='sky_birth_lat',
- field=models.DecimalField(blank=True, decimal_places=4, max_digits=9, null=True),
- ),
- migrations.AddField(
- model_name='user',
- name='sky_birth_lon',
- field=models.DecimalField(blank=True, decimal_places=4, max_digits=9, null=True),
- ),
- migrations.AddField(
- model_name='user',
- name='sky_birth_place',
- field=models.CharField(blank=True, max_length=255),
- ),
- migrations.AddField(
- model_name='user',
- name='sky_house_system',
- field=models.CharField(blank=True, default='O', max_length=1),
- ),
- migrations.AddField(
- model_name='user',
- name='sky_chart_data',
- field=models.JSONField(blank=True, null=True),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0019_sky_birth_tz.py b/src/apps/lyric/migrations/0019_sky_birth_tz.py
deleted file mode 100644
index 229e1e2..0000000
--- a/src/apps/lyric/migrations/0019_sky_birth_tz.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 6.0 on 2026-04-22 01:39
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('lyric', '0018_user_sky_fields'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='sky_birth_tz',
- field=models.CharField(blank=True, max_length=64),
- ),
- ]
diff --git a/src/apps/lyric/migrations/0020_add_active_title.py b/src/apps/lyric/migrations/0020_add_active_title.py
deleted file mode 100644
index d9db9d8..0000000
--- a/src/apps/lyric/migrations/0020_add_active_title.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Generated by Django 6.0 on 2026-04-23 05:37
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('drama', '0005_rename_recognition_to_note'),
- ('lyric', '0019_sky_birth_tz'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='user',
- name='active_title',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='drama.note'),
- ),
- ]
diff --git a/src/apps/tooltips/migrations/0001_initial.py b/src/apps/tooltips/migrations/0001_initial.py
index 877588a..79f9e89 100644
--- a/src/apps/tooltips/migrations/0001_initial.py
+++ b/src/apps/tooltips/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 6.0 on 2026-04-16 00:38
+# Generated by Django 6.0 on 2026-04-28 00:59
from django.db import migrations, models