modeling many-to-many relationships
This commit is contained in:
parent
e231828425
commit
ba0e66f9af
|
@ -15,7 +15,12 @@
|
||||||
<div><img id='portrait' /></div>
|
<div><img id='portrait' /></div>
|
||||||
<div>
|
<div>
|
||||||
{{ field('name') }}
|
{{ 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'>
|
<div id='controls'>
|
||||||
{{ c.form.save }} {{ c.form.delete }}
|
{{ c.form.save }} {{ c.form.delete }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -135,6 +140,14 @@
|
||||||
<ul>
|
<ul>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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='card'>
|
||||||
<div class='label'>Defenses</div>
|
<div class='label'>Defenses</div>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -159,7 +172,7 @@
|
||||||
<code>
|
<code>
|
||||||
{{ DISABLED }}
|
{{ DISABLED }}
|
||||||
<code>
|
<code>
|
||||||
{{ c }}
|
{{ c.record }}
|
||||||
</code>
|
</code>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -170,7 +183,7 @@
|
||||||
console.log("{{ field }}: {{ msg }}");
|
console.log("{{ field }}: {{ msg }}");
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
const TRAITS = {
|
const TRAITS = {
|
||||||
{% for trait_desc, traits in c.traits.items() %}
|
{% for trait_desc, traits in [] %}
|
||||||
'{{ trait_desc }}': [
|
'{{ trait_desc }}': [
|
||||||
{% for trait in traits %}
|
{% for trait in traits %}
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import enum
|
||||||
import logging
|
import logging
|
||||||
import nanoid
|
import nanoid
|
||||||
from nanoid_dictionary import human_alphabet
|
from nanoid_dictionary import human_alphabet
|
||||||
|
@ -33,7 +34,11 @@ class IterableMixin:
|
||||||
yield attr, values[attr]
|
yield attr, values[attr]
|
||||||
for relname in self.__mapper__.relationships.keys():
|
for relname in self.__mapper__.relationships.keys():
|
||||||
relvals = []
|
relvals = []
|
||||||
for rel in self.__getattribute__(relname):
|
reliter = self.__getattribute__(relname)
|
||||||
|
if not reliter:
|
||||||
|
yield relname, relvals
|
||||||
|
continue
|
||||||
|
for rel in reliter:
|
||||||
try:
|
try:
|
||||||
relvals.append({k: v for k, v in vars(rel).items() if not k.startswith('_')})
|
relvals.append({k: v for k, v in vars(rel).items() if not k.startswith('_')})
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -49,6 +54,9 @@ class IterableMixin:
|
||||||
serialized[key] = value
|
serialized[key] = value
|
||||||
return serialized
|
return serialized
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(dict(self))
|
||||||
|
|
||||||
|
|
||||||
def multivalue_string_factory(name, column=Column(String), separator=';'):
|
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):
|
# class Table(*Bases):
|
||||||
Bases = [BaseObject, IterableMixin, SlugMixin]
|
Bases = [BaseObject, IterableMixin, SlugMixin]
|
||||||
|
|
|
@ -49,15 +49,40 @@ data = {
|
||||||
{'id': 1, 'name': 'human', 'creature_type': 'humanoid'},
|
{'id': 1, 'name': 'human', 'creature_type': 'humanoid'},
|
||||||
{'id': 2, 'name': 'dragonborn', 'creature_type': 'humanoid'},
|
{'id': 2, 'name': 'dragonborn', 'creature_type': 'humanoid'},
|
||||||
{'id': 3, 'name': 'tiefling', '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': [
|
'Character': [
|
||||||
{
|
{
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'name': 'Sabetha',
|
'name': 'Sabetha',
|
||||||
'ancestry': 'human',
|
'ancestry_id': 1,
|
||||||
'character_class': ['fighter', 'rogue'],
|
|
||||||
'level': 1,
|
|
||||||
'armor_class': 10,
|
'armor_class': 10,
|
||||||
'max_hit_points': 14,
|
'max_hit_points': 14,
|
||||||
'hit_points': 14,
|
'hit_points': 14,
|
||||||
|
@ -76,29 +101,18 @@ data = {
|
||||||
],
|
],
|
||||||
|
|
||||||
'ClassAttribute': [
|
'ClassAttribute': [
|
||||||
{
|
{'id': 1, 'name': 'Fighting Style', 'value': 'Archery'},
|
||||||
'character_class_id': 1,
|
|
||||||
'name': 'Fighting Style',
|
|
||||||
'value': 'Archery',
|
|
||||||
'level': 1,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'AncestryTrait': [
|
'ClassAttributeMap': [
|
||||||
{
|
{'class_attribute_id': 1, 'character_class_id': 1, 'level': 2}, # Fighter: Archery fighting style
|
||||||
'id': 1,
|
|
||||||
'ancestry_id': 1,
|
|
||||||
'name': '+1 to All Ability Scores',
|
|
||||||
'level': 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 2,
|
|
||||||
'ancestry_id': 2,
|
|
||||||
'name': 'Breath Weapon',
|
|
||||||
'level': 1,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'CharacterClassAttributeMap': [
|
||||||
|
{'class_attribute_id': 1, 'character_id': 1}, # Sabetha: Archery fighting style
|
||||||
|
],
|
||||||
|
|
||||||
|
|
||||||
'Modifier': [
|
'Modifier': [
|
||||||
# Humans
|
# Humans
|
||||||
{'source_table_name': 'ancestry_trait', 'source_table_id': 1, 'value': '+1', 'type': 'stat', 'target': 'str'},
|
{'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.controllers.base import BaseController
|
||||||
from ttfrog.webserver.forms import DeferredSelectField, DeferredSelectMultipleField
|
from ttfrog.webserver.forms import DeferredSelectField, DeferredSelectMultipleField
|
||||||
from ttfrog.db.schema import Character, Ancestry, CharacterClass, AncestryTrait, Modifier, STATS
|
from ttfrog.db.schema import Character, Ancestry, CharacterClass
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.base import STATS
|
||||||
from ttfrog.attribute_map import AttributeMap
|
|
||||||
|
|
||||||
from wtforms_alchemy import ModelForm
|
from wtforms_alchemy import ModelForm
|
||||||
from wtforms.fields import SubmitField, SelectMultipleField
|
from wtforms.fields import SubmitField, SelectMultipleField
|
||||||
|
@ -17,7 +16,7 @@ class CharacterForm(ModelForm):
|
||||||
save = SubmitField()
|
save = SubmitField()
|
||||||
delete = 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(
|
character_class = DeferredSelectMultipleField(
|
||||||
'CharacterClass',
|
'CharacterClass',
|
||||||
|
@ -38,14 +37,3 @@ class CharacterSheet(BaseController):
|
||||||
return super().resources + [
|
return super().resources + [
|
||||||
{'type': 'script', 'uri': 'js/character_sheet.js'},
|
{'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):
|
class DeferredSelectMultipleField(SelectMultipleField):
|
||||||
def __init__(self, *args, model=None, **kwargs):
|
def __init__(self, *args, model=None, **kwargs):
|
||||||
super().__init__(*args, **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):
|
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 = 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