adding support for managing class attributes
This commit is contained in:
parent
75b9aec28e
commit
304a4d9c79
|
@ -143,11 +143,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class='card'>
|
<div class='card'>
|
||||||
<div class='label'>Attributes</div>
|
<div class='label'>Attributes</div>
|
||||||
<ul>
|
{% if c.record.class_attributes %}
|
||||||
{% for rec in c.record.attributes %}
|
{{ field('class_attributes') }}
|
||||||
<li>{{ rec.attribute.name }}: {{ rec.attribute.value }}</li>
|
{% endif %}
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class='card'>
|
<div class='card'>
|
||||||
<div class='label'>Defenses</div>
|
<div class='label'>Defenses</div>
|
||||||
|
@ -171,18 +169,17 @@
|
||||||
<div style='clear:both;display:block;'>
|
<div style='clear:both;display:block;'>
|
||||||
<h2>Debug</h2>
|
<h2>Debug</h2>
|
||||||
<code>
|
<code>
|
||||||
{{ DISABLED }}
|
{% for field, msg in c.form.errors.items() %}
|
||||||
<code>
|
{{ field }}: {{ msg }}
|
||||||
{{ c.record }}
|
{% endfor %}
|
||||||
|
</code>
|
||||||
|
{{ c.record.class_attributes }}
|
||||||
</code>
|
</code>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
<script type='text/javascript'>
|
<script type='text/javascript'>
|
||||||
{% for field, msg in c.form.errors.items() %}
|
|
||||||
console.log("{{ field }}: {{ msg }}");
|
|
||||||
{% endfor %}
|
|
||||||
const TRAITS = {
|
const TRAITS = {
|
||||||
{% for trait_desc, traits in [] %}
|
{% for trait_desc, traits in [] %}
|
||||||
'{{ trait_desc }}': [
|
'{{ trait_desc }}': [
|
||||||
|
|
|
@ -53,16 +53,16 @@ data = {
|
||||||
],
|
],
|
||||||
|
|
||||||
'AncestryTrait': [
|
'AncestryTrait': [
|
||||||
{ 'id': 1, 'name': '+1 to All Ability Scores', },
|
{'id': 1, 'name': '+1 to All Ability Scores', },
|
||||||
{ 'id': 2, 'name': 'Breath Weapon', },
|
{'id': 2, 'name': 'Breath Weapon', },
|
||||||
{ 'id': 3, 'name': 'Darkvision', },
|
{'id': 3, 'name': 'Darkvision', },
|
||||||
],
|
],
|
||||||
|
|
||||||
'AncestryTraitMap': [
|
'AncestryTraitMap': [
|
||||||
{ 'ancestry_id': 1, 'ancestry_trait_id': 1, 'level': 1}, # human +1 to scores
|
{'ancestry_id': 1, 'ancestry_trait_id': 1, 'level': 1}, # human +1 to scores
|
||||||
{ 'ancestry_id': 2, 'ancestry_trait_id': 2, 'level': 1}, # dragonborn breath weapon
|
{'ancestry_id': 2, 'ancestry_trait_id': 2, 'level': 1}, # dragonborn breath weapon
|
||||||
{ 'ancestry_id': 3, 'ancestry_trait_id': 3, 'level': 1}, # tiefling darkvision
|
{'ancestry_id': 3, 'ancestry_trait_id': 3, 'level': 1}, # tiefling darkvision
|
||||||
{ 'ancestry_id': 2, 'ancestry_trait_id': 2, 'level': 1}, # elf darkvision
|
{'ancestry_id': 2, 'ancestry_trait_id': 2, 'level': 1}, # elf darkvision
|
||||||
],
|
],
|
||||||
|
|
||||||
'CharacterClassMap': [
|
'CharacterClassMap': [
|
||||||
|
@ -101,15 +101,26 @@ data = {
|
||||||
],
|
],
|
||||||
|
|
||||||
'ClassAttribute': [
|
'ClassAttribute': [
|
||||||
{'id': 1, 'name': 'Fighting Style', 'value': 'Archery'},
|
{'id': 1, 'name': 'Fighting Style'},
|
||||||
|
{'id': 2, 'name': 'Another Attribute'},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'ClassAttributeOption': [
|
||||||
|
{'id': 1, 'attribute_id': 1, 'name': 'Archery'},
|
||||||
|
{'id': 2, 'attribute_id': 1, 'name': 'Battlemaster'},
|
||||||
|
{'id': 3, 'attribute_id': 2, 'name': 'Another Option 1'},
|
||||||
|
{'id': 4, 'attribute_id': 2, 'name': 'Another Option 2'},
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
'ClassAttributeMap': [
|
'ClassAttributeMap': [
|
||||||
{'class_attribute_id': 1, 'character_class_id': 1, 'level': 2}, # Fighter: Archery fighting style
|
{'class_attribute_id': 1, 'character_class_id': 1, 'level': 2}, # Fighter: Fighting Style
|
||||||
|
{'class_attribute_id': 2, 'character_class_id': 1, 'level': 1}, # Fighter: Another Attr
|
||||||
],
|
],
|
||||||
|
|
||||||
'CharacterClassAttributeMap': [
|
'CharacterClassAttributeMap': [
|
||||||
{'class_attribute_id': 1, 'character_id': 1}, # Sabetha: Archery fighting style
|
{'character_id': 1, 'class_attribute_id': 2, 'option_id': 4}, # Sabetha, another option, option 2
|
||||||
|
{'character_id': 1, 'class_attribute_id': 1, 'option_id': 1}, # Sabetha, fighting style, archery
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
from ttfrog.db.base import Bases, BaseObject, IterableMixin, SavingThrowsMixin, SkillsMixin
|
from ttfrog.db.base import Bases, BaseObject, IterableMixin, SavingThrowsMixin, SkillsMixin
|
||||||
from ttfrog.db.base import CreatureTypesEnum
|
from ttfrog.db.base import CreatureTypesEnum
|
||||||
|
|
||||||
|
@ -24,11 +21,17 @@ __all__ = [
|
||||||
'Character',
|
'Character',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def class_map_creator(fields):
|
def class_map_creator(fields):
|
||||||
if isinstance(fields, CharacterClassMap):
|
if isinstance(fields, CharacterClassMap):
|
||||||
return fields
|
return fields
|
||||||
return CharacterClassMap(**fields)
|
return CharacterClassMap(**fields)
|
||||||
|
|
||||||
|
def attr_map_creator(fields):
|
||||||
|
if isinstance(fields, CharacterClassAttributeMap):
|
||||||
|
return fields
|
||||||
|
return CharacterClassAttributeMap(**fields)
|
||||||
|
|
||||||
|
|
||||||
class AncestryTraitMap(BaseObject):
|
class AncestryTraitMap(BaseObject):
|
||||||
__tablename__ = "trait_map"
|
__tablename__ = "trait_map"
|
||||||
|
@ -62,7 +65,7 @@ class AncestryTrait(BaseObject, IterableMixin):
|
||||||
description = Column(Text)
|
description = Column(Text)
|
||||||
|
|
||||||
|
|
||||||
class CharacterClassMap(BaseObject):
|
class CharacterClassMap(BaseObject, IterableMixin):
|
||||||
__tablename__ = "class_map"
|
__tablename__ = "class_map"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
character_id = Column(Integer, ForeignKey("character.id"))
|
character_id = Column(Integer, ForeignKey("character.id"))
|
||||||
|
@ -73,11 +76,16 @@ class CharacterClassMap(BaseObject):
|
||||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20}, default=1)
|
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20}, default=1)
|
||||||
|
|
||||||
|
|
||||||
class CharacterClassAttributeMap(BaseObject):
|
class CharacterClassAttributeMap(BaseObject, IterableMixin):
|
||||||
__tablename__ = "character_class_attribute_map"
|
__tablename__ = "character_class_attribute_map"
|
||||||
class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), primary_key=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
character_id = Column(Integer, ForeignKey("character.id"), primary_key=True)
|
character_id = Column(Integer, ForeignKey("character.id"), nullable=False)
|
||||||
attribute = relationship("ClassAttribute", lazy='immediate')
|
class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), nullable=False)
|
||||||
|
option_id = Column(Integer, ForeignKey("class_attribute_option.id"), nullable=False)
|
||||||
|
mapping = UniqueConstraint(character_id, class_attribute_id)
|
||||||
|
|
||||||
|
class_attribute = relationship("ClassAttribute", lazy='immediate')
|
||||||
|
option = relationship("ClassAttributeOption", lazy='immediate')
|
||||||
|
|
||||||
|
|
||||||
class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||||
|
@ -100,7 +108,8 @@ class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||||
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)
|
||||||
|
|
||||||
attributes = relationship("CharacterClassAttributeMap")
|
character_class_attribute_map = relationship("CharacterClassAttributeMap", cascade='all,delete,delete-orphan')
|
||||||
|
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)
|
||||||
|
|
|
@ -13,15 +13,15 @@ from sqlalchemy.orm import relationship
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'ClassAttributeMap',
|
'ClassAttributeMap',
|
||||||
'ClassAttribute',
|
'ClassAttribute',
|
||||||
|
'ClassAttributeOption',
|
||||||
'CharacterClass',
|
'CharacterClass',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ClassAttributeMap(BaseObject):
|
class ClassAttributeMap(BaseObject, IterableMixin):
|
||||||
__tablename__ = "class_attribute_map"
|
__tablename__ = "class_attribute_map"
|
||||||
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)
|
||||||
attribute = relationship("ClassAttribute", lazy='immediate')
|
|
||||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20}, default=1)
|
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20}, default=1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,8 +29,17 @@ 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)
|
||||||
value = Column(String, nullable=False)
|
|
||||||
description = Column(Text)
|
def __repr__(self):
|
||||||
|
return f"{self.id}: {self.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class ClassAttributeOption(BaseObject, IterableMixin):
|
||||||
|
__tablename__ = "class_attribute_option"
|
||||||
|
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):
|
class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||||
|
|
|
@ -100,10 +100,27 @@ class BaseController:
|
||||||
def populate(self):
|
def populate(self):
|
||||||
self.form.populate_obj(self.record)
|
self.form.populate_obj(self.record)
|
||||||
|
|
||||||
|
def populate_association(self, key, formdata):
|
||||||
|
populated = []
|
||||||
|
for field in formdata:
|
||||||
|
map_id = field.pop('id')
|
||||||
|
map_id = int(map_id) if map_id else 0
|
||||||
|
if not field[key]:
|
||||||
|
continue
|
||||||
|
elif not map_id:
|
||||||
|
populated.append(field)
|
||||||
|
else:
|
||||||
|
field['id'] = map_id
|
||||||
|
populated.append(field)
|
||||||
|
return populated
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
return self.form.validate()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
if not self.form.save.data:
|
if not self.form.save.data:
|
||||||
return
|
return
|
||||||
if not self.form.validate():
|
if not self.validate():
|
||||||
return
|
return
|
||||||
logging.debug(f"{self.form.data = }")
|
logging.debug(f"{self.form.data = }")
|
||||||
# previous = dict(self.record)
|
# previous = dict(self.record)
|
||||||
|
@ -112,7 +129,8 @@ class BaseController:
|
||||||
# transaction_log.record(previous, self.record)
|
# transaction_log.record(previous, self.record)
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
db.add(self.record)
|
db.add(self.record)
|
||||||
logging.debug(f"Added {self.record = }")
|
self.save_callback()
|
||||||
|
logging.debug(f"Saved {self.record = }")
|
||||||
location = self.request.current_route_path()
|
location = self.request.current_route_path()
|
||||||
if self.record.slug not in location:
|
if self.record.slug not in location:
|
||||||
location = f"{location}/{self.record.uri}"
|
location = f"{location}/{self.record.uri}"
|
||||||
|
|
|
@ -3,7 +3,15 @@ import logging
|
||||||
from ttfrog.webserver.controllers.base import BaseController
|
from ttfrog.webserver.controllers.base import BaseController
|
||||||
from ttfrog.webserver.forms import DeferredSelectField
|
from ttfrog.webserver.forms import DeferredSelectField
|
||||||
from ttfrog.webserver.forms import NullableDeferredSelectField
|
from ttfrog.webserver.forms import NullableDeferredSelectField
|
||||||
from ttfrog.db.schema import Character, Ancestry, CharacterClass, CharacterClassMap
|
from ttfrog.db.schema import (
|
||||||
|
Character,
|
||||||
|
Ancestry,
|
||||||
|
CharacterClass,
|
||||||
|
CharacterClassMap,
|
||||||
|
ClassAttributeOption,
|
||||||
|
CharacterClassAttributeMap
|
||||||
|
)
|
||||||
|
|
||||||
from ttfrog.db.base import STATS
|
from ttfrog.db.base import STATS
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.manager import db
|
||||||
|
|
||||||
|
@ -11,14 +19,37 @@ from wtforms_alchemy import ModelForm
|
||||||
from wtforms.fields import SubmitField, SelectField, SelectMultipleField, FieldList, FormField, HiddenField
|
from wtforms.fields import SubmitField, SelectField, SelectMultipleField, FieldList, FormField, HiddenField
|
||||||
from wtforms.widgets import Select, ListWidget
|
from wtforms.widgets import Select, ListWidget
|
||||||
from wtforms import ValidationError
|
from wtforms import ValidationError
|
||||||
|
from wtforms.validators import Optional
|
||||||
|
|
||||||
VALID_LEVELS = range(1, 21)
|
VALID_LEVELS = range(1, 21)
|
||||||
|
|
||||||
|
|
||||||
|
class ClassAttributesForm(ModelForm):
|
||||||
|
id = HiddenField()
|
||||||
|
class_attribute_id = HiddenField()
|
||||||
|
|
||||||
|
option_id = SelectField(
|
||||||
|
widget=Select(),
|
||||||
|
choices=[],
|
||||||
|
validators=[Optional()],
|
||||||
|
coerce=int
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, formdata=None, obj=None, prefix=None):
|
||||||
|
if obj:
|
||||||
|
obj = db.query(CharacterClassAttributeMap).get(obj)
|
||||||
|
super().__init__(formdata=formdata, obj=obj, prefix=prefix)
|
||||||
|
|
||||||
|
if obj:
|
||||||
|
options = db.query(ClassAttributeOption).filter_by(attribute_id=obj.class_attribute.id)
|
||||||
|
self.option_id.label = obj.class_attribute.name
|
||||||
|
self.option_id.choices = [(rec.id, rec.name) for rec in options.all()]
|
||||||
|
|
||||||
|
|
||||||
class MulticlassForm(ModelForm):
|
class MulticlassForm(ModelForm):
|
||||||
|
|
||||||
id = HiddenField()
|
id = HiddenField()
|
||||||
character_class_id = NullableDeferredSelectField(
|
character_class_id = NullableDeferredSelectField(
|
||||||
'CharacterClass',
|
|
||||||
model=CharacterClass,
|
model=CharacterClass,
|
||||||
validate_choice=True,
|
validate_choice=True,
|
||||||
widget=Select(),
|
widget=Select(),
|
||||||
|
@ -32,7 +63,8 @@ class MulticlassForm(ModelForm):
|
||||||
to an instance. This will ensure that the rendered field is populated with the current
|
to an instance. This will ensure that the rendered field is populated with the current
|
||||||
value of the class_map.
|
value of the class_map.
|
||||||
"""
|
"""
|
||||||
obj = db.query(CharacterClassMap).get(obj)
|
if obj:
|
||||||
|
obj = db.query(CharacterClassMap).get(obj)
|
||||||
super().__init__(formdata=formdata, obj=obj, prefix=prefix)
|
super().__init__(formdata=formdata, obj=obj, prefix=prefix)
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,6 +78,9 @@ class CharacterForm(ModelForm):
|
||||||
ancestry_id = DeferredSelectField('Ancestry', model=Ancestry, default=1, validate_choice=True, widget=Select())
|
ancestry_id = DeferredSelectField('Ancestry', model=Ancestry, default=1, validate_choice=True, widget=Select())
|
||||||
classes = FieldList(FormField(MulticlassForm, widget=ListWidget()), min_entries=0)
|
classes = FieldList(FormField(MulticlassForm, widget=ListWidget()), min_entries=0)
|
||||||
newclass = FormField(MulticlassForm, widget=ListWidget())
|
newclass = FormField(MulticlassForm, widget=ListWidget())
|
||||||
|
|
||||||
|
class_attributes = FieldList(FormField(ClassAttributesForm, widget=ListWidget()), min_entries=1)
|
||||||
|
|
||||||
saving_throws = SelectMultipleField('Saving Throws', validate_choice=True, choices=STATS)
|
saving_throws = SelectMultipleField('Saving Throws', validate_choice=True, choices=STATS)
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,62 +94,82 @@ class CharacterSheet(BaseController):
|
||||||
{'type': 'script', 'uri': 'js/character_sheet.js'},
|
{'type': 'script', 'uri': 'js/character_sheet.js'},
|
||||||
]
|
]
|
||||||
|
|
||||||
def populate_class_map(self, formdata):
|
def validate_callback(self):
|
||||||
"""
|
|
||||||
Populate the record's class_map association_proxy with dictionaries of
|
|
||||||
CharacterClassMap field data. The creator factory on the proxy will
|
|
||||||
convert dictionary data to CharacterClassMap instances..
|
|
||||||
"""
|
|
||||||
populated = []
|
|
||||||
for field in formdata:
|
|
||||||
class_map_id = field.pop('id')
|
|
||||||
class_map_id = int(class_map_id) if class_map_id else 0
|
|
||||||
logging.debug(f"{class_map_id = }, {field = }, {self.record.classes = }")
|
|
||||||
if not field['character_class_id']:
|
|
||||||
continue
|
|
||||||
elif not class_map_id:
|
|
||||||
populated.append(field)
|
|
||||||
else:
|
|
||||||
field['id'] = class_map_id
|
|
||||||
populated.append(field)
|
|
||||||
self.record.classes = populated
|
|
||||||
|
|
||||||
def validate_multiclass_form(self):
|
|
||||||
"""
|
"""
|
||||||
Validate multiclass fields in form data.
|
Validate multiclass fields in form data.
|
||||||
"""
|
"""
|
||||||
|
ret = super().validate()
|
||||||
|
if not self.form.data['classes']:
|
||||||
|
return ret
|
||||||
|
|
||||||
err = ""
|
err = ""
|
||||||
total_level = 0
|
total_level = 0
|
||||||
for field in self.form.data['classes']:
|
for field in self.form.data['classes']:
|
||||||
level = field.get('level', 0)
|
level = field.get('level')
|
||||||
total_level += level
|
total_level += level
|
||||||
if level not in VALID_LEVELS:
|
if level not in VALID_LEVELS:
|
||||||
err = f"Multiclass form field {field = } level is outside possible range."
|
err = f"Multiclass form field {field = } level is outside possible range."
|
||||||
break
|
break
|
||||||
if self.record.id and field.get('character_id', None) != self.record.id:
|
|
||||||
err = f"Multiclass form field {field = } does not match character ID {self.record.id}"
|
|
||||||
break
|
|
||||||
if total_level not in VALID_LEVELS:
|
if total_level not in VALID_LEVELS:
|
||||||
err = f"Total level for all multiclasses ({total_level}) is outside possible range."
|
err = f"Total level for all multiclasses ({total_level}) is outside possible range."
|
||||||
if err:
|
if err:
|
||||||
logging.error(err)
|
logging.error(err)
|
||||||
raise ValidationError(err)
|
raise ValidationError(err)
|
||||||
|
return ret and True
|
||||||
|
|
||||||
def validate(self):
|
def add_class_attributes(self):
|
||||||
"""
|
# prefetch the records for each of the character's classes
|
||||||
Add custom validation of the multiclass form data to standard form validation.
|
classes_by_id = {
|
||||||
"""
|
c.id: c for c in db.query(CharacterClass).filter(CharacterClass.id.in_(
|
||||||
super().validate()
|
c.character_class_id for c in self.record.class_map
|
||||||
self.validate_multiclass_form()
|
)).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.character_class_attribute_map.append(
|
||||||
|
CharacterClassAttributeMap(
|
||||||
|
class_attribute_id=attr_map.class_attribute_id,
|
||||||
|
option=default_option
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_callback(self):
|
||||||
|
self.add_class_attributes()
|
||||||
|
|
||||||
def populate(self):
|
def populate(self):
|
||||||
"""
|
"""
|
||||||
Delete the multiclass form data before calling form.populate_obj() and use
|
Delete the association proxies' form data before calling form.populate_obj(),
|
||||||
our own method for populating the fieldlist.
|
and instead use our own methods for populating the fieldlist.
|
||||||
"""
|
"""
|
||||||
formdata = self.form.data['classes']
|
|
||||||
formdata.append(self.form.data['newclass'])
|
# multiclass form
|
||||||
|
classes_formdata = self.form.data['classes']
|
||||||
|
classes_formdata.append(self.form.data['newclass'])
|
||||||
del self.form.classes
|
del self.form.classes
|
||||||
del self.form.newclass
|
del self.form.newclass
|
||||||
|
|
||||||
|
# class attributes
|
||||||
|
attrs_formdata = self.form.data['class_attributes']
|
||||||
|
del self.form.class_attributes
|
||||||
|
|
||||||
super().populate()
|
super().populate()
|
||||||
self.populate_class_map(formdata)
|
|
||||||
|
self.record.classes = self.populate_association('character_class_id', classes_formdata)
|
||||||
|
self.record.class_attributes = self.populate_association('class_attribute_id', attrs_formdata)
|
||||||
|
|
|
@ -11,7 +11,7 @@ class DeferredSelectMultipleField(SelectMultipleField):
|
||||||
class DeferredSelectField(SelectField):
|
class DeferredSelectField(SelectField):
|
||||||
def __init__(self, *args, model=None, **kwargs):
|
def __init__(self, *args, model=None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.choices = [(rec.id, rec.name) for rec in db.query(model).all()]
|
self.choices = [(rec.id, getattr(rec, 'name', str(rec))) for rec in db.query(model).all()]
|
||||||
|
|
||||||
|
|
||||||
class NullableDeferredSelectField(DeferredSelectField):
|
class NullableDeferredSelectField(DeferredSelectField):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user