Skip to content

Commit 39bacb6

Browse files
committed
moved Item/Weapon serializer/view to use new WeaponProperties
1 parent 3d974b0 commit 39bacb6

File tree

5 files changed

+158
-580
lines changed

5 files changed

+158
-580
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Generated by Django 5.1.2 on 2025-05-16 12:01
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api_v2', '0043_alter_weaponproperty_options_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='weapon',
15+
name='is_finesse',
16+
),
17+
migrations.RemoveField(
18+
model_name='weapon',
19+
name='is_heavy',
20+
),
21+
migrations.RemoveField(
22+
model_name='weapon',
23+
name='is_lance',
24+
),
25+
migrations.RemoveField(
26+
model_name='weapon',
27+
name='is_light',
28+
),
29+
migrations.RemoveField(
30+
model_name='weapon',
31+
name='is_net',
32+
),
33+
migrations.RemoveField(
34+
model_name='weapon',
35+
name='is_thrown',
36+
),
37+
migrations.RemoveField(
38+
model_name='weapon',
39+
name='is_two_handed',
40+
),
41+
migrations.RemoveField(
42+
model_name='weapon',
43+
name='reach',
44+
),
45+
migrations.RemoveField(
46+
model_name='weapon',
47+
name='requires_ammunition',
48+
),
49+
migrations.RemoveField(
50+
model_name='weapon',
51+
name='requires_loading',
52+
),
53+
migrations.RemoveField(
54+
model_name='weapon',
55+
name='versatile_dice',
56+
),
57+
]

api_v2/models/weapon.py

Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def __str__(self):
2020

2121

2222
class WeaponPropertyAssignment(FromDocument):
23+
"""
24+
This is an intermediate model that is used to assign WeaponProperties to
25+
Weapons while bundling in any extra contextual data in the `detail` field
26+
"""
2327
weapon = models.ForeignKey(
2428
'Weapon',
2529
related_name='properties',
@@ -53,15 +57,6 @@ class Weapon(HasName, FromDocument):
5357
max_length=100,
5458
help_text='The damage dice when used making an attack.')
5559

56-
versatile_dice = models.CharField(
57-
null=False,
58-
default=0,
59-
max_length=100,
60-
help_text="""The damage dice when attacking using versatile.
61-
A value of 0 means that the weapon does not have the versatile property.""")
62-
63-
reach = distance_field()
64-
6560
range = distance_field()
6661

6762
long_range = distance_field()
@@ -76,52 +71,6 @@ def get_distance_unit(self):
7671
return self.document.distance_unit
7772
return self.distance_unit
7873

79-
80-
is_finesse = models.BooleanField(
81-
null=False,
82-
default=False,
83-
help_text='If the weapon is finesse.')
84-
85-
is_thrown = models.BooleanField(
86-
null=False,
87-
default=False,
88-
help_text='If the weapon is thrown.')
89-
90-
is_two_handed = models.BooleanField(
91-
null=False,
92-
default=False,
93-
help_text='If the weapon is two-handed.')
94-
95-
requires_ammunition = models.BooleanField(
96-
null=False,
97-
default=False,
98-
help_text='If the weapon requires ammunition.')
99-
100-
requires_loading = models.BooleanField(
101-
null=False,
102-
default=False,
103-
help_text='If the weapon requires loading.')
104-
105-
is_heavy = models.BooleanField(
106-
null=False,
107-
default=False,
108-
help_text='If the weapon is heavy.')
109-
110-
is_light = models.BooleanField(
111-
null=False,
112-
default=False,
113-
help_text='If the weapon is light.')
114-
115-
is_lance = models.BooleanField(
116-
null=False,
117-
default=False,
118-
help_text='If the weapon is a lance.')
119-
120-
is_net = models.BooleanField(
121-
null=False,
122-
default=False,
123-
help_text='If the weapon is a net.')
124-
12574
is_simple = models.BooleanField(
12675
null=False,
12776
default=False,
@@ -131,37 +80,8 @@ def get_distance_unit(self):
13180
null=False,
13281
default=False,
13382
help_text='If the weapon is improvised.')
134-
135-
@property
136-
@extend_schema_field(OpenApiTypes.BOOL)
137-
def is_versatile(self):
138-
return self.versatile_dice != str(0)
13983

14084
@property
14185
@extend_schema_field(OpenApiTypes.BOOL)
14286
def is_martial(self):
143-
return not self.is_simple
144-
145-
@property
146-
@extend_schema_field(OpenApiTypes.BOOL)
147-
def is_melee(self):
148-
# Ammunition weapons can only be used as improvised melee weapons.
149-
return not self.requires_ammunition
150-
151-
@property
152-
@extend_schema_field(OpenApiTypes.BOOL)
153-
def ranged_attack_possible(self):
154-
# Only ammunition or throw weapons can make ranged attacks.
155-
return self.requires_ammunition or self.is_thrown
156-
157-
@property
158-
# type is any
159-
@extend_schema_field(OpenApiTypes.BOOL)
160-
def range_melee(self):
161-
return self.reach
162-
163-
@property
164-
@extend_schema_field(OpenApiTypes.BOOL)
165-
def is_reach(self):
166-
# A weapon with a longer reach than the default has the reach property.
167-
return self.reach > 5
87+
return not self.is_simple

api_v2/serializers/item.py

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,10 @@ class Meta:
6363
class WeaponSerializer(GameContentSerializer):
6464
key = serializers.ReadOnlyField()
6565
document = DocumentSummarySerializer()
66+
properties = WeaponPropertyAssignmentSerializer(many=True)
6667
damage_type = DamageTypeSummarySerializer()
67-
is_versatile = serializers.ReadOnlyField()
68-
is_martial = serializers.ReadOnlyField()
69-
is_melee = serializers.ReadOnlyField()
7068
ranged_attack_possible = serializers.ReadOnlyField()
7169
range_melee = serializers.ReadOnlyField()
72-
is_reach = serializers.ReadOnlyField()
7370
distance_unit = serializers.SerializerMethodField()
7471

7572
class Meta:
@@ -88,13 +85,8 @@ class WeaponSummarySerializer(GameContentSerializer):
8885
`"weapon"` field on the ItemSerializer
8986
'''
9087
damage_type = DamageTypeSummarySerializer()
91-
is_versatile = serializers.ReadOnlyField()
9288
is_martial = serializers.ReadOnlyField()
9389
is_melee = serializers.ReadOnlyField()
94-
is_finesse = serializers.ReadOnlyField()
95-
ranged_attack_possible = serializers.ReadOnlyField()
96-
range_melee = serializers.ReadOnlyField()
97-
is_reach = serializers.ReadOnlyField()
9890
distance_unit = serializers.SerializerMethodField()
9991
properties = WeaponPropertyAssignmentSerializer(many=True, read_only=True)
10092

@@ -106,28 +98,11 @@ class Meta:
10698
'url',
10799
'damage_type',
108100
'damage_dice',
109-
'versatile_dice',
110-
'is_versatile',
111-
'reach',
112-
'is_reach',
113-
'is_finesse',
114-
'range',
115-
'range_melee',
116-
'long_range',
117-
'ranged_attack_possible',
118-
'is_thrown',
119-
'is_two_handed',
120-
'requires_ammunition',
121-
'requires_loading',
122-
'is_heavy',
123-
'is_light',
124-
'is_lance',
125-
'is_net',
101+
'properties',
126102
'is_melee',
127103
'is_simple',
128104
'is_martial',
129105
'is_improvised',
130-
'properties',
131106
'distance_unit',
132107
]
133108

api_v2/views/item.py

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,59 @@
11
from rest_framework import viewsets
2+
from django_filters import FilterSet, BooleanFilter
23

3-
from django_filters import FilterSet
4-
from django_filters import BooleanFilter
5-
6-
from api_v2 import models
7-
from api_v2 import serializers
4+
from api_v2 import models, serializers
85
from .mixins import EagerLoadingMixin
96

107
class ItemFilterSet(FilterSet):
11-
is_magic_item = BooleanFilter(field_name='rarity', lookup_expr='isnull', exclude=True)
8+
''' Filter set for the Item model. Used in the ItemViewSet below '''
9+
10+
is_magic_item = BooleanFilter(
11+
label='Magic Items',
12+
field_name='rarity',
13+
lookup_expr='isnull',
14+
exclude=True
15+
)
16+
17+
is_weapon = BooleanFilter(
18+
label='Weapons',
19+
field_name='weapon',
20+
lookup_expr='isnull',
21+
exclude=True
22+
)
23+
24+
is_armor = BooleanFilter(
25+
label='Armor',
26+
field_name='armor',
27+
lookup_expr='isnull',
28+
exclude=True
29+
)
30+
31+
# Weapon property filter factory method
32+
def weapon_property_filter(property_name):
33+
return lambda queryset, name, value: (
34+
queryset.filter(weapon__properties__property__name__iexact=property_name)
35+
)
36+
37+
# Filters for weapon properties (using the factory method defined above)
38+
is_light = BooleanFilter(label='Light Weapons', method=weapon_property_filter('light'))
39+
is_versatile = BooleanFilter(label='Versatile Weapons', method=weapon_property_filter('versatile'))
40+
is_thrown = BooleanFilter(label='Thrown Weapons', method=weapon_property_filter('thrown'))
41+
is_finesse = BooleanFilter(label='Finesse Weapons', method = weapon_property_filter('finesse'))
42+
is_two_handed = BooleanFilter(label='Two-handed Weapons', method=weapon_property_filter('two-handed'))
1243

1344
class Meta:
1445
model = models.Item
1546
fields = {
16-
'key': ['in', 'iexact', 'exact'],
17-
'name': ['iexact', 'exact', 'icontains'],
47+
'key': ['in', 'iexact'],
48+
'name': ['iexact', 'icontains'],
1849
'desc': ['icontains'],
1950
'cost': ['exact', 'range', 'gt', 'gte', 'lt', 'lte'],
2051
'weight': ['exact', 'range', 'gt', 'gte', 'lt', 'lte'],
2152
'rarity': ['exact', 'in'],
22-
'requires_attunement': ['exact'],
53+
'requires_attunement': ['iexact'],
2354
'category': ['in', 'exact'],
24-
'document__key': ['in','iexact','exact'],
25-
'document__gamesystem__key': ['in','iexact','exact'],
55+
'document__key': ['in','iexact'],
56+
'document__gamesystem__key': ['in','iexact'],
2657
}
2758

2859

@@ -45,8 +76,9 @@ class ItemViewSet(EagerLoadingMixin, viewsets.ReadOnlyModelViewSet):
4576
'damage_vulnerabilities',
4677
'document',
4778
'weapon__properties',
79+
'weapon__damage_type',
80+
'weapon__document',
4881
'weapon__properties__property',
49-
# 'document__gamesystem',
5082
'rarity',
5183
'size',
5284
]
@@ -108,30 +140,27 @@ class ItemCategoryViewSet(viewsets.ReadOnlyModelViewSet):
108140

109141
class WeaponFilterSet(FilterSet):
110142

143+
# returns a filter method to a given WeaponProperty
144+
def weapon_property_filter(property_name):
145+
return lambda queryset, name, value: (
146+
queryset.filter(properties__property__name__iexact=property_name)
147+
)
148+
149+
is_light = BooleanFilter(label='Is Light', method=weapon_property_filter('light'))
150+
is_versatile = BooleanFilter(label='Is Versatile', method=weapon_property_filter('versatile'))
151+
is_thrown = BooleanFilter(label='Is Thrown', method=weapon_property_filter('thrown'))
152+
is_finesse = BooleanFilter(label='Is Finesse', method = weapon_property_filter('finesse'))
153+
is_two_handed = BooleanFilter(label='Is Two-handed', method=weapon_property_filter('two-handed'))
154+
111155
class Meta:
112156
model = models.Weapon
113157
fields = {
114-
'key': ['in', 'iexact', 'exact' ],
115-
'name': ['iexact', 'exact'],
116-
'document__key': ['in','iexact','exact'],
117-
'document__gamesystem__key': ['in','iexact','exact'],
118-
'damage_dice': ['in','iexact','exact'],
119-
'versatile_dice': ['in','iexact','exact'],
120-
'reach': ['exact','lt','lte','gt','gte'],
121-
'range': ['exact','lt','lte','gt','gte'],
122-
'long_range': ['exact','lt','lte','gt','gte'],
123-
'is_finesse': ['exact'],
124-
'is_thrown': ['exact'],
125-
'is_two_handed': ['exact'],
126-
'requires_ammunition': ['exact'],
127-
'requires_loading': ['exact'],
128-
'is_heavy': ['exact'],
129-
'is_light': ['exact'],
130-
'is_lance': ['exact'],
131-
'is_net': ['exact'],
132-
'is_simple': ['exact'],
133-
'is_improvised': ['exact']
134-
}
158+
'key': ['in', 'iexact'],
159+
'name': ['iexact'],
160+
'document__key': ['in','iexact'],
161+
'document__gamesystem__key': ['in','iexact'],
162+
'damage_dice': ['in','iexact'],
163+
}
135164

136165

137166
class WeaponViewSet(EagerLoadingMixin, viewsets.ReadOnlyModelViewSet):
@@ -143,7 +172,7 @@ class WeaponViewSet(EagerLoadingMixin, viewsets.ReadOnlyModelViewSet):
143172
serializer_class = serializers.WeaponSerializer
144173
filterset_class = WeaponFilterSet
145174

146-
prefetch_related_fields = ['document']
175+
prefetch_related_fields = ['document', 'damage_type', 'properties__property']
147176

148177
class ArmorFilterSet(FilterSet):
149178

0 commit comments

Comments
 (0)