Refactors card styling by moving color data to the `CardSet` model, removing the `Card.style` and `Pack.hex_color` fields. - Adds `hex_color` to `CardSet` and a `CardSetColorMapping` model to populate it during import. - Card importer now uses correct filename regexes to identify sets and apply color mappings. - Card badge styling is now derived from the `CardSet`'s color, simplifying the data model. Replaces the old clunky card multiselect with a dynamic Alpine.js component for an improved user experience. - Introduces an API endpoint (`cards/api/search/`) for asynchronous searching. - Provides a modern search-as-you-type interface with a `<noscript>` fallback.
771 lines
28 KiB
Python
771 lines
28 KiB
Python
# Generated by Django 5.1 on 2025-06-20 07:14
|
|
|
|
import django.db.models.deletion
|
|
import parler.fields
|
|
import parler.models
|
|
from django.db import migrations, models
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
|
|
initial = True
|
|
|
|
dependencies = []
|
|
|
|
operations = [
|
|
migrations.CreateModel(
|
|
name="Ability",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Ability",
|
|
"verbose_name_plural": "Abilities",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="Attack",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
(
|
|
"damage",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Damage string, e.g., '40', '20x', '80+'.",
|
|
max_length=10,
|
|
null=True,
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Attack",
|
|
"verbose_name_plural": "Attacks",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="CardSet",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.CharField(
|
|
help_text="The ID for the set, e.g., 'A1', 'A1a'.",
|
|
max_length=3,
|
|
primary_key=True,
|
|
serialize=False,
|
|
),
|
|
),
|
|
(
|
|
"file_name",
|
|
models.CharField(
|
|
help_text="Original name of the JSON file, e.g., 'a1-genetic-apex.json'.",
|
|
max_length=32,
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Card Set",
|
|
"verbose_name_plural": "Card Sets",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="CardSetColorMapping",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
(
|
|
"cardset_id",
|
|
models.CharField(
|
|
help_text="The cardset ID to match (e.g., 'A1').",
|
|
max_length=10,
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"hex_color",
|
|
models.CharField(
|
|
help_text="The hex color code to use for this cardset.",
|
|
max_length=9,
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Cardset Color Mapping",
|
|
"verbose_name_plural": "Cardset Color Mappings",
|
|
"ordering": ["cardset_id"],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name="CardType",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Card Type",
|
|
"verbose_name_plural": "Card Types",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="Energy",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Energy",
|
|
"verbose_name_plural": "Energies",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="Rarity",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
("icon", models.CharField(max_length=12)),
|
|
("level", models.PositiveIntegerField()),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Rarity",
|
|
"verbose_name_plural": "Rarities",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="RarityMapping",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
(
|
|
"original_name",
|
|
models.CharField(
|
|
help_text="The rarity name as it appears in the import source (e.g., JSON file).",
|
|
max_length=255,
|
|
unique=True,
|
|
),
|
|
),
|
|
(
|
|
"mapped_name",
|
|
models.CharField(
|
|
help_text="The standardized rarity name to use in the system.",
|
|
max_length=32,
|
|
),
|
|
),
|
|
(
|
|
"icon",
|
|
models.CharField(
|
|
help_text="The icon associated with this rarity.", max_length=12
|
|
),
|
|
),
|
|
(
|
|
"level",
|
|
models.PositiveIntegerField(
|
|
help_text="The level or order of this rarity."
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
],
|
|
options={
|
|
"verbose_name": "Rarity Mapping",
|
|
"verbose_name_plural": "Rarity Mappings",
|
|
"ordering": ["original_name"],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name="AttackCost",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"quantity",
|
|
models.PositiveIntegerField(
|
|
default=1,
|
|
help_text="Quantity of this energy type required for the attack.",
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
(
|
|
"attack",
|
|
models.ForeignKey(
|
|
on_delete=django.db.models.deletion.CASCADE, to="cards.attack"
|
|
),
|
|
),
|
|
(
|
|
"energy",
|
|
models.ForeignKey(
|
|
on_delete=django.db.models.deletion.CASCADE, to="cards.energy"
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Attack Cost",
|
|
"verbose_name_plural": "Attack Costs",
|
|
"unique_together": {("attack", "energy")},
|
|
},
|
|
),
|
|
migrations.AddField(
|
|
model_name="attack",
|
|
name="energy_cost",
|
|
field=models.ManyToManyField(
|
|
related_name="attacks", through="cards.AttackCost", to="cards.energy"
|
|
),
|
|
),
|
|
migrations.CreateModel(
|
|
name="Pack",
|
|
fields=[
|
|
("id", models.AutoField(primary_key=True, serialize=False)),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
(
|
|
"cardset",
|
|
models.ForeignKey(
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="packs",
|
|
to="cards.cardset",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Pack",
|
|
"verbose_name_plural": "Packs",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="Card",
|
|
fields=[
|
|
("cardnum", models.AutoField(primary_key=True, serialize=False)),
|
|
(
|
|
"id",
|
|
models.CharField(
|
|
db_index=True,
|
|
help_text="The unique ID from the JSON source, cardset-cardnum (e.g., 'a1-001').",
|
|
max_length=10,
|
|
),
|
|
),
|
|
(
|
|
"checksum",
|
|
models.CharField(
|
|
blank=True,
|
|
db_index=True,
|
|
help_text="SHA256 checksum of the card data.",
|
|
max_length=64,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"health",
|
|
models.PositiveIntegerField(
|
|
blank=True, help_text="HP of the Pokémon.", null=True
|
|
),
|
|
),
|
|
(
|
|
"retreat_cost",
|
|
models.PositiveIntegerField(
|
|
blank=True,
|
|
help_text="The number of retreat cost for the card.",
|
|
null=True,
|
|
),
|
|
),
|
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
("updated_at", models.DateTimeField(auto_now=True)),
|
|
("deleted_at", models.DateTimeField(blank=True, null=True)),
|
|
(
|
|
"abilities",
|
|
models.ManyToManyField(
|
|
blank=True, related_name="cards", to="cards.ability"
|
|
),
|
|
),
|
|
(
|
|
"attacks",
|
|
models.ManyToManyField(related_name="cards", to="cards.attack"),
|
|
),
|
|
(
|
|
"cardset",
|
|
models.ForeignKey(
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="cards",
|
|
to="cards.cardset",
|
|
),
|
|
),
|
|
(
|
|
"card_type",
|
|
models.ForeignKey(
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="cards",
|
|
to="cards.cardtype",
|
|
),
|
|
),
|
|
(
|
|
"pkmn_type",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="cards_pkmn_type",
|
|
to="cards.energy",
|
|
),
|
|
),
|
|
(
|
|
"weakness_type",
|
|
models.ForeignKey(
|
|
blank=True,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="cards_weakness_type",
|
|
to="cards.energy",
|
|
),
|
|
),
|
|
(
|
|
"packs",
|
|
models.ManyToManyField(related_name="cards", to="cards.pack"),
|
|
),
|
|
(
|
|
"rarity",
|
|
models.ForeignKey(
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="cards",
|
|
to="cards.rarity",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Card",
|
|
"verbose_name_plural": "Cards",
|
|
},
|
|
bases=(parler.models.TranslatableModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="AbilityTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(
|
|
help_text="The name of the ability.", max_length=32
|
|
),
|
|
),
|
|
(
|
|
"effect",
|
|
models.TextField(help_text="Description of the ability's effect."),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.ability",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Ability Translation",
|
|
"db_table": "cards_ability_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="AttackTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(
|
|
help_text="The name of the attack.", max_length=32
|
|
),
|
|
),
|
|
(
|
|
"effect",
|
|
models.TextField(help_text="Description of the attack's effect."),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.attack",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Attack Translation",
|
|
"db_table": "cards_attack_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="CardSetTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(
|
|
help_text="The full name of the set, e.g., 'Genetic Apex'.",
|
|
max_length=32,
|
|
),
|
|
),
|
|
(
|
|
"hex_color",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="The hex color code associated with this card set.",
|
|
max_length=9,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.cardset",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Card Set Translation",
|
|
"db_table": "cards_cardset_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="CardTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(help_text="The name of the card.", max_length=32),
|
|
),
|
|
(
|
|
"evolves_from_name",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="Name of the Pokémon this card evolves from.",
|
|
max_length=32,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.card",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Card Translation",
|
|
"db_table": "cards_card_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="CardTypeTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(
|
|
help_text="The name of the card type.", max_length=32
|
|
),
|
|
),
|
|
(
|
|
"subtype",
|
|
models.CharField(
|
|
blank=True,
|
|
help_text="The subtype of the card type.",
|
|
max_length=32,
|
|
null=True,
|
|
),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.cardtype",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Card Type Translation",
|
|
"db_table": "cards_cardtype_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="EnergyTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(
|
|
help_text="The name of the energy.", max_length=32
|
|
),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.energy",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Energy Translation",
|
|
"db_table": "cards_energy_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="PackTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"full_name",
|
|
models.CharField(
|
|
help_text="The full name of the pack, e.g., 'Genetic Apex: Mewtwo'.",
|
|
max_length=32,
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(
|
|
help_text="The pack name itself, e.g., 'Mewtwo'.", max_length=32
|
|
),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.pack",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Pack Translation",
|
|
"db_table": "cards_pack_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
migrations.CreateModel(
|
|
name="RarityTranslation",
|
|
fields=[
|
|
(
|
|
"id",
|
|
models.BigAutoField(
|
|
auto_created=True,
|
|
primary_key=True,
|
|
serialize=False,
|
|
verbose_name="ID",
|
|
),
|
|
),
|
|
(
|
|
"language_code",
|
|
models.CharField(
|
|
db_index=True, max_length=15, verbose_name="Language"
|
|
),
|
|
),
|
|
(
|
|
"name",
|
|
models.CharField(
|
|
help_text="The name of the rarity.", max_length=32
|
|
),
|
|
),
|
|
(
|
|
"master",
|
|
parler.fields.TranslationsForeignKey(
|
|
editable=False,
|
|
null=True,
|
|
on_delete=django.db.models.deletion.CASCADE,
|
|
related_name="translations",
|
|
to="cards.rarity",
|
|
),
|
|
),
|
|
],
|
|
options={
|
|
"verbose_name": "Rarity Translation",
|
|
"db_table": "cards_rarity_translation",
|
|
"db_tablespace": "",
|
|
"managed": True,
|
|
"default_permissions": (),
|
|
"unique_together": {("language_code", "master")},
|
|
},
|
|
bases=(parler.models.TranslatedFieldsModelMixin, models.Model),
|
|
),
|
|
]
|