diff --git a/api_v2/migrations/0016_alter_creaturetrait_parent.py b/api_v2/migrations/0016_alter_creaturetrait_parent.py new file mode 100644 index 00000000..3ef7df3f --- /dev/null +++ b/api_v2/migrations/0016_alter_creaturetrait_parent.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.2 on 2024-11-17 11:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api_v2', '0015_creature_environments'), + ] + + operations = [ + migrations.AlterField( + model_name='creaturetrait', + name='parent', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='traits', to='api_v2.creature'), + ), + ] diff --git a/api_v2/models/creature.py b/api_v2/models/creature.py index f151ec3c..a22deed3 100644 --- a/api_v2/models/creature.py +++ b/api_v2/models/creature.py @@ -291,8 +291,8 @@ class CreatureTrait(Modification): It inherits from modification, which is an abstract concept. """ key = key_field() - parent = models.ForeignKey('Creature', on_delete=models.CASCADE) - + parent = models.ForeignKey(Creature, on_delete=models.CASCADE, related_name="traits") + class CreatureSet(HasName, FromDocument): """Set that the creature belongs to.""" diff --git a/api_v2/serializers/__init__.py b/api_v2/serializers/__init__.py index 3155efd7..6a6c4ca4 100644 --- a/api_v2/serializers/__init__.py +++ b/api_v2/serializers/__init__.py @@ -24,6 +24,7 @@ from .creature import CreatureSerializer from .creature import CreatureTypeSerializer from .creature import CreatureSetSerializer +from .creature import CreatureTraitSerializer from .damagetype import DamageTypeSerializer diff --git a/api_v2/serializers/creature.py b/api_v2/serializers/creature.py index 57003bc8..0d8fc06c 100644 --- a/api_v2/serializers/creature.py +++ b/api_v2/serializers/creature.py @@ -47,6 +47,14 @@ class Meta: fields = '__all__' +class CreatureTraitSerializer(GameContentSerializer): + '''Serializer for the Creature Trait object''' + key = serializers.ReadOnlyField() + + class Meta: + model = models.CreatureTrait + fields = '__all__' + class CreatureSerializer(GameContentSerializer): '''The serializer for the Creature object.''' @@ -58,6 +66,7 @@ class CreatureSerializer(GameContentSerializer): skill_bonuses = serializers.SerializerMethodField() skill_bonuses_all = serializers.SerializerMethodField() actions = CreatureActionSerializer(many=True, context={'request': {}}) + traits = CreatureTraitSerializer(many=True, read_only=True) speed = serializers.SerializerMethodField() speed_all = serializers.SerializerMethodField() challenge_rating_text = serializers.SerializerMethodField() @@ -109,6 +118,7 @@ class Meta: 'tremorsense_range', 'truesight_range', 'actions', + 'traits', 'creaturesets', 'environments' ] diff --git a/api_v2/tests/responses/TestObjects.test_creature_ancient_example.approved.json b/api_v2/tests/responses/TestObjects.test_creature_ancient_example.approved.json index aaa7bf2e..461d0fe4 100644 --- a/api_v2/tests/responses/TestObjects.test_creature_ancient_example.approved.json +++ b/api_v2/tests/responses/TestObjects.test_creature_ancient_example.approved.json @@ -249,6 +249,16 @@ "walk": 40.0 }, "subcategory": "Dragons, Chromatic", + "traits": [ + { + "desc": "If the dragon fails a saving throw, it can choose to succeed instead.", + "key": "srd_ancient-red-dragon_legendary-resistance-3day", + "name": "Legendary Resistance (3/Day)", + "parent": "http://localhost:8000/v2/creatures/srd_ancient-red-dragon/", + "type": null, + "url": "http://localhost:8000/v2/creaturetraits/srd_ancient-red-dragon_legendary-resistance-3day/" + } + ], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/dragon/", diff --git a/api_v2/tests/responses/TestObjects.test_creature_goblin_example.approved.json b/api_v2/tests/responses/TestObjects.test_creature_goblin_example.approved.json index 8aec6a2b..cffb7d92 100644 --- a/api_v2/tests/responses/TestObjects.test_creature_goblin_example.approved.json +++ b/api_v2/tests/responses/TestObjects.test_creature_goblin_example.approved.json @@ -265,6 +265,16 @@ "walk": 30.0 }, "subcategory": null, + "traits": [ + { + "desc": "The goblin can take the Disengage or Hide action as a bonus action on each of its turns.", + "key": "srd_goblin_nimble-escape", + "name": "Nimble Escape", + "parent": "http://localhost:8000/v2/creatures/srd_goblin/", + "type": null, + "url": "http://localhost:8000/v2/creaturetraits/srd_goblin_nimble-escape/" + } + ], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/humanoid/", diff --git a/api_v2/tests/responses/TestObjects.test_creature_guard_example.approved.json b/api_v2/tests/responses/TestObjects.test_creature_guard_example.approved.json index faf91bec..20b11532 100644 --- a/api_v2/tests/responses/TestObjects.test_creature_guard_example.approved.json +++ b/api_v2/tests/responses/TestObjects.test_creature_guard_example.approved.json @@ -363,6 +363,7 @@ "walk": 30.0 }, "subcategory": null, + "traits": [], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/humanoid/", diff --git a/api_v2/tests/responses/TestObjects.test_creatureset_example.approved.json b/api_v2/tests/responses/TestObjects.test_creatureset_example.approved.json index 89c50a45..7db6f595 100644 --- a/api_v2/tests/responses/TestObjects.test_creatureset_example.approved.json +++ b/api_v2/tests/responses/TestObjects.test_creatureset_example.approved.json @@ -150,6 +150,7 @@ "walk": 50.0 }, "subcategory": null, + "traits": [], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -271,6 +272,7 @@ "walk": 0.0 }, "subcategory": null, + "traits": [], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -416,6 +418,7 @@ "walk": 40.0 }, "subcategory": null, + "traits": [], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -593,6 +596,16 @@ "walk": 40.0 }, "subcategory": null, + "traits": [ + { + "desc": "If the elephant moves at least 20 ft. straight toward a creature and then hits it with a gore attack on the same turn, that target must succeed on a DC 12 Strength saving throw or be knocked prone. If the target is prone, the elephant can make one stomp attack against it as a bonus action.", + "key": "srd_elephant_trampling-charge", + "name": "Trampling Charge", + "parent": "http://localhost:8000/v2/creatures/srd_elephant/", + "type": null, + "url": "http://localhost:8000/v2/creaturetraits/srd_elephant_trampling-charge/" + } + ], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -758,6 +771,16 @@ "walk": 40.0 }, "subcategory": null, + "traits": [ + { + "desc": "The mastiff has advantage on Wisdom (Perception) checks that rely on hearing or smell.", + "key": "srd_mastiff_keen-hearing-and-smell", + "name": "Keen Hearing and Smell", + "parent": "http://localhost:8000/v2/creatures/srd_mastiff/", + "type": null, + "url": "http://localhost:8000/v2/creaturetraits/srd_mastiff_keen-hearing-and-smell/" + } + ], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -923,6 +946,24 @@ "walk": 40.0 }, "subcategory": null, + "traits": [ + { + "desc": "The mule is considered to be a Large animal for the purpose of determining its carrying capacity.", + "key": "srd_mule_beast-of-burden", + "name": "Beast of Burden", + "parent": "http://localhost:8000/v2/creatures/srd_mule/", + "type": null, + "url": "http://localhost:8000/v2/creaturetraits/srd_mule_beast-of-burden/" + }, + { + "desc": "The mule has advantage on Strength and Dexterity saving throws made against effects that would knock it prone.", + "key": "srd_mule_sure-footed", + "name": "Sure-Footed", + "parent": "http://localhost:8000/v2/creatures/srd_mule/", + "type": null, + "url": "http://localhost:8000/v2/creaturetraits/srd_mule_sure-footed/" + } + ], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -1078,6 +1119,7 @@ "walk": 40.0 }, "subcategory": null, + "traits": [], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -1233,6 +1275,7 @@ "walk": 60.0 }, "subcategory": null, + "traits": [], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", @@ -1378,6 +1421,16 @@ "walk": 60.0 }, "subcategory": null, + "traits": [ + { + "desc": "If the horse moves at least 20 ft. straight toward a creature and then hits it with a hooves attack on the same turn, that target must succeed on a DC 14 Strength saving throw or be knocked prone. If the target is prone, the horse can make another attack with its hooves against it as a bonus action.", + "key": "srd_warhorse_trampling-charge", + "name": "Trampling Charge", + "parent": "http://localhost:8000/v2/creatures/srd_warhorse/", + "type": null, + "url": "http://localhost:8000/v2/creaturetraits/srd_warhorse_trampling-charge/" + } + ], "tremorsense_range": null, "truesight_range": null, "type": "http://localhost:8000/v2/creaturetypes/beast/", diff --git a/api_v2/urls.py b/api_v2/urls.py index 22e8987e..bb6caaeb 100644 --- a/api_v2/urls.py +++ b/api_v2/urls.py @@ -34,6 +34,7 @@ router.register(r'skills',views.SkillViewSet) router.register(r'rules', views.RuleViewSet) router.register(r'rulesets', views.RuleSetViewSet) +router.register(r'creaturetraits', views.CreatureTraitViewSet) search_router = routers.DefaultRouter() search_router.register('',views.SearchResultViewSet, basename='search') diff --git a/api_v2/views/__init__.py b/api_v2/views/__init__.py index 38066678..d004574c 100644 --- a/api_v2/views/__init__.py +++ b/api_v2/views/__init__.py @@ -5,6 +5,7 @@ from .creature import CreatureFilterSet, CreatureViewSet from .creature import CreatureTypeViewSet from .creature import CreatureSetViewSet +from .creature import CreatureTraitViewSet from .document import DocumentViewSet from .document import GameSystemViewSet diff --git a/api_v2/views/creature.py b/api_v2/views/creature.py index 4958b4a0..bb7289df 100644 --- a/api_v2/views/creature.py +++ b/api_v2/views/creature.py @@ -81,7 +81,8 @@ def setup_eager_loading(queryset, action, depth): prefetches = [ 'creatureaction_set', 'condition_immunities', 'damage_immunities', 'damage_vulnerabilities', 'damage_resistances', 'environments', - 'document' + 'document', 'traits', 'document', 'document__publisher', 'document__gamesystem', + 'document__licenses', 'languages__document' ] if depth >= 2: @@ -130,3 +131,8 @@ class CreatureSetViewSet(viewsets.ReadOnlyModelViewSet): queryset = models.CreatureSet.objects.all().order_by('pk') serializer_class = serializers.CreatureSetSerializer filterset_class = CreatureSetFilterSet + + +class CreatureTraitViewSet(viewsets.ReadOnlyModelViewSet): + queryset = models.CreatureTrait.objects.all().order_by('pk') + serializer_class = serializers.CreatureTraitSerializer \ No newline at end of file