adding tests and helper UX to schema
This commit is contained in:
parent
78115023bb
commit
dbb9461b7a
|
@ -96,6 +96,7 @@ class CharacterClassAttributeMap(BaseObject, IterableMixin):
|
|||
primaryjoin="CharacterClassAttributeMap.character_id == CharacterClassMap.character_id",
|
||||
secondaryjoin="CharacterClass.id == CharacterClassMap.character_class_id",
|
||||
viewonly=True,
|
||||
uselist=False,
|
||||
)
|
||||
|
||||
|
||||
|
@ -117,14 +118,18 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
|||
proficiencies = Column(String)
|
||||
|
||||
class_map = relationship("CharacterClassMap", cascade="all,delete,delete-orphan")
|
||||
classes = association_proxy("class_map", "id", creator=class_map_creator)
|
||||
_classes = association_proxy("class_map", "id", creator=class_map_creator)
|
||||
|
||||
character_class_attribute_map = relationship("CharacterClassAttributeMap", cascade="all,delete,delete-orphan")
|
||||
class_attributes = association_proxy("character_class_attribute_map", "id", creator=attr_map_creator)
|
||||
_class_attributes = association_proxy("character_class_attribute_map", "id", creator=attr_map_creator)
|
||||
|
||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"), nullable=False, default="1")
|
||||
ancestry = relationship("Ancestry", uselist=False)
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
return dict([(mapping.character_class.name, mapping.character_class) for mapping in self.class_map])
|
||||
|
||||
@property
|
||||
def traits(self):
|
||||
return [mapping.trait for mapping in self.ancestry.traits]
|
||||
|
@ -137,6 +142,10 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
|||
def levels(self):
|
||||
return dict([(mapping.character_class.name, mapping.level) for mapping in self.class_map])
|
||||
|
||||
@property
|
||||
def class_attributes(self):
|
||||
return dict([(mapping.class_attribute.name, mapping.option) for mapping in self.character_class_attribute_map])
|
||||
|
||||
def add_class(self, newclass, level=1):
|
||||
if level == 0:
|
||||
return self.remove_class(newclass)
|
||||
|
@ -145,7 +154,24 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
|||
level_in_class = level_in_class[0]
|
||||
level_in_class.level = level
|
||||
return
|
||||
self.classes.append(CharacterClassMap(character_id=self.id, character_class_id=newclass.id, level=level))
|
||||
self._classes.append(CharacterClassMap(character_id=self.id, character_class_id=newclass.id, level=level))
|
||||
|
||||
def remove_class(self, target):
|
||||
self.class_map = [m for m in self.class_map if m.id != target.id]
|
||||
for mapping in self.character_class_attribute_map:
|
||||
if mapping.character_class.id == target.id:
|
||||
self.remove_class_attribute(mapping.class_attribute)
|
||||
|
||||
def remove_class_attribute(self, attribute):
|
||||
self.character_class_attribute_map = [m for m in self.character_class_attribute_map if m.id != attribute.id]
|
||||
|
||||
def add_class_attribute(self, attribute, option):
|
||||
for thisclass in self.classes.values():
|
||||
if attribute.name in thisclass.attributes_by_level.get(self.levels[thisclass.name], {}):
|
||||
self._class_attributes.append(
|
||||
CharacterClassAttributeMap(
|
||||
character_id=self.id, class_attribute_id=attribute.id, option_id=option.id
|
||||
)
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -16,12 +16,14 @@ class ClassAttributeMap(BaseObject, IterableMixin):
|
|||
class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), primary_key=True)
|
||||
character_class_id = Column(Integer, ForeignKey("character_class.id"), primary_key=True)
|
||||
level = Column(Integer, nullable=False, info={"min": 1, "max": 20}, default=1)
|
||||
attribute = relationship("ClassAttribute", uselist=False, viewonly=True, lazy="immediate")
|
||||
|
||||
|
||||
class ClassAttribute(BaseObject, IterableMixin):
|
||||
__tablename__ = "class_attribute"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, nullable=False)
|
||||
options = relationship("ClassAttributeOption", cascade="all,delete,delete-orphan", lazy="immediate")
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.id}: {self.name}"
|
||||
|
@ -32,7 +34,6 @@ class ClassAttributeOption(BaseObject, IterableMixin):
|
|||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, nullable=False)
|
||||
attribute_id = Column(Integer, ForeignKey("class_attribute.id"), nullable=False)
|
||||
# attribute = relationship("ClassAttribute", uselist=False)
|
||||
|
||||
|
||||
class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||
|
@ -42,4 +43,11 @@ class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
|||
hit_dice = Column(String, default="1d6")
|
||||
hit_dice_stat = Column(Enum(StatsEnum))
|
||||
proficiencies = Column(String)
|
||||
attributes = relationship("ClassAttributeMap")
|
||||
attributes = relationship("ClassAttributeMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
||||
|
||||
@property
|
||||
def attributes_by_level(self):
|
||||
by_level = {}
|
||||
for mapping in self.attributes:
|
||||
by_level[mapping.level] = {mapping.attribute.name: mapping.attribute}
|
||||
return by_level
|
||||
|
|
25
test/fixtures/classes.json
vendored
25
test/fixtures/classes.json
vendored
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"CharacterClass": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "fighter",
|
||||
"hit_dice": "1d10",
|
||||
"hit_dice_stat": "CON",
|
||||
|
@ -9,6 +10,7 @@
|
|||
"skills": ["Acrobatics", "Animal Handling", "Athletics", "History", "Insight", "Intimidation", "Perception", "Survival"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "rogue",
|
||||
"hit_dice": "1d8",
|
||||
"hit_dice_stat": "DEX",
|
||||
|
@ -16,5 +18,28 @@
|
|||
"saving_throws": ["DEX", "INT"],
|
||||
"skills": ["Acrobatics", "Athletics", "Deception", "Insight", "Intimidation", "Investigation", "Perception", "Performance", "Persuasion", "Sleight of Hand", "Stealth"]
|
||||
}
|
||||
],
|
||||
"ClassAttribute": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Fighting Style"
|
||||
}
|
||||
],
|
||||
"ClassAttributeMap": [
|
||||
{
|
||||
"class_attribute_id": 1,
|
||||
"character_class_id": 1,
|
||||
"level": 2
|
||||
}
|
||||
],
|
||||
"ClassAttributeOption": [
|
||||
{
|
||||
"attribute_id": 1,
|
||||
"name": "Archery"
|
||||
},
|
||||
{
|
||||
"attribute_id": 1,
|
||||
"name": "Battlemaster"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -26,16 +26,26 @@ def test_create_character(db, classes, ancestries):
|
|||
db.add(char)
|
||||
assert char.levels == {"fighter": 1}
|
||||
assert char.level == 1
|
||||
assert char.class_attributes == []
|
||||
assert char.class_attributes == {}
|
||||
|
||||
# 'fighting style' is available, but not at this level
|
||||
fighting_style = char.classes["fighter"].attributes_by_level[2]["Fighting Style"]
|
||||
assert char.add_class_attribute(fighting_style, fighting_style.options[0]) is False
|
||||
db.add(char)
|
||||
assert char.class_attributes == {}
|
||||
|
||||
# level up
|
||||
char.add_class(classes["fighter"], level=2)
|
||||
db.add(char)
|
||||
assert char.levels == {"fighter": 2}
|
||||
assert char.level == 2
|
||||
assert char.class_attributes == []
|
||||
|
||||
# multiclass
|
||||
# Assign the fighting style
|
||||
assert char.add_class_attribute(fighting_style, fighting_style.options[0])
|
||||
db.add(char)
|
||||
assert char.class_attributes[fighting_style.name] == fighting_style.options[0]
|
||||
|
||||
# classes
|
||||
char.add_class(classes["rogue"], level=1)
|
||||
db.add(char)
|
||||
assert char.level == 3
|
||||
|
@ -51,6 +61,7 @@ def test_create_character(db, classes, ancestries):
|
|||
char.remove_class(classes["fighter"])
|
||||
db.add(char)
|
||||
|
||||
# ensure we're not persisting any orphan records in the map table
|
||||
# ensure we're not persisting any orphan records in the map tables
|
||||
dump = db.dump()
|
||||
assert dump["class_map"] == []
|
||||
assert dump["character_class_attribute_map"] == []
|
||||
|
|
Loading…
Reference in New Issue
Block a user