modeling many-to-many relationships
This commit is contained in:
parent
e231828425
commit
ba0e66f9af
|
@ -15,7 +15,12 @@
|
|||
<div><img id='portrait' /></div>
|
||||
<div>
|
||||
{{ field('name') }}
|
||||
{{ field('ancestry') }} {{ c.record.character_class|join(' / ') }} Level {{ field('level') }}
|
||||
{{ field('ancestry') }}
|
||||
{% for rec in c.record.classes %}
|
||||
{{ rec.character_class.name }} {{ rec.level }}
|
||||
{% endfor %}
|
||||
|
||||
{{ c.record.character_class|join(' / ') }}
|
||||
<div id='controls'>
|
||||
{{ c.form.save }} {{ c.form.delete }}
|
||||
</div>
|
||||
|
@ -135,6 +140,14 @@
|
|||
<ul>
|
||||
</ul>
|
||||
</div>
|
||||
<div class='card'>
|
||||
<div class='label'>Attributes</div>
|
||||
<ul>
|
||||
{% for rec in c.record.attributes %}
|
||||
<li>{{ rec.attribute.name }}: {{ rec.attribute.value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class='card'>
|
||||
<div class='label'>Defenses</div>
|
||||
<ul>
|
||||
|
@ -159,7 +172,7 @@
|
|||
<code>
|
||||
{{ DISABLED }}
|
||||
<code>
|
||||
{{ c }}
|
||||
{{ c.record }}
|
||||
</code>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -170,7 +183,7 @@
|
|||
console.log("{{ field }}: {{ msg }}");
|
||||
{% endfor %}
|
||||
const TRAITS = {
|
||||
{% for trait_desc, traits in c.traits.items() %}
|
||||
{% for trait_desc, traits in [] %}
|
||||
'{{ trait_desc }}': [
|
||||
{% for trait in traits %}
|
||||
{
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import enum
|
||||
import logging
|
||||
import nanoid
|
||||
from nanoid_dictionary import human_alphabet
|
||||
|
@ -33,7 +34,11 @@ class IterableMixin:
|
|||
yield attr, values[attr]
|
||||
for relname in self.__mapper__.relationships.keys():
|
||||
relvals = []
|
||||
for rel in self.__getattribute__(relname):
|
||||
reliter = self.__getattribute__(relname)
|
||||
if not reliter:
|
||||
yield relname, relvals
|
||||
continue
|
||||
for rel in reliter:
|
||||
try:
|
||||
relvals.append({k: v for k, v in vars(rel).items() if not k.startswith('_')})
|
||||
except TypeError:
|
||||
|
@ -49,6 +54,9 @@ class IterableMixin:
|
|||
serialized[key] = value
|
||||
return serialized
|
||||
|
||||
def __repr__(self):
|
||||
return str(dict(self))
|
||||
|
||||
|
||||
def multivalue_string_factory(name, column=Column(String), separator=';'):
|
||||
"""
|
||||
|
@ -76,5 +84,22 @@ def multivalue_string_factory(name, column=Column(String), separator=';'):
|
|||
})
|
||||
|
||||
|
||||
class EnumField(enum.Enum):
|
||||
"""
|
||||
A serializable enum.
|
||||
"""
|
||||
def __json__(self, request):
|
||||
return self.value
|
||||
|
||||
|
||||
SavingThrowsMixin = multivalue_string_factory('saving_throws')
|
||||
SkillsMixin = multivalue_string_factory('skills')
|
||||
|
||||
STATS = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']
|
||||
CREATURE_TYPES = ['aberation', 'beast', 'celestial', 'construct', 'dragon', 'elemental', 'fey', 'fiend', 'Giant',
|
||||
'humanoid', 'monstrosity', 'ooze', 'plant', 'undead']
|
||||
CreatureTypesEnum = EnumField("CreatureTypesEnum", ((k, k) for k in CREATURE_TYPES))
|
||||
StatsEnum = EnumField("StatsEnum", ((k, k) for k in STATS))
|
||||
|
||||
# class Table(*Bases):
|
||||
Bases = [BaseObject, IterableMixin, SlugMixin]
|
||||
|
|
|
@ -49,15 +49,40 @@ data = {
|
|||
{'id': 1, 'name': 'human', 'creature_type': 'humanoid'},
|
||||
{'id': 2, 'name': 'dragonborn', 'creature_type': 'humanoid'},
|
||||
{'id': 3, 'name': 'tiefling', 'creature_type': 'humanoid'},
|
||||
{'id': 4, 'name': 'elf', 'creature_type': 'humanoid'},
|
||||
],
|
||||
|
||||
'AncestryTrait': [
|
||||
{ 'id': 1, 'name': '+1 to All Ability Scores', },
|
||||
{ 'id': 2, 'name': 'Breath Weapon', },
|
||||
{ 'id': 3, 'name': 'Darkvision', },
|
||||
],
|
||||
|
||||
'AncestryTraitMap': [
|
||||
{ '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': 3, 'ancestry_trait_id': 3, 'level': 1}, # tiefling darkvision
|
||||
{ 'ancestry_id': 2, 'ancestry_trait_id': 2, 'level': 1}, # elf darkvision
|
||||
],
|
||||
|
||||
'CharacterClassMap': [
|
||||
{
|
||||
'character_id': 1,
|
||||
'character_class_id': 1,
|
||||
'level': 2,
|
||||
},
|
||||
{
|
||||
'character_id': 1,
|
||||
'character_class_id': 2,
|
||||
'level': 3,
|
||||
},
|
||||
],
|
||||
|
||||
'Character': [
|
||||
{
|
||||
'id': 1,
|
||||
'name': 'Sabetha',
|
||||
'ancestry': 'human',
|
||||
'character_class': ['fighter', 'rogue'],
|
||||
'level': 1,
|
||||
'ancestry_id': 1,
|
||||
'armor_class': 10,
|
||||
'max_hit_points': 14,
|
||||
'hit_points': 14,
|
||||
|
@ -76,29 +101,18 @@ data = {
|
|||
],
|
||||
|
||||
'ClassAttribute': [
|
||||
{
|
||||
'character_class_id': 1,
|
||||
'name': 'Fighting Style',
|
||||
'value': 'Archery',
|
||||
'level': 1,
|
||||
},
|
||||
{'id': 1, 'name': 'Fighting Style', 'value': 'Archery'},
|
||||
],
|
||||
|
||||
'AncestryTrait': [
|
||||
{
|
||||
'id': 1,
|
||||
'ancestry_id': 1,
|
||||
'name': '+1 to All Ability Scores',
|
||||
'level': 1,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'ancestry_id': 2,
|
||||
'name': 'Breath Weapon',
|
||||
'level': 1,
|
||||
},
|
||||
'ClassAttributeMap': [
|
||||
{'class_attribute_id': 1, 'character_class_id': 1, 'level': 2}, # Fighter: Archery fighting style
|
||||
],
|
||||
|
||||
'CharacterClassAttributeMap': [
|
||||
{'class_attribute_id': 1, 'character_id': 1}, # Sabetha: Archery fighting style
|
||||
],
|
||||
|
||||
|
||||
'Modifier': [
|
||||
# Humans
|
||||
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'str'},
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
import enum
|
||||
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy import Enum
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy import UniqueConstraint
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from ttfrog.db.base import Bases, BaseObject, IterableMixin
|
||||
from ttfrog.db.base import multivalue_string_factory
|
||||
|
||||
|
||||
STATS = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']
|
||||
|
||||
CREATURE_TYPES = ['aberation', 'beast', 'celestial', 'construct', 'dragon', 'elemental', 'fey', 'fiend', 'Giant',
|
||||
'humanoid', 'monstrosity', 'ooze', 'plant', 'undead']
|
||||
|
||||
|
||||
class EnumField(enum.Enum):
|
||||
"""
|
||||
A serializable enum.
|
||||
"""
|
||||
def __json__(self, request):
|
||||
return self.value
|
||||
|
||||
|
||||
# enums for db schemas
|
||||
StatsEnum = EnumField("StatsEnum", ((k, k) for k in STATS))
|
||||
CreatureTypesEnum = EnumField("CreatureTypesEnum", ((k, k) for k in CREATURE_TYPES))
|
||||
|
||||
CharacterClassMixin = multivalue_string_factory('character_class', Column(String, nullable=False))
|
||||
SavingThrowsMixin = multivalue_string_factory('saving_throws')
|
||||
SkillsMixin = multivalue_string_factory('skills')
|
||||
|
||||
|
||||
class Skill(*Bases):
|
||||
__tablename__ = "skill"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
description = Column(Text)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class Proficiency(*Bases):
|
||||
__tablename__ = "proficiency"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class Ancestry(*Bases):
|
||||
__tablename__ = "ancestry"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
creature_type = Column(Enum(CreatureTypesEnum))
|
||||
traits = relationship("AncestryTrait")
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class AncestryTrait(BaseObject, IterableMixin):
|
||||
__tablename__ = "ancestry_trait"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"), nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
description = Column(Text)
|
||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
||||
|
||||
|
||||
class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||
__tablename__ = "character_class"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
hit_dice = Column(String, default='1d6')
|
||||
hit_dice_stat = Column(Enum(StatsEnum))
|
||||
proficiencies = Column(String)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class ClassAttribute(BaseObject, IterableMixin):
|
||||
__tablename__ = "class_attribute"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
character_class_id = Column(Integer, ForeignKey("character_class.id"), nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
value = Column(String, nullable=False)
|
||||
description = Column(Text)
|
||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class Character(*Bases, CharacterClassMixin, SavingThrowsMixin, SkillsMixin):
|
||||
__tablename__ = "character"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
ancestry = Column(String, ForeignKey("ancestry.name"), nullable=False, default='human')
|
||||
name = Column(String, default='New Character', nullable=False)
|
||||
level = Column(Integer, default=1, nullable=False, info={'min': 1, 'max': 20})
|
||||
armor_class = Column(Integer, default=10, nullable=False, info={'min': 1, 'max': 99})
|
||||
hit_points = Column(Integer, default=1, nullable=False, info={'min': 0, 'max': 999})
|
||||
max_hit_points = Column(Integer, default=1, nullable=False, info={'min': 0, 'max': 999})
|
||||
temp_hit_points = Column(Integer, default=0, nullable=False, info={'min': 0, 'max': 999})
|
||||
speed = Column(Integer, nullable=False, default=30, info={'min': 0, 'max': 99})
|
||||
str = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
dex = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
con = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
int = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
wis = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
cha = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
proficiencies = Column(String)
|
||||
|
||||
|
||||
class Modifier(BaseObject, IterableMixin):
|
||||
__tablename__ = "modifier"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('source_table_name', 'source_table_id', 'value', 'type', 'target'),
|
||||
)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
source_table_name = Column(String, index=True, nullable=False)
|
||||
source_table_id = Column(Integer, index=True, nullable=False)
|
||||
value = Column(String, nullable=False)
|
||||
type = Column(String, nullable=False)
|
||||
target = Column(String, nullable=False)
|
||||
|
||||
|
||||
class TransactionLog(BaseObject, IterableMixin):
|
||||
__tablename__ = "transaction_log"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
source_table_name = Column(String, index=True, nullable=False)
|
||||
primary_key = Column(Integer, index=True)
|
||||
diff = Column(Text)
|
4
ttfrog/db/schema/__init__.py
Normal file
4
ttfrog/db/schema/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .character import *
|
||||
from .classes import *
|
||||
from .property import *
|
||||
from .transaction import *
|
91
ttfrog/db/schema/character.py
Normal file
91
ttfrog/db/schema/character.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
from ttfrog.db.base import Bases, BaseObject, IterableMixin, SavingThrowsMixin, SkillsMixin
|
||||
from ttfrog.db.base import CreatureTypesEnum
|
||||
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import Enum
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Ancestry',
|
||||
'AncestryTrait',
|
||||
'AncestryTraitMap',
|
||||
'CharacterClassMap',
|
||||
'CharacterClassAttributeMap',
|
||||
'Character',
|
||||
]
|
||||
|
||||
|
||||
class AncestryTraitMap(BaseObject):
|
||||
__tablename__ = "trait_map"
|
||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"), primary_key=True)
|
||||
ancestry_trait_id = Column(Integer, ForeignKey("ancestry_trait.id"), primary_key=True)
|
||||
trait = relationship("AncestryTrait", lazy='immediate')
|
||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
||||
|
||||
|
||||
class Ancestry(*Bases):
|
||||
"""
|
||||
A character ancestry ("race"), which has zero or more AncestryTraits.
|
||||
"""
|
||||
__tablename__ = "ancestry"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
creature_type = Column(Enum(CreatureTypesEnum))
|
||||
traits = relationship("AncestryTraitMap", lazy='immediate')
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class AncestryTrait(BaseObject, IterableMixin):
|
||||
"""
|
||||
A trait granted to a character via its Ancestry.
|
||||
"""
|
||||
__tablename__ = "ancestry_trait"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, nullable=False)
|
||||
description = Column(Text)
|
||||
|
||||
|
||||
class CharacterClassMap(BaseObject):
|
||||
__tablename__ = "class_map"
|
||||
character_id = Column(Integer, ForeignKey("character.id"), primary_key=True)
|
||||
character_class_id = Column(Integer, ForeignKey("character_class.id"), primary_key=True)
|
||||
character_class = relationship("CharacterClass", lazy='immediate')
|
||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20}, default=1)
|
||||
|
||||
|
||||
class CharacterClassAttributeMap(BaseObject):
|
||||
__tablename__ = "character_class_attribute_map"
|
||||
class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), primary_key=True)
|
||||
character_id = Column(Integer, ForeignKey("character.id"), primary_key=True)
|
||||
attribute = relationship("ClassAttribute", lazy='immediate')
|
||||
|
||||
|
||||
class Character(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||
__tablename__ = "character"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, default='New Character', nullable=False)
|
||||
armor_class = Column(Integer, default=10, nullable=False, info={'min': 1, 'max': 99})
|
||||
hit_points = Column(Integer, default=1, nullable=False, info={'min': 0, 'max': 999})
|
||||
max_hit_points = Column(Integer, default=1, nullable=False, info={'min': 0, 'max': 999})
|
||||
temp_hit_points = Column(Integer, default=0, nullable=False, info={'min': 0, 'max': 999})
|
||||
speed = Column(Integer, nullable=False, default=30, info={'min': 0, 'max': 99})
|
||||
str = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
dex = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
con = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
int = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
wis = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
cha = Column(Integer, nullable=False, default=10, info={'min': 0, 'max': 30})
|
||||
proficiencies = Column(String)
|
||||
|
||||
classes = relationship("CharacterClassMap")
|
||||
attributes = relationship("CharacterClassAttributeMap")
|
||||
|
||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"), nullable=False, default='1')
|
||||
ancestry = relationship("Ancestry", uselist=False)
|
43
ttfrog/db/schema/classes.py
Normal file
43
ttfrog/db/schema/classes.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from ttfrog.db.base import Bases, BaseObject, IterableMixin, SavingThrowsMixin, SkillsMixin
|
||||
from ttfrog.db.base import StatsEnum
|
||||
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import Enum
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ClassAttributeMap',
|
||||
'ClassAttribute',
|
||||
'CharacterClass',
|
||||
]
|
||||
|
||||
|
||||
class ClassAttributeMap(BaseObject):
|
||||
__tablename__ = "class_attribute_map"
|
||||
class_attribute_id = Column(Integer, ForeignKey("class_attribute.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)
|
||||
|
||||
|
||||
class ClassAttribute(BaseObject, IterableMixin):
|
||||
__tablename__ = "class_attribute"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, nullable=False)
|
||||
value = Column(String, nullable=False)
|
||||
description = Column(Text)
|
||||
|
||||
|
||||
class CharacterClass(*Bases, SavingThrowsMixin, SkillsMixin):
|
||||
__tablename__ = "character_class"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
hit_dice = Column(String, default='1d6')
|
||||
hit_dice_stat = Column(Enum(StatsEnum))
|
||||
proficiencies = Column(String)
|
||||
attributes = relationship("ClassAttributeMap")
|
46
ttfrog/db/schema/property.py
Normal file
46
ttfrog/db/schema/property.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from ttfrog.db.base import Bases, BaseObject, IterableMixin
|
||||
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy import UniqueConstraint
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Skill',
|
||||
'Proficiency',
|
||||
'Modifier',
|
||||
]
|
||||
|
||||
|
||||
class Skill(*Bases):
|
||||
__tablename__ = "skill"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
description = Column(Text)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class Proficiency(*Bases):
|
||||
__tablename__ = "proficiency"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, index=True, unique=True)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.name)
|
||||
|
||||
|
||||
class Modifier(BaseObject, IterableMixin):
|
||||
__tablename__ = "modifier"
|
||||
__table_args__ = (
|
||||
UniqueConstraint('source_table_name', 'source_table_id', 'value', 'type', 'target'),
|
||||
)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
source_table_name = Column(String, index=True, nullable=False)
|
||||
source_table_id = Column(Integer, index=True, nullable=False)
|
||||
value = Column(String, nullable=False)
|
||||
type = Column(String, nullable=False)
|
||||
target = Column(String, nullable=False)
|
14
ttfrog/db/schema/transaction.py
Normal file
14
ttfrog/db/schema/transaction.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from ttfrog.db.base import BaseObject, IterableMixin
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import Text
|
||||
|
||||
__all__ = ['TransactionLog']
|
||||
|
||||
class TransactionLog(BaseObject, IterableMixin):
|
||||
__tablename__ = "transaction_log"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
source_table_name = Column(String, index=True, nullable=False)
|
||||
primary_key = Column(Integer, index=True)
|
||||
diff = Column(Text)
|
|
@ -1,8 +1,7 @@
|
|||
from ttfrog.webserver.controllers.base import BaseController
|
||||
from ttfrog.webserver.forms import DeferredSelectField, DeferredSelectMultipleField
|
||||
from ttfrog.db.schema import Character, Ancestry, CharacterClass, AncestryTrait, Modifier, STATS
|
||||
from ttfrog.db.manager import db
|
||||
from ttfrog.attribute_map import AttributeMap
|
||||
from ttfrog.db.schema import Character, Ancestry, CharacterClass
|
||||
from ttfrog.db.base import STATS
|
||||
|
||||
from wtforms_alchemy import ModelForm
|
||||
from wtforms.fields import SubmitField, SelectMultipleField
|
||||
|
@ -17,7 +16,7 @@ class CharacterForm(ModelForm):
|
|||
save = SubmitField()
|
||||
delete = SubmitField()
|
||||
|
||||
ancestry = DeferredSelectField('Ancestry', model=Ancestry, default='human', validate_choice=True, widget=Select())
|
||||
ancestry = DeferredSelectField('Ancestry', model=Ancestry, default=1, validate_choice=True, widget=Select())
|
||||
|
||||
character_class = DeferredSelectMultipleField(
|
||||
'CharacterClass',
|
||||
|
@ -38,14 +37,3 @@ class CharacterSheet(BaseController):
|
|||
return super().resources + [
|
||||
{'type': 'script', 'uri': 'js/character_sheet.js'},
|
||||
]
|
||||
|
||||
def template_context(self, **kwargs) -> dict:
|
||||
ctx = super().template_context(**kwargs)
|
||||
if self.record.ancestry:
|
||||
ancestry = db.query(Ancestry).filter_by(name=self.record.ancestry).one()
|
||||
ctx['traits'] = {}
|
||||
for trait in db.query(AncestryTrait).filter_by(ancestry_id=ancestry.id).all():
|
||||
ctx['traits'][trait.description] = db.query(Modifier).filter_by(source_table_name=trait.__tablename__, source_table_id=trait.id).all()
|
||||
else:
|
||||
ctx['traits'] = {};
|
||||
return ctx
|
||||
|
|
25
ttfrog/webserver/controllers/json_data.py
Normal file
25
ttfrog/webserver/controllers/json_data.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import logging
|
||||
|
||||
from ttfrog.db import schema
|
||||
from ttfrog.db.manager import db
|
||||
from .base import BaseController
|
||||
|
||||
from pyramid.httpexceptions import exception_response
|
||||
|
||||
|
||||
class JsonData(BaseController):
|
||||
model = None
|
||||
model_form = None
|
||||
|
||||
def configure_for_model(self):
|
||||
try:
|
||||
self.model = getattr(schema, self.request.matchdict.get('table_name'))
|
||||
except AttributeError:
|
||||
raise exception_response(404)
|
||||
|
||||
def response(self):
|
||||
query = db.query(self.model).filter_by(**self.request.params)
|
||||
return {
|
||||
'table_name': self.model.__tablename__,
|
||||
'records': query.all()
|
||||
}
|
|
@ -4,9 +4,10 @@ from wtforms.fields import SelectField, SelectMultipleField
|
|||
class DeferredSelectMultipleField(SelectMultipleField):
|
||||
def __init__(self, *args, model=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.choices = db.query(model).all()
|
||||
self.choices = [(rec.id, rec.name) for rec in db.query(model).all()]
|
||||
|
||||
|
||||
class DeferredSelectField(SelectField):
|
||||
def __init__(self, *args, model=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.choices = db.query(model).all()
|
||||
self.choices = [(rec.id, rec.name) for rec in db.query(model).all()]
|
||||
|
|
Loading…
Reference in New Issue
Block a user