diff --git a/data/v2/build.py b/data/v2/build.py index 87a6a3291..e9a3a5896 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -2212,6 +2212,26 @@ def csv_record_to_objects(info): build_generic((PokemonFormType,), "pokemon_form_types.csv", csv_record_to_objects) + def csv_record_to_objects(info): + yield PokemonFormTrigger(id=int(info[0]), name=info[1]) + + build_generic( + (PokemonFormTrigger,), "pokemon_form_triggers.csv", csv_record_to_objects + ) + + def csv_record_to_objects(info): + yield PokemonFormCondition( + pokemon_form_id=int(info[0]), + form_trigger_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..139a73620 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,, +2233,clefablite,44,0,80, +2234,victreebelite,44,0,80, +2235,starminite,44,0,80, +2236,dragoninite,44,0,80, +2237,meganiumite,44,0,80, +2238,feraligite,44,0,80, +2239,skarmorite,44,0,80, +2240,froslassite,44,0,80, +2241,heatranite,44,0,80, +2242,darkranite,44,0,80, +2243,emboarite,44,0,80, +2244,excadrite,44,0,80, +2245,scolipite,44,0,80, +2246,scraftinite,44,0,80, +2247,eelektrossite,44,0,80, +2248,chandelurite,44,0,80, +2249,chesnaughtite,44,0,80, +2250,delphoxite,44,0,80, +2251,greninjite,44,0,80, +2252,pyroarite,44,0,80, +2253,floettite,44,0,80, +2254,malamarite,44,0,80, +2255,barbaracite,44,0,80, +2256,dragalgite,44,0,80, +2257,hawluchanite,44,0,80, +2258,zygardite,44,0,80, +2259,drampanite,44,0,80, +2260,zeraorite,44,0,80, +2261,falinksite,44,0,80, +2262,raichunite-x,44,0,80, +2263,raichunite-y,44,0,80, +2264,chimechite,44,0,80, +2265,absolite-z,44,0,80, +2266,staraptite,44,0,80, +2267,garchompite-z,44,0,80, +2268,lucarionite-z,44,0,80, +2269,golurkite,44,0,80, +2270,meowsticite,44,0,80, +2271,crabominite,44,0,80, +2272,golisopite,44,0,80, +2273,magearnite,44,0,80, +2274,scovillainite,44,0,80, +2275,baxcalibrite,44,0,80, +2276,tatsugirinite,44,0,80, +2277,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..61302109e --- /dev/null +++ b/data/v2/csv/pokemon_form_conditions.csv @@ -0,0 +1,224 @@ +pokemon_form_id,form_trigger_id,trigger_item_id,trigger_ability_id,trigger_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,2233,, +10504,1,2234,, +10505,1,2235,, +10506,1,2236,, +10507,1,2237,, +10508,1,2238,, +10509,1,2239,, +10510,1,2240,, +10511,1,2243,, +10512,1,2244,, +10513,1,2245,, +10514,1,2246,, +10515,1,2247,, +10516,1,2248,, +10517,1,2249,, +10518,1,2250,, +10519,1,2251,, +10520,1,2252,, +10521,1,2253,, +10522,1,2254,, +10523,1,2255,, +10524,1,2256,, +10525,1,2257,, +10526,1,2258,, +10527,1,2259,, +10528,1,2261,, +10529,1,2262,, +10530,1,2263,, +10531,1,2264,, +10532,1,2265,, +10533,1,2266,, +10534,1,2267,, +10535,1,2268,, +10536,1,2241,, +10537,1,2242,, +10538,1,2269,, +10539,1,2270,, +10540,1,2271,, +10541,1,2272,, +10542,1,2273,, +10543,1,2273,, +10544,1,2260,, +10545,1,2274,, +10546,1,2277,, +10547,1,2276,, +10548,1,2276,, +10549,1,2276,, +10550,1,2275,, +10554,1,2270,, diff --git a/data/v2/csv/pokemon_form_trigger_prose.csv b/data/v2/csv/pokemon_form_trigger_prose.csv new file mode 100644 index 000000000..8cf955230 --- /dev/null +++ b/data/v2/csv/pokemon_form_trigger_prose.csv @@ -0,0 +1,7 @@ +pokemon_form_trigger_id,local_language_id,name +1,9,Held item +2,9,Consumed item +3,9,Key item +4,9,Ability +5,9,Gigantamax factor +6,9,Move diff --git a/data/v2/csv/pokemon_form_triggers.csv b/data/v2/csv/pokemon_form_triggers.csv new file mode 100644 index 000000000..6cc4d9488 --- /dev/null +++ b/data/v2/csv/pokemon_form_triggers.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_pokemonformtrigger_pokemonformcondition.py b/pokemon_v2/migrations/0029_pokemonformtrigger_pokemonformcondition.py new file mode 100644 index 000000000..8f13f6af3 --- /dev/null +++ b/pokemon_v2/migrations/0029_pokemonformtrigger_pokemonformcondition.py @@ -0,0 +1,93 @@ +# Generated by Django 5.2.10 on 2026-07-01 16:03 + +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="PokemonFormTrigger", + 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", + ), + ), + ( + "move", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="pokemon_v2.move", + ), + ), + ( + "pokemon_form", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s", + to="pokemon_v2.pokemonform", + ), + ), + ( + "form_trigger", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="pokemon_v2.pokemonformtrigger", + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/pokemon_v2/models.py b/pokemon_v2/models.py index a18f2a8c8..e8311991c 100644 --- a/pokemon_v2/models.py +++ b/pokemon_v2/models.py @@ -1774,6 +1774,19 @@ class PokemonFormSprites(HasPokemonForm): sprites = models.JSONField() +class PokemonFormTrigger(HasName): + pass + + +class PokemonFormCondition(HasPokemonForm): + form_trigger = models.ForeignKey(PokemonFormTrigger, 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..7460dc49b 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -3893,6 +3893,17 @@ class Meta: fields = ("name", "pokemon_name", "language") +class PokemonFormConditionSerializer(serializers.ModelSerializer): + trigger = serializers.CharField(source="form_trigger.name", read_only=True) + item = ItemSummarySerializer() + ability = AbilitySummarySerializer() + move = MoveSummarySerializer() + + class Meta: + model = PokemonFormCondition + fields = ("trigger", "item", "ability", "move") + + class PokemonFormDetailSerializer(serializers.ModelSerializer): pokemon = PokemonSummarySerializer() version_group = VersionGroupSummarySerializer() @@ -3900,6 +3911,9 @@ 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_conditions" + ) class Meta: model = PokemonForm @@ -3918,6 +3932,7 @@ class Meta: "form_names", "names", "types", + "trigger_conditions", ) @extend_schema_field( @@ -4083,6 +4098,49 @@ def get_pokemon_form_types(self, obj): return form_types + @extend_schema_field( + field={ + "type": "array", + "items": { + "type": "object", + "required": ["trigger", "name", "url"], + "properties": { + "trigger": { + "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_conditions(self, obj): + conditions = PokemonFormCondition.objects.filter(pokemon_form=obj) + conditions_data = PokemonFormConditionSerializer( + conditions, many=True, context=self.context + ).data + + triggers = [] + for condition in conditions_data: + trigger_value = condition.pop("trigger", None) + if not trigger_value: + continue + trigger = {"trigger": trigger_value} + 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..b49c99e4a 100644 --- a/pokemon_v2/tests.py +++ b/pokemon_v2/tests.py @@ -1619,6 +1619,31 @@ def setup_pokemon_form_data( return pokemon_form + @classmethod + def setup_pokemon_form_trigger_data(cls, name="frm trgr for pkmn frm"): + pokemon_form_trigger = PokemonFormTrigger(name=name) + pokemon_form_trigger.save() + return pokemon_form_trigger + + @classmethod + def setup_pokemon_form_condition_data( + cls, pokemon_form, form_trigger=None, item=None, ability=None, move=None + ): + form_trigger = form_trigger or cls.setup_pokemon_form_trigger_data( + name="frm trgr for pkmn frm" + ) + item = item or cls.setup_item_data(name="itm for pkmn frm") + + pokemon_form_condition = PokemonFormCondition( + pokemon_form=pokemon_form, + form_trigger=form_trigger, + 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 +5290,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 +5343,19 @@ def test_pokemon_form_api(self): "{}{}/type/{}/".format(TEST_HOST, API_V2, pokemon_form_type.type.pk), ) + self.assertEqual( + response.data["trigger_conditions"][0]["trigger"], + pokemon_form_condition.form_trigger.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")