diff --git a/data/v2/build.py b/data/v2/build.py index 87a6a3291..2c7b86c49 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -2212,6 +2212,21 @@ def csv_record_to_objects(info): build_generic((PokemonFormType,), "pokemon_form_types.csv", csv_record_to_objects) + def csv_record_to_objects(info): + yield PokemonFormMethod(id=int(info[0]), name=info[1]) + + build_generic((PokemonFormMethod,), "pokemon_form_methods.csv", csv_record_to_objects) + + def csv_record_to_objects(info): + yield PokemonFormCondition( + pokemon_form_id=int(info[0]), + form_method_id=int(info[1]), + item_id=int(info[2]) if info[2] != "" else None, + ability_id=int(info[3]) if info[3] != "" else None, + move_id=int(info[4]) if info[4] != "" else None, + ) + build_generic((PokemonFormCondition,), "pokemon_form_conditions.csv", csv_record_to_objects) + def csv_record_to_objects(info): yield PokemonGameIndex( pokemon_id=int(info[0]), version_id=int(info[1]), game_index=int(info[2]) diff --git a/data/v2/csv/items.csv b/data/v2/csv/items.csv index da4160b70..0eb2862b0 100644 --- a/data/v2/csv/items.csv +++ b/data/v2/csv/items.csv @@ -2174,4 +2174,49 @@ id,identifier,category_id,cost,fling_power,fling_effect_id 2229,laorigin-ball,34,0,, 2230,black-augurite,10,0,, 2231,peat-block,10,0,, -2232,metal-alloy,10,0,, \ No newline at end of file +2232,metal-alloy,10,0,, +2559,clefablite,44,0,80, +2560,victreebelite,44,0,80, +2561,starminite,44,0,80, +2562,dragoninite,44,0,80, +2563,meganiumite,44,0,80, +2564,feraligite,44,0,80, +2565,skarmorite,44,0,80, +2566,froslassite,44,0,80, +2567,heatranite,44,0,80, +2568,darkranite,44,0,80, +2569,emboarite,44,0,80, +2570,excadrite,44,0,80, +2571,scolipite,44,0,80, +2572,scraftinite,44,0,80, +2573,eelektrossite,44,0,80, +2574,chandelurite,44,0,80, +2575,chesnaughtite,44,0,80, +2576,delphoxite,44,0,80, +2577,greninjite,44,0,80, +2578,pyroarite,44,0,80, +2579,floettite,44,0,80, +2580,malamarite,44,0,80, +2581,barbaracite,44,0,80, +2582,dragalgite,44,0,80, +2583,hawluchanite,44,0,80, +2584,zygardite,44,0,80, +2585,drampanite,44,0,80, +2586,zeraorite,44,0,80, +2587,falinksite,44,0,80, +2635,raichunite-x,44,0,80, +2636,raichunite-y,44,0,80, +2637,chimechite,44,0,80, +2638,absolite-z,44,0,80, +2639,staraptite,44,0,80, +2640,garchompite-z,44,0,80, +2641,lucarionite-z,44,0,80, +2642,golurkite,44,0,80, +2643,meowsticite,44,0,80, +2644,crabominite,44,0,80, +2645,golisopite,44,0,80, +2646,magearnite,44,0,80, +2647,scovillainite,44,0,80, +2648,baxcalibrite,44,0,80, +2649,tatsugirinite,44,0,80, +2650,glimmoranite,44,0,80, diff --git a/data/v2/csv/pokemon_form_conditions.csv b/data/v2/csv/pokemon_form_conditions.csv new file mode 100644 index 000000000..8df92859a --- /dev/null +++ b/data/v2/csv/pokemon_form_conditions.csv @@ -0,0 +1,224 @@ +pokemon_form_id,form_method_id,item_id,ability_id,move_id +493,1,1662,, +681,4,,176, +718,1,884,, +741,2,889,, +1017,1,2102,, +10028,4,,59, +10029,4,,59, +10030,4,,59, +10038,4,,122, +10041,1,285,, +10042,1,289,, +10043,1,288,, +10044,1,277,, +10045,1,280,, +10046,1,275,, +10047,1,283,, +10048,1,287,, +10049,1,278,, +10050,1,282,, +10051,1,279,, +10052,1,281,, +10053,1,284,, +10054,1,286,, +10055,1,290,, +10056,1,276,, +10063,1,442,, +10063,1,1661,, +10064,3,444,, +10067,4,,161, +10074,6,,,547 +10075,1,563,, +10076,1,564,, +10077,1,565,, +10078,1,566,, +10079,3,681,, +10080,3,681,, +10081,3,681,, +10084,6,,,548 +10085,1,684,, +10125,4,,176, +10133,1,698,, +10134,1,699,, +10135,1,717,, +10136,1,700,, +10137,1,718,, +10138,1,695,, +10139,1,714,, +10140,1,710,, +10141,1,715,, +10142,1,711,, +10143,1,701,, +10144,1,702,, +10145,1,697,, +10146,1,709,, +10147,1,719,, +10148,1,705,, +10149,1,708,, +10150,1,703,, +10151,1,696,, +10152,1,720,, +10153,1,706,, +10154,1,704,, +10155,1,721,, +10156,1,707,, +10157,1,716,, +10158,1,722,, +10159,1,712,, +10160,1,713,, +10164,1,760,, +10165,1,761,, +10166,1,793,, +10167,1,794,, +10168,1,795,, +10169,1,796,, +10170,1,797,, +10171,1,798,, +10172,1,800,, +10173,1,801,, +10174,1,802,, +10175,1,803,, +10176,1,804,, +10177,1,805,, +10178,1,799,, +10179,1,468,, +10180,1,467,, +10188,3,806,, +10189,1,808,, +10190,1,809,, +10191,1,810,, +10192,1,811,, +10219,4,,210, +10220,1,884,, +10221,1,884,, +10222,4,,211, +10225,2,890,, +10226,2,891,, +10227,2,892,, +10229,4,,208, +10232,1,902,, +10233,1,903,, +10234,1,904,, +10235,1,905,, +10236,1,906,, +10237,1,907,, +10238,1,908,, +10239,1,909,, +10240,1,910,, +10241,1,911,, +10242,1,912,, +10243,1,913,, +10244,1,914,, +10245,1,915,, +10246,1,916,, +10247,1,917,, +10248,1,918,, +10255,4,,197, +10256,4,,197, +10257,4,,197, +10258,4,,197, +10259,4,,197, +10260,4,,197, +10261,4,,197, +10262,4,,209, +10264,4,,209, +10337,4,,161, +10340,1,884,, +10341,4,,241, +10342,4,,241, +10354,4,,248, +10356,4,,258, +10357,1,1161,, +10358,1,1162,, +10364,5,,, +10365,5,,, +10366,5,,, +10367,5,,, +10368,5,,, +10369,5,,, +10370,5,,, +10371,5,,, +10372,5,,, +10373,5,,, +10374,5,,, +10375,5,,, +10376,5,,, +10377,5,,, +10378,5,,, +10379,5,,, +10380,5,,, +10381,5,,, +10382,5,,, +10383,5,,, +10384,5,,, +10385,5,,, +10386,5,,, +10387,5,,, +10388,5,,, +10389,5,,, +10390,5,,, +10391,5,,, +10392,5,,, +10393,5,,, +10394,5,,, +10395,5,,, +10396,5,,, +10397,5,,, +10414,1,1659,, +10415,1,1660,, +10425,4,,278, +10442,1,2106,, +10443,1,2107,, +10444,1,2108,, +10445,4,,304, +10446,3,1669,, +10503,1,2559,, +10504,1,2560,, +10505,1,2561,, +10506,1,2562,, +10507,1,2563,, +10508,1,2564,, +10509,1,2565,, +10510,1,2566,, +10511,1,2569,, +10512,1,2570,, +10513,1,2571,, +10514,1,2572,, +10515,1,2573,, +10516,1,2574,, +10517,1,2575,, +10518,1,2576,, +10519,1,2577,, +10520,1,2578,, +10521,1,2579,, +10522,1,2580,, +10523,1,2581,, +10524,1,2582,, +10525,1,2583,, +10526,1,2584,, +10527,1,2585,, +10528,1,2587,, +10529,1,2635,, +10530,1,2636,, +10531,1,2637,, +10532,1,2638,, +10533,1,2639,, +10534,1,2640,, +10535,1,2641,, +10536,1,2567,, +10537,1,2568,, +10538,1,2642,, +10539,1,2643,, +10540,1,2644,, +10541,1,2645,, +10542,1,2646,, +10543,1,2646,, +10544,1,2586,, +10545,1,2647,, +10546,1,2650,, +10547,1,2649,, +10548,1,2649,, +10549,1,2649,, +10550,1,2648,, +10554,1,2643,, diff --git a/data/v2/csv/pokemon_form_methods.csv b/data/v2/csv/pokemon_form_methods.csv new file mode 100644 index 000000000..6cc4d9488 --- /dev/null +++ b/data/v2/csv/pokemon_form_methods.csv @@ -0,0 +1,7 @@ +id,identifier +1,held-item +2,consumed-item +3,key-item +4,ability +5,gigantamax-factor +6,move diff --git a/pokemon_v2/migrations/0029_pokemonformmethod_pokemonformcondition.py b/pokemon_v2/migrations/0029_pokemonformmethod_pokemonformcondition.py new file mode 100644 index 000000000..1bf808287 --- /dev/null +++ b/pokemon_v2/migrations/0029_pokemonformmethod_pokemonformcondition.py @@ -0,0 +1,37 @@ +# Generated by Django 5.2.10 on 2026-06-29 04:01 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pokemon_v2', '0028_pokemonevolution_evolved_form_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='PokemonFormMethod', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(db_index=True, max_length=200)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='PokemonFormCondition', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ability', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pokemon_v2.ability')), + ('item', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pokemon_v2.item')), + ('pokemon_form', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s', to='pokemon_v2.pokemonform')), + ('form_method', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pokemon_v2.pokemonformmethod')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/pokemon_v2/migrations/0030_pokemonformcondition_move.py b/pokemon_v2/migrations/0030_pokemonformcondition_move.py new file mode 100644 index 000000000..51e8397a6 --- /dev/null +++ b/pokemon_v2/migrations/0030_pokemonformcondition_move.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.10 on 2026-06-30 08:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pokemon_v2', '0029_pokemonformmethod_pokemonformcondition'), + ] + + operations = [ + migrations.AddField( + model_name='pokemonformcondition', + name='move', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pokemon_v2.move'), + ), + ] diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index a18f2a8c8..688b8167a 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -1774,6 +1774,17 @@ class PokemonFormSprites(HasPokemonForm): sprites = models.JSONField() +class PokemonFormMethod(HasName): + pass + + +class PokemonFormCondition(HasPokemonForm): + form_method = models.ForeignKey(PokemonFormMethod, on_delete=models.CASCADE) + item = models.ForeignKey(Item, blank=True, null=True, on_delete=models.CASCADE) + ability = models.ForeignKey(Ability, blank=True, null=True, on_delete=models.CASCADE) + move = models.ForeignKey(Move, blank=True, null=True, on_delete=models.CASCADE) + + class PokemonGameIndex(HasPokemon, HasGameIndex, HasVersion): pass diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index 3e87a5661..506456509 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -3892,6 +3892,15 @@ class Meta: model = PokemonFormName fields = ("name", "pokemon_name", "language") +class PokemonFormTriggerSerializer(serializers.ModelSerializer): + method = serializers.CharField(source="form_method.name", read_only=True) + item = ItemSummarySerializer() + ability = AbilitySummarySerializer() + move = MoveSummarySerializer() + + class Meta: + model = PokemonFormCondition + fields = ("method", "item", "ability", "move") class PokemonFormDetailSerializer(serializers.ModelSerializer): pokemon = PokemonSummarySerializer() @@ -3900,6 +3909,7 @@ class PokemonFormDetailSerializer(serializers.ModelSerializer): form_names = serializers.SerializerMethodField("get_pokemon_form_names") names = serializers.SerializerMethodField("get_pokemon_form_pokemon_names") types = serializers.SerializerMethodField("get_pokemon_form_types") + trigger_conditions = serializers.SerializerMethodField("get_pokemon_form_triggers") class Meta: model = PokemonForm @@ -3918,6 +3928,7 @@ class Meta: "form_names", "names", "types", + "trigger_conditions", ) @extend_schema_field( @@ -4083,6 +4094,52 @@ def get_pokemon_form_types(self, obj): return form_types + @extend_schema_field( + field={ + "type": "array", + "items": { + "type": "object", + "required": ["method", "name", "url"], + "properties": { + "method": { + "type": "string", + "examples": ["held-item"], + }, + "name": { + "type": "string", + "examples": ["venusaurite"], + }, + "url": { + "type": "string", + "format": "uri", + "examples": ["https://pokeapi.co/api/v2/item/698/"], + }, + }, + }, + } + ) + def get_pokemon_form_triggers(self, obj): + conditions = PokemonFormCondition.objects.filter( + pokemon_form=obj + ) + conditions_data = PokemonFormTriggerSerializer( + conditions, many=True, context=self.context + ).data + + triggers = [] + for condition in conditions_data: + method = condition.pop("method", None) + if not method: + continue + trigger = {"method": method} + for value in condition.values(): + if value: + trigger.update(value) + break + triggers.append(trigger) + return triggers + + ################################# # POKEMON HABITAT SERIALIZERS # diff --git a/pokemon_v2/tests.py b/pokemon_v2/tests.py index 57086e188..bb97abb03 100644 --- a/pokemon_v2/tests.py +++ b/pokemon_v2/tests.py @@ -1619,6 +1619,21 @@ def setup_pokemon_form_data( return pokemon_form + @classmethod + def setup_pokemon_form_method_data(cls, name="frm mthd for pkmn frm"): + pokemon_form_method = PokemonFormMethod(name=name) + pokemon_form_method.save() + return pokemon_form_method + + @classmethod + def setup_pokemon_form_condition_data(cls, pokemon_form, form_method=None, item=None, ability=None, move=None): + form_method = form_method or cls.setup_pokemon_form_method_data(name="frm mthd for pkmn frm") + item = item or cls.setup_item_data(name="itm for pkmn frm") + + pokemon_form_condition = PokemonFormCondition(pokemon_form=pokemon_form, form_method=form_method, item=item, ability=ability, move=move) + pokemon_form_condition.save() + return pokemon_form_condition + @classmethod def setup_pokemon_ability_data(cls, pokemon, ability=None, is_hidden=False, slot=1): ability = ability or cls.setup_ability_data(name="ablty for pkmn") @@ -5265,6 +5280,7 @@ def test_pokemon_form_api(self): ) pokemon_form_sprites = self.setup_pokemon_form_sprites_data(pokemon_form) pokemon_form_type = self.setup_pokemon_form_type_data(pokemon_form) + pokemon_form_condition = self.setup_pokemon_form_condition_data(pokemon_form) response = self.client.get( "{}/pokemon-form/{}/".format(API_V2, pokemon_form.pk), @@ -5317,6 +5333,10 @@ def test_pokemon_form_api(self): "{}{}/type/{}/".format(TEST_HOST, API_V2, pokemon_form_type.type.pk), ) + self.assertEqual(response.data["trigger_conditions"][0]["method"], pokemon_form_condition.form_method.name) + self.assertEqual(response.data["trigger_conditions"][0]["name"], pokemon_form_condition.item.name) + self.assertEqual(response.data["trigger_conditions"][0]["url"], "{}{}/item/{}/".format(TEST_HOST, API_V2, pokemon_form_condition.item.pk)) + # Evolution test def test_evolution_trigger_api(self): evolution_trigger = self.setup_evolution_trigger_data(name="base evltn trgr")