adding tests and helper UX to schema

This commit is contained in:
evilchili 2024-03-26 21:58:04 -07:00
parent 78115023bb
commit dbb9461b7a
4 changed files with 79 additions and 9 deletions

View File

@ -96,6 +96,7 @@ class CharacterClassAttributeMap(BaseObject, IterableMixin):
primaryjoin="CharacterClassAttributeMap.character_id == CharacterClassMap.character_id", primaryjoin="CharacterClassAttributeMap.character_id == CharacterClassMap.character_id",
secondaryjoin="CharacterClass.id == CharacterClassMap.character_class_id", secondaryjoin="CharacterClass.id == CharacterClassMap.character_class_id",
viewonly=True, viewonly=True,
uselist=False,
) )
@ -117,14 +118,18 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
proficiencies = Column(String) proficiencies = Column(String)
class_map = relationship("CharacterClassMap", cascade="all,delete,delete-orphan") 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") 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_id = Column(Integer, ForeignKey("ancestry.id"), nullable=False, default="1")
ancestry = relationship("Ancestry", uselist=False) 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 @property
def traits(self): def traits(self):
return [mapping.trait for mapping in self.ancestry.traits] return [mapping.trait for mapping in self.ancestry.traits]
@ -137,6 +142,10 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
def levels(self): def levels(self):
return dict([(mapping.character_class.name, mapping.level) for mapping in self.class_map]) 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): def add_class(self, newclass, level=1):
if level == 0: if level == 0:
return self.remove_class(newclass) 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_in_class[0]
level_in_class.level = level level_in_class.level = level
return 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): def remove_class(self, target):
self.class_map = [m for m in self.class_map if m.id != target.id] 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

View File

@ -16,12 +16,14 @@ class ClassAttributeMap(BaseObject, IterableMixin):
class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), primary_key=True) class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), primary_key=True)
character_class_id = Column(Integer, ForeignKey("character_class.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) 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): class ClassAttribute(BaseObject, IterableMixin):
__tablename__ = "class_attribute" __tablename__ = "class_attribute"
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False) name = Column(String, nullable=False)
options = relationship("ClassAttributeOption", cascade="all,delete,delete-orphan", lazy="immediate")
def __repr__(self): def __repr__(self):
return f"{self.id}: {self.name}" return f"{self.id}: {self.name}"
@ -32,7 +34,6 @@ class ClassAttributeOption(BaseObject, IterableMixin):
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False) name = Column(String, nullable=False)
attribute_id = Column(Integer, ForeignKey("class_attribute.id"), nullable=False) attribute_id = Column(Integer, ForeignKey("class_attribute.id"), nullable=False)
# attribute = relationship("ClassAttribute", uselist=False)
class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin): class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
@ -42,4 +43,11 @@ class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
hit_dice = Column(String, default="1d6") hit_dice = Column(String, default="1d6")
hit_dice_stat = Column(Enum(StatsEnum)) hit_dice_stat = Column(Enum(StatsEnum))
proficiencies = Column(String) 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

View File

@ -1,6 +1,7 @@
{ {
"CharacterClass": [ "CharacterClass": [
{ {
"id": 1,
"name": "fighter", "name": "fighter",
"hit_dice": "1d10", "hit_dice": "1d10",
"hit_dice_stat": "CON", "hit_dice_stat": "CON",
@ -9,6 +10,7 @@
"skills": ["Acrobatics", "Animal Handling", "Athletics", "History", "Insight", "Intimidation", "Perception", "Survival"] "skills": ["Acrobatics", "Animal Handling", "Athletics", "History", "Insight", "Intimidation", "Perception", "Survival"]
}, },
{ {
"id": 2,
"name": "rogue", "name": "rogue",
"hit_dice": "1d8", "hit_dice": "1d8",
"hit_dice_stat": "DEX", "hit_dice_stat": "DEX",
@ -16,5 +18,28 @@
"saving_throws": ["DEX", "INT"], "saving_throws": ["DEX", "INT"],
"skills": ["Acrobatics", "Athletics", "Deception", "Insight", "Intimidation", "Investigation", "Perception", "Performance", "Persuasion", "Sleight of Hand", "Stealth"] "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"
}
] ]
} }

View File

@ -26,16 +26,26 @@ def test_create_character(db, classes, ancestries):
db.add(char) db.add(char)
assert char.levels == {"fighter": 1} assert char.levels == {"fighter": 1}
assert char.level == 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 # level up
char.add_class(classes["fighter"], level=2) char.add_class(classes["fighter"], level=2)
db.add(char) db.add(char)
assert char.levels == {"fighter": 2} assert char.levels == {"fighter": 2}
assert char.level == 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) char.add_class(classes["rogue"], level=1)
db.add(char) db.add(char)
assert char.level == 3 assert char.level == 3
@ -51,6 +61,7 @@ def test_create_character(db, classes, ancestries):
char.remove_class(classes["fighter"]) char.remove_class(classes["fighter"])
db.add(char) 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() dump = db.dump()
assert dump["class_map"] == [] assert dump["class_map"] == []
assert dump["character_class_attribute_map"] == []