Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"conditions": "http://localhost:8000/v2/conditions/",
"documents": "http://localhost:8000/v2/documents/",
"feats": "http://localhost:8000/v2/feats/",
"magicitems": "http://localhost:8000/v1/magicitems/",
"magicitems": "http://localhost:8000/v2/magicitems/",
"monsters": "http://localhost:8000/v1/monsters/",
"planes": "http://localhost:8000/v1/planes/",
"races": "http://localhost:8000/v1/races/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generated by Django 5.2.11 on 2026-03-06 23:41

import django.core.validators
import django.db.models.deletion
from decimal import Decimal
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api_v2', '0071_convert_distance_fields_to_integer'),
]

operations = [
migrations.AlterModelOptions(
name='item',
options={},
),
migrations.RemoveField(
model_name='item',
name='attunement_detail',
),
migrations.RemoveField(
model_name='item',
name='rarity',
),
migrations.RemoveField(
model_name='item',
name='requires_attunement',
),
migrations.AlterField(
model_name='item',
name='damage_immunities',
field=models.ManyToManyField(related_name='%(class)s_damage_immunities', to='api_v2.damagetype'),
),
migrations.AlterField(
model_name='item',
name='damage_resistances',
field=models.ManyToManyField(related_name='%(class)s_damage_resistances', to='api_v2.damagetype'),
),
migrations.AlterField(
model_name='item',
name='damage_vulnerabilities',
field=models.ManyToManyField(related_name='%(class)s_damage_vulnerabilities', to='api_v2.damagetype'),
),
migrations.CreateModel(
name='MagicItem',
fields=[
('name', models.CharField(help_text='Name of the item.', max_length=100)),
('desc', models.TextField(blank=True, help_text='Description of the game content item. Markdown.')),
('cost', models.DecimalField(decimal_places=2, default=None, help_text='Number representing the cost of the object.', max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0'))])),
('key', models.CharField(help_text='Unique key for the Item.', max_length=100, primary_key=True, serialize=False)),
('weight', models.DecimalField(decimal_places=3, default=0, help_text='Number representing the weight of the object.', max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0'))])),
('armor_class', models.IntegerField(default=0, help_text='Integer representing the armor class of the object.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('armor_detail', models.CharField(default='', help_text='Represents parathetical text that follows an objects AC', max_length=64, null=True)),
('hit_points', models.IntegerField(default=0, help_text='Integer representing the hit points of the object.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000)])),
('hit_dice', models.TextField(blank=True, help_text='Dice string representing a way to calculate hit points.', null=True)),
('nonmagical_attack_resistance', models.BooleanField(default=False, help_text='If api_v2.models.object is resistant to nonmagical attacks.')),
('nonmagical_attack_immunity', models.BooleanField(default=False, help_text='If the api_v2.models.object is immune to nonmagical attacks.')),
('requires_attunement', models.BooleanField(default=False, help_text='If the item requires attunement.')),
('attunement_detail', models.CharField(blank=True, max_length=128, null=True)),
('armor', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='api_v2.armor')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api_v2.itemcategory')),
('damage_immunities', models.ManyToManyField(related_name='%(class)s_damage_immunities', to='api_v2.damagetype')),
('damage_resistances', models.ManyToManyField(related_name='%(class)s_damage_resistances', to='api_v2.damagetype')),
('damage_vulnerabilities', models.ManyToManyField(related_name='%(class)s_damage_vulnerabilities', to='api_v2.damagetype')),
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api_v2.document')),
('rarity', models.ForeignKey(help_text='Rarity object.', null=True, on_delete=django.db.models.deletion.CASCADE, to='api_v2.itemrarity')),
('size', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api_v2.size')),
('weapon', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='api_v2.weapon')),
],
options={
'abstract': False,
},
),
]
1 change: 1 addition & 0 deletions api_v2/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .item import ItemCategory
from .item import Item
from .item import MagicItem
from .item import ItemSet
from .item import ItemRarity

Expand Down
55 changes: 27 additions & 28 deletions api_v2/models/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ class ItemCategory(HasName, FromDocument):
"""A class describing categories of items."""


class Item(Object, HasDescription, FromDocument, HasPrice):
class ItemBase(models.Model):
"""
This is the model for an Item, which is an object that can be used.

This extends the object model, but adds cost, and is_magical.
Common inheritance of Item and MagicItem models
"""

weapon = models.ForeignKey(
Weapon,
on_delete=models.CASCADE,
Expand All @@ -48,11 +45,34 @@ class Item(Object, HasDescription, FromDocument, HasPrice):
category = models.ForeignKey(
ItemCategory,
on_delete=models.CASCADE,
null=False)
null=False
)

damage_vulnerabilities = models.ManyToManyField(DamageType,
related_name="%(class)s_damage_vulnerabilities")

damage_immunities = models.ManyToManyField(DamageType,
related_name="%(class)s_damage_immunities")

damage_resistances = models.ManyToManyField(DamageType,
related_name="%(class)s_damage_resistances")

class Meta:
abstract = True

class Item(ItemBase, Object, HasDescription, FromDocument, HasPrice):
"""
This is the model for an Item, which is an object that can be used.

This extends the object model, but adds cost, and is_magical.
"""
pass


class MagicItem(ItemBase, Object, HasDescription, FromDocument, HasPrice):
requires_attunement = models.BooleanField(
null=False,
default=False, # An item is not magical unless specified.
default=False,
help_text='If the item requires attunement.')

attunement_detail = models.CharField(
Expand All @@ -67,27 +87,6 @@ class Item(Object, HasDescription, FromDocument, HasPrice):
on_delete=models.CASCADE,
help_text="Rarity object.")

damage_vulnerabilities = models.ManyToManyField(DamageType,
related_name="item_damage_vulnerabilities")

damage_immunities = models.ManyToManyField(DamageType,
related_name="item_damage_immunities")

damage_resistances = models.ManyToManyField(DamageType,
related_name="item_damage_resistances")

@property
@extend_schema_field(OpenApiTypes.BOOL)
def is_magic_item(self):
return self.rarity is not None

def search_result_extra_fields(self):
return {
"is_magic_item": self.is_magic_item,
"type": self.category.name if self.is_magic_item else None,
"rarity": self.rarity.name if self.is_magic_item else None,
}


class ItemSet(HasName, HasDescription, FromDocument):
"""A set of items to be referenced."""
Expand Down
1 change: 1 addition & 0 deletions api_v2/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .item import ArmorSerializer
from .item import WeaponSerializer
from .item import ItemSerializer
from .item import MagicItemSerializer
from .item import ItemRaritySerializer
from .item import ItemSetSerializer
from .item import ItemCategorySerializer
Expand Down
41 changes: 35 additions & 6 deletions api_v2/serializers/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,10 @@ class Meta:

class ItemSerializer(GameContentSerializer):
key = serializers.ReadOnlyField()
is_magic_item = serializers.ReadOnlyField()
weapon = WeaponSummarySerializer()
armor = ArmorSummarySerializer()
document = DocumentSummarySerializer()
category = ItemCategorySummarySerializer()
rarity = ItemRaritySerializer()
#damage_immunities = DamageTypeSummarySerializer(many=True)
size = SizeSummarySerializer()
weight_unit = serializers.SerializerMethodField()
Expand All @@ -155,16 +153,12 @@ class Meta:
'name',
'desc',
'category',
'rarity',
'is_magic_item',
'weapon',
'armor',
'size',
'weight',
'weight_unit',
'cost',
'requires_attunement',
'attunement_detail',
'document',]


Expand All @@ -173,6 +167,41 @@ class Meta:
model = models.Item
fields = ['name', 'key', 'url']

class MagicItemSerializer(GameContentSerializer):
key = serializers.ReadOnlyField()
is_magic_item = serializers.ReadOnlyField()
weapon = WeaponSummarySerializer()
armor = ArmorSummarySerializer()
document = DocumentSummarySerializer()
category = ItemCategorySummarySerializer()
rarity = ItemRaritySerializer()
#damage_immunities = DamageTypeSummarySerializer(many=True)
size = SizeSummarySerializer()
weight_unit = serializers.SerializerMethodField()

@extend_schema_field(OpenApiTypes.STR)
def get_weight_unit(self, item):
return item.get_weight_unit()

class Meta:
model = models.MagicItem
fields = [
'url',
'key',
'name',
'desc',
'category',
'rarity',
'is_magic_item',
'weapon',
'armor',
'size',
'weight',
'weight_unit',
'cost',
'requires_attunement',
'attunement_detail',
'document',]


class ItemSetSerializer(GameContentSerializer):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"strength_score_required": 15,
"url": "http://localhost:8000/v2/armor/srd_splint/"
},
"attunement_detail": null,
"category": {
"key": "armor",
"name": "Armor",
Expand All @@ -36,11 +35,8 @@
},
"type": "SOURCE"
},
"is_magic_item": false,
"key": "srd_splint-armor",
"name": "Splint Armor",
"rarity": null,
"requires_attunement": false,
"size": {
"key": "tiny",
"name": "Tiny",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
},
"type": "SOURCE"
},
"is_magic_item": true,
"key": "srd_apparatus-of-the-crab",
"name": "Apparatus of the Crab",
"rarity": {
Expand All @@ -40,7 +39,7 @@
"name": "Tiny",
"url": "http://localhost:8000/v2/sizes/tiny/"
},
"url": "http://localhost:8000/v2/items/srd_apparatus-of-the-crab/",
"url": "http://localhost:8000/v2/magicitems/srd_apparatus-of-the-crab/",
"weapon": null,
"weight": "0.000",
"weight_unit": "lb"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"armor": null,
"attunement_detail": null,
"category": {
"key": "weapon",
"name": "Weapon",
Expand All @@ -25,11 +24,8 @@
},
"type": "SOURCE"
},
"is_magic_item": false,
"key": "srd_shortsword",
"name": "Shortsword",
"rarity": null,
"requires_attunement": false,
"size": {
"key": "tiny",
"name": "Tiny",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"armor": null,
"attunement_detail": null,
"category": {
"key": "weapon",
"name": "Weapon",
Expand All @@ -25,11 +24,8 @@
},
"type": "SOURCE"
},
"is_magic_item": false,
"key": "srd_longbow",
"name": "Longbow",
"rarity": null,
"requires_attunement": false,
"size": {
"key": "tiny",
"name": "Tiny",
Expand Down
2 changes: 1 addition & 1 deletion api_v2/tests/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _verify(self, endpoint: str, transformer: Callable[[dict], None] = None):

# /ITEMS ENDPOINT
def test_item_example(self):
path="/v2/items/srd_apparatus-of-the-crab/"
path="/v2/magicitems/srd_apparatus-of-the-crab/"
self._verify(path)

def test_item_melee_weapon_example(self):
Expand Down
2 changes: 1 addition & 1 deletion api_v2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

router = routers.DefaultRouter()
router.register(r'items',views.ItemViewSet)
router.register(r'magicitems', views.MagicItemViewSet, basename="magicitems")
router.register(r'magicitems', views.MagicItemViewSet)
router.register(r'itemsets',views.ItemSetViewSet)
router.register(r'itemcategories',views.ItemCategoryViewSet)
router.register(r'documents',views.DocumentViewSet)
Expand Down
Loading