wip
This commit is contained in:
parent
dbb9461b7a
commit
44cd8fe9c9
|
@ -16,7 +16,7 @@
|
|||
<div>
|
||||
{{ field('name') }}
|
||||
{{ field('ancestry_id') }}
|
||||
{% for obj in c.form['classes'] %}
|
||||
{% for obj in c.form['class_list'] %}
|
||||
{{ obj(class='multiclass') }}
|
||||
{% endfor %}
|
||||
<span class='label'>Add Class:</span> {{ c.form['newclass'](class='multiclass') }}
|
||||
|
@ -143,8 +143,8 @@
|
|||
</div>
|
||||
<div class='card'>
|
||||
<div class='label'>Attributes</div>
|
||||
{% if c.record.class_attributes %}
|
||||
{{ field('class_attributes') }}
|
||||
{% if c.record.attribute_list %}
|
||||
{{ field('attribute_list') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class='card'>
|
||||
|
@ -173,7 +173,7 @@
|
|||
{{ field }}: {{ msg }}
|
||||
{% endfor %}
|
||||
</code>
|
||||
{{ c.record.class_attributes }}
|
||||
{{ c.record }}
|
||||
</code>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -67,8 +67,8 @@ class AncestryTrait(BaseObject, IterableMixin):
|
|||
class CharacterClassMap(BaseObject, IterableMixin):
|
||||
__tablename__ = "class_map"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
character_id = Column(Integer, ForeignKey("character.id"))
|
||||
character_class_id = Column(Integer, ForeignKey("character_class.id"))
|
||||
character_id = Column(Integer, ForeignKey("character.id"), nullable=False)
|
||||
character_class_id = Column(Integer, ForeignKey("character_class.id"), nullable=False)
|
||||
mapping = UniqueConstraint(character_id, character_class_id)
|
||||
level = Column(Integer, nullable=False, info={"min": 1, "max": 20}, default=1)
|
||||
|
||||
|
@ -76,7 +76,7 @@ class CharacterClassMap(BaseObject, IterableMixin):
|
|||
character = relationship("Character", uselist=False, viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.character.name}, {self.character_class.name}, level {self.level}"
|
||||
return "{self.character.name}, {self.character_class.name}, level {self.level}"
|
||||
|
||||
|
||||
class CharacterClassAttributeMap(BaseObject, IterableMixin):
|
||||
|
@ -118,10 +118,10 @@ 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)
|
||||
class_list = 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)
|
||||
attribute_list = 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)
|
||||
|
@ -153,8 +153,13 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
|||
if level_in_class:
|
||||
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))
|
||||
else:
|
||||
self.class_list.append(CharacterClassMap(character_id=self.id, character_class_id=newclass.id, level=level))
|
||||
for lvl in range(1, level + 1):
|
||||
if not newclass.attributes_by_level[lvl]:
|
||||
continue
|
||||
for attr_name, attr in newclass.attributes_by_level[lvl].items():
|
||||
self.add_class_attribute(attr, attr.options[0])
|
||||
|
||||
def remove_class(self, target):
|
||||
self.class_map = [m for m in self.class_map if m.id != target.id]
|
||||
|
@ -167,8 +172,9 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
|||
|
||||
def add_class_attribute(self, attribute, option):
|
||||
for thisclass in self.classes.values():
|
||||
# this test is failing?
|
||||
if attribute.name in thisclass.attributes_by_level.get(self.levels[thisclass.name], {}):
|
||||
self._class_attributes.append(
|
||||
self.attribute_list.append(
|
||||
CharacterClassAttributeMap(
|
||||
character_id=self.id, class_attribute_id=attribute.id, option_id=option.id
|
||||
)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from collections import defaultdict
|
||||
|
||||
from sqlalchemy import Column, Enum, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
@ -47,7 +49,7 @@ class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
|||
|
||||
@property
|
||||
def attributes_by_level(self):
|
||||
by_level = {}
|
||||
by_level = defaultdict(list)
|
||||
for mapping in self.attributes:
|
||||
by_level[mapping.level] = {mapping.attribute.name: mapping.attribute}
|
||||
return by_level
|
||||
|
|
|
@ -45,7 +45,8 @@ class ClassAttributesFormField(FormField):
|
|||
def process(self, *args, **kwargs):
|
||||
super().process(*args, **kwargs)
|
||||
self.character_class_map = db.query(CharacterClassAttributeMap).get(self.data["id"])
|
||||
self.label.text = self.character_class_map.character_class[0].name
|
||||
if self.character_class_map:
|
||||
self.label.text = self.character_class_map.character_class.name
|
||||
|
||||
|
||||
class ClassAttributesForm(ModelForm):
|
||||
|
@ -56,6 +57,7 @@ class ClassAttributesForm(ModelForm):
|
|||
|
||||
def __init__(self, formdata=None, obj=None, prefix=None):
|
||||
if obj:
|
||||
logging.debug(f"Loading existing attribute {self = } {formdata = } {obj = }")
|
||||
obj = db.query(CharacterClassAttributeMap).get(obj)
|
||||
super().__init__(formdata=formdata, obj=obj, prefix=prefix)
|
||||
|
||||
|
@ -77,6 +79,7 @@ class MulticlassForm(ModelForm):
|
|||
to an instance. This will ensure that the rendered field is populated with the current
|
||||
value of the class_map.
|
||||
"""
|
||||
logging.debug(f"Loading existing class {self = } {formdata = } {obj = }")
|
||||
if obj:
|
||||
obj = db.query(CharacterClassMap).get(obj)
|
||||
super().__init__(formdata=formdata, obj=obj, prefix=prefix)
|
||||
|
@ -90,10 +93,10 @@ class CharacterForm(ModelForm):
|
|||
save = SubmitField()
|
||||
delete = SubmitField()
|
||||
ancestry_id = DeferredSelectField("Ancestry", model=Ancestry, default=1, validate_choice=True, widget=Select())
|
||||
classes = FieldList(FormField(MulticlassForm, label=None, widget=ListWidget()), min_entries=0)
|
||||
class_list = FieldList(FormField(MulticlassForm, label=None, widget=ListWidget()), min_entries=0)
|
||||
newclass = FormField(MulticlassForm, widget=ListWidget())
|
||||
|
||||
class_attributes = FieldList(
|
||||
attribute_list = FieldList(
|
||||
ClassAttributesFormField(ClassAttributesForm, widget=ClassAttributeWidget()), min_entries=1
|
||||
)
|
||||
|
||||
|
@ -115,12 +118,12 @@ class CharacterSheet(BaseController):
|
|||
Validate multiclass fields in form data.
|
||||
"""
|
||||
ret = super().validate()
|
||||
if not self.form.data["classes"]:
|
||||
if not self.form.data["class_list"]:
|
||||
return ret
|
||||
|
||||
err = ""
|
||||
total_level = 0
|
||||
for field in self.form.data["classes"]:
|
||||
for field in self.form.data["class_list"]:
|
||||
level = field.get("level")
|
||||
total_level += level
|
||||
if level not in VALID_LEVELS:
|
||||
|
@ -134,40 +137,16 @@ class CharacterSheet(BaseController):
|
|||
return ret and True
|
||||
|
||||
def add_class_attributes(self):
|
||||
# prefetch the records for each of the character's classes
|
||||
classes_by_id = {
|
||||
c.id: c
|
||||
for c in db.query(CharacterClass)
|
||||
.filter(CharacterClass.id.in_(c.character_class_id for c in self.record.class_map))
|
||||
.all()
|
||||
}
|
||||
|
||||
assigned = [int(m.class_attribute_id) for m in self.record.character_class_attribute_map]
|
||||
logging.debug(f"{assigned = }")
|
||||
|
||||
# step through the list of class mappings for this character
|
||||
for class_map in self.record.class_map:
|
||||
thisclass = classes_by_id[class_map.character_class_id]
|
||||
|
||||
# assign each class attribute available at the character's current
|
||||
# level to the list of the character's class attributes
|
||||
for attr_map in [a for a in thisclass.attributes if a.level <= class_map.level]:
|
||||
# when creating a record, assign the first of the available
|
||||
# options to the character's class attribute.
|
||||
default_option = (
|
||||
db.query(ClassAttributeOption).filter_by(attribute_id=attr_map.class_attribute_id).first()
|
||||
)
|
||||
|
||||
if attr_map.class_attribute_id not in assigned:
|
||||
self.record.class_attributes.append(
|
||||
{
|
||||
"class_attribute_id": attr_map.class_attribute_id,
|
||||
"option_id": default_option.id,
|
||||
}
|
||||
)
|
||||
for class_name, class_def in self.record.classes.items():
|
||||
logging.error(f"{class_name = }, {class_def = }")
|
||||
for level in range(1, self.record.levels[class_name] + 1):
|
||||
for attr in class_def.attributes_by_level.get(level, None):
|
||||
self.record.add_class_attribute(attr, attr.options[0])
|
||||
|
||||
def save_callback(self):
|
||||
self.add_class_attributes()
|
||||
# self.add_class_attributes()
|
||||
pass
|
||||
|
||||
def populate(self):
|
||||
"""
|
||||
|
@ -176,16 +155,16 @@ class CharacterSheet(BaseController):
|
|||
"""
|
||||
|
||||
# multiclass form
|
||||
classes_formdata = self.form.data["classes"]
|
||||
classes_formdata = self.form.data["class_list"]
|
||||
classes_formdata.append(self.form.data["newclass"])
|
||||
del self.form.classes
|
||||
del self.form.class_list
|
||||
del self.form.newclass
|
||||
|
||||
# class attributes
|
||||
attrs_formdata = self.form.data["class_attributes"]
|
||||
del self.form.class_attributes
|
||||
attrs_formdata = self.form.data["attribute_list"]
|
||||
del self.form.attribute_list
|
||||
|
||||
super().populate()
|
||||
|
||||
self.record.classes = self.populate_association("character_class_id", classes_formdata)
|
||||
self.record.class_attributes = self.populate_association("class_attribute_id", attrs_formdata)
|
||||
self.record.class_list = self.populate_association("character_class_id", classes_formdata)
|
||||
self.record.attribute_list = self.populate_association("class_attribute_id", attrs_formdata)
|
||||
|
|
|
@ -30,12 +30,16 @@ def db(monkeypatch):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def classes(db):
|
||||
def classes_factory(db):
|
||||
load_fixture(db, "classes")
|
||||
return dict((rec.name, rec) for rec in db.session.query(schema.CharacterClass).all())
|
||||
def factory():
|
||||
return dict((rec.name, rec) for rec in db.session.query(schema.CharacterClass).all())
|
||||
return factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ancestries(db):
|
||||
def ancestries_factory(db):
|
||||
load_fixture(db, "ancestry")
|
||||
return dict((rec.name, rec) for rec in db.session.query(schema.Ancestry).all())
|
||||
def factory():
|
||||
return dict((rec.name, rec) for rec in db.session.query(schema.Ancestry).all())
|
||||
return factory
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
from ttfrog.db import schema
|
||||
|
||||
|
||||
def test_create_character(db, classes, ancestries):
|
||||
def test_create_character(db, classes_factory, ancestries_factory):
|
||||
with db.transaction():
|
||||
# load the fixtures so they are bound to the current session
|
||||
classes = classes_factory()
|
||||
ancestries = ancestries_factory()
|
||||
darkvision = db.session.query(schema.AncestryTrait).filter_by(name="Darkvision")[0]
|
||||
|
||||
# create a human character (the default)
|
||||
|
|
Loading…
Reference in New Issue
Block a user