Skip to content

Commit 667e94f

Browse files
Merge pull request #615 from open5e/staging
1.9.0
2 parents b1dc102 + 779d62e commit 667e94f

16 files changed

+21024
-1684
lines changed

api_v2/admin.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ class RaceAdmin(admin.ModelAdmin):
3434
RaceTraitInline,
3535
]
3636

37+
class ClassFeatureItemInline(admin.TabularInline):
38+
model = ClassFeatureItem
39+
40+
class ClassFeatureAdmin(admin.ModelAdmin):
41+
inlines = [
42+
ClassFeatureItemInline
43+
]
44+
3745

3846
class BackgroundBenefitInline(admin.TabularInline):
3947
model = BackgroundBenefit
@@ -93,7 +101,7 @@ class LanguageAdmin(admin.ModelAdmin):
93101
admin.site.register(Condition)
94102

95103
admin.site.register(ClassFeatureItem)
96-
admin.site.register(ClassFeature)
104+
admin.site.register(ClassFeature, admin_class=ClassFeatureAdmin)
97105
admin.site.register(CharacterClass)
98106

99107
admin.site.register(Environment)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.2 on 2024-11-25 19:36
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api_v2', '0016_alter_creaturetrait_parent'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='characterclass',
15+
name='saving_throws',
16+
field=models.ManyToManyField(help_text='Saving throw proficiencies for this class.', related_name='characterclass_saving_throws', to='api_v2.ability'),
17+
),
18+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.2 on 2024-11-26 22:05
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api_v2', '0017_characterclass_saving_throws'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='characterclass',
15+
name='caster_type',
16+
field=models.CharField(blank=True, choices=[('FULL', 'Full'), ('HALF', 'Half'), ('NONE', 'None')], default=None, help_text='Type of caster. Options are full, half, none.', max_length=100, null=True),
17+
),
18+
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.1.2 on 2024-12-01 15:06
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api_v2', '0018_characterclass_caster_type'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='classfeatureitem',
15+
name='column',
16+
field=models.BooleanField(default=False, help_text='Whether or not the field should be displayed as a column.'),
17+
preserve_default=False,
18+
),
19+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 5.1.2 on 2024-12-01 15:11
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api_v2', '0019_classfeatureitem_column'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='classfeatureitem',
15+
name='column',
16+
),
17+
migrations.AddField(
18+
model_name='classfeatureitem',
19+
name='column_value',
20+
field=models.CharField(blank=True, help_text='The value that should be displayed in the table column (where applicable).', max_length=20, null=True),
21+
),
22+
]

api_v2/models/characterclass.py

Lines changed: 95 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55

66
from .abstracts import HasName, HasDescription, Modification
77
from .abstracts import key_field
8+
from .abilities import Ability
89
from .document import FromDocument
9-
from .enums import DIE_TYPES
10+
from .enums import DIE_TYPES, CASTER_TYPES
1011
from drf_spectacular.utils import extend_schema_field, inline_serializer
1112
from drf_spectacular.types import OpenApiTypes
1213
from rest_framework import serializers
1314

15+
16+
1417
class ClassFeatureItem(models.Model):
1518
"""This is the class for an individual class feature item, a subset of a class
16-
feature. The name field is unused."""
19+
feature."""
1720

1821
key = key_field()
1922

@@ -23,6 +26,14 @@ class ClassFeatureItem(models.Model):
2326
parent = models.ForeignKey('ClassFeature', on_delete=models.CASCADE)
2427
level = models.IntegerField(validators=[MinValueValidator(0),MaxValueValidator(20)])
2528

29+
column_value = models.CharField(
30+
# The value displayed in a column, or null if no value.
31+
null=True,
32+
blank=True,
33+
max_length=20,
34+
help_text='The value that should be displayed in the table column (where applicable).'
35+
)
36+
2637
def __str__(self):
2738
return "{} {} ({})".format(
2839
self.parent.parent.name,
@@ -37,18 +48,25 @@ class ClassFeature(HasName, HasDescription, FromDocument):
3748
parent = models.ForeignKey('CharacterClass',
3849
on_delete=models.CASCADE)
3950

51+
def featureitems(self):
52+
return self.classfeatureitem_set.exclude(column_value__isnull=False)
53+
54+
def columnitems(self):
55+
return self.classfeatureitem_set.exclude(column_value__isnull=True)
56+
4057
def __str__(self):
4158
return "{} ({})".format(self.name,self.parent.name)
4259

4360

4461
class CharacterClass(HasName, FromDocument):
4562
"""The model for a character class or subclass."""
63+
4664
subclass_of = models.ForeignKey('self',
4765
default=None,
4866
blank=True,
4967
null=True,
5068
on_delete=models.CASCADE)
51-
69+
5270
hit_dice = models.CharField(
5371
max_length=100,
5472
default=None,
@@ -57,6 +75,18 @@ class CharacterClass(HasName, FromDocument):
5775
choices=DIE_TYPES,
5876
help_text='Dice notation hit dice option.')
5977

78+
saving_throws = models.ManyToManyField(Ability,
79+
related_name="characterclass_saving_throws",
80+
help_text='Saving throw proficiencies for this class.')
81+
82+
caster_type = models.CharField(
83+
max_length=100,
84+
default=None,
85+
blank=True,
86+
null=True,
87+
choices=CASTER_TYPES,
88+
help_text='Type of caster. Options are full, half, none.')
89+
6090
@property
6191
@extend_schema_field(inline_serializer(
6292
name="hit_points",
@@ -104,21 +134,68 @@ def features(self):
104134
}
105135
)
106136
))
107-
def levels(self):
108-
"""Returns an array of level information for the given class."""
109-
by_level = {}
110-
111-
for classfeature in self.classfeature_set.all():
112-
for fl in classfeature.classfeatureitem_set.all():
113-
if (str(fl.level)) not in by_level.keys():
114-
by_level[str(fl.level)] = {}
115-
by_level[str(fl.level)]['features'] = []
116-
117-
by_level[str(fl.level)]['features'].append(fl.parent.key)
118-
by_level[str(fl.level)]['proficiency-bonus'] = self.proficiency_bonus(player_level=fl.level)
119-
by_level[str(fl.level)]['level'] = fl.level
120-
121-
return by_level
137+
138+
139+
def get_slots_by_player_level(self,level,caster_type):
140+
# full is for a full caster, not including cantrips.
141+
# full=False is for a half caster.
142+
if level<0: # Invalid player level.
143+
return None
144+
if level>20: # Invalid player level.
145+
return None
146+
147+
full = [[],
148+
[0,2,0,0,0,0,0,0,0,0],
149+
[0,3,0,0,0,0,0,0,0,0],
150+
[0,4,2,0,0,0,0,0,0,0],
151+
[0,4,3,0,0,0,0,0,0,0],
152+
[0,4,3,2,0,0,0,0,0,0],
153+
[0,4,3,3,0,0,0,0,0,0],
154+
[0,4,3,3,1,0,0,0,0,0],
155+
[0,4,3,3,2,0,0,0,0,0],
156+
[0,4,3,3,3,1,0,0,0,0],
157+
[0,4,3,3,3,2,0,0,0,0],
158+
[0,4,3,3,3,2,1,0,0,0],
159+
[0,4,3,3,3,2,1,0,0,0],
160+
[0,4,3,3,3,2,1,1,0,0],
161+
[0,4,3,3,3,2,1,1,0,0],
162+
[0,4,3,3,3,2,1,1,1,0],
163+
[0,4,3,3,3,2,1,1,1,0],
164+
[0,4,3,3,3,2,1,1,1,1],
165+
[0,4,3,3,3,3,1,1,1,1],
166+
[0,4,3,3,3,3,2,1,1,1],
167+
[0,4,3,3,3,3,2,2,1,1]
168+
]
169+
170+
half = [[],
171+
[0,0,0,0,0,0],
172+
[0,2,0,0,0,0],
173+
[0,3,0,0,0,0],
174+
[0,3,0,0,0,0],
175+
[0,4,2,0,0,0],
176+
[0,4,2,0,0,0],
177+
[0,4,3,0,0,0],
178+
[0,4,3,0,0,0],
179+
[0,4,3,2,0,0],
180+
[0,4,3,2,0,0],
181+
[0,4,3,3,0,0],
182+
[0,4,3,3,0,0],
183+
[0,4,3,3,1,0],
184+
[0,4,3,3,1,0],
185+
[0,4,3,3,2,0],
186+
[0,4,3,3,2,0],
187+
[0,4,3,3,3,1],
188+
[0,4,3,3,3,1],
189+
[0,4,3,3,3,2],
190+
[0,4,3,3,3,2]
191+
]
192+
193+
if caster_type=='FULL':
194+
return full[level]
195+
if caster_type=='HALF':
196+
return half[level]
197+
else:
198+
return []
122199

123200
def proficiency_bonus(self, player_level):
124201
# Consider as part of enums
@@ -146,5 +223,3 @@ def search_result_extra_fields(self):
146223
"key": self.subclass_of.key
147224
} if self.subclass_of else None
148225
}
149-
150-
#TODO add verbose name plural

api_v2/models/enums.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@
4141
("WEAPON", "Weapon"),
4242
]
4343

44+
CASTER_TYPES = [
45+
("FULL","Full"),
46+
("HALF","Half"),
47+
("NONE","None")
48+
]
49+
4450
ACTION_TYPES = [
4551
("ACTION", "Action"),
4652
("REACTION","Reaction"),

api_v2/serializers/characterclass.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,36 @@
77
from .abstracts import GameContentSerializer
88
from .document import DocumentSerializer
99

10+
1011
class ClassFeatureItemSerializer(GameContentSerializer):
12+
1113
class Meta:
1214
model = models.ClassFeatureItem
13-
fields = ['name','desc','type']
15+
fields = ['level']
16+
17+
class ClassFeatureColumnItemSerializer(GameContentSerializer):
18+
class Meta:
19+
model = models.ClassFeatureItem
20+
fields = ['level','column_value']
1421

1522
class ClassFeatureSerializer(GameContentSerializer):
1623
key = serializers.ReadOnlyField()
24+
featureitems = ClassFeatureItemSerializer(
25+
many=True
26+
)
27+
28+
columnitems = ClassFeatureColumnItemSerializer(
29+
many=True
30+
)
1731

1832
class Meta:
1933
model = models.ClassFeature
20-
fields = ['key', 'name', 'desc']
34+
fields = ['key', 'name', 'desc','featureitems','columnitems']
2135

2236
class CharacterClassSerializer(GameContentSerializer):
2337
key = serializers.ReadOnlyField()
2438
features = ClassFeatureSerializer(
2539
many=True, context={'request': {}})
26-
levels = serializers.ReadOnlyField()
2740
hit_points = serializers.ReadOnlyField()
2841
document = DocumentSerializer()
2942

0 commit comments

Comments
 (0)