Adding slugs, refactoring
This commit is contained in:
parent
64451ddf8b
commit
8f17ddfb05
|
@ -1,5 +1,15 @@
|
|||
<div style='float:left; max-width:20%'>
|
||||
<ul>
|
||||
{% for char in all_characters %}
|
||||
<li><a href="/sheet/{{char['slug']}}/{{char['name']}}">{{ char['name'] }}</a></li>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<h1>{{ character.name }}</h1>
|
||||
{{ form.display(value=character) }}
|
||||
{{ flash }}
|
||||
|
||||
<pre>
|
||||
{{ character }}
|
||||
</pre>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from ttfrog.db import db, session
|
||||
|
@ -18,17 +16,6 @@ data = {
|
|||
}
|
||||
|
||||
|
||||
def slug_from_rec(rec):
|
||||
"""
|
||||
Create a uniquish slug from a dictionary.
|
||||
"""
|
||||
sha1bytes = hashlib.sha1(str(rec).encode())
|
||||
return '-'.join([
|
||||
base64.urlsafe_b64encode(sha1bytes.digest()).decode("ascii")[:10],
|
||||
rec.get('name', '') # will need to normalize this for URLs
|
||||
])
|
||||
|
||||
|
||||
def bootstrap():
|
||||
"""
|
||||
Initialize the database with source data. Idempotent; will skip anything that already exists.
|
||||
|
@ -39,13 +26,17 @@ def bootstrap():
|
|||
logging.debug("No bootstrap data for table {table_name}; skipping.")
|
||||
continue
|
||||
for rec in data[table_name]:
|
||||
if 'slug' in table.columns:
|
||||
rec['slug'] = slug_from_rec(rec)
|
||||
stmt = table.insert().values(**rec).prefix_with("OR IGNORE")
|
||||
result = session.execute(stmt)
|
||||
session.commit()
|
||||
last_id = result.inserted_primary_key[0]
|
||||
if last_id == 0:
|
||||
result, error = db.execute(stmt)
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
|
||||
rec['id'] = result.inserted_primary_key[0]
|
||||
if rec['id'] == 0:
|
||||
logging.info(f"Skipped existing {table_name} {rec}")
|
||||
else:
|
||||
logging.info(f"Created {table_name} {result.inserted_primary_key[0]}: {rec}")
|
||||
continue
|
||||
|
||||
if 'slug' in table.columns:
|
||||
rec['slug'] = db.slugify(rec)
|
||||
db.update(table, **rec)
|
||||
logging.info(f"Created {table_name} {rec}")
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
from functools import cached_property
|
||||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from ttfrog.path import database
|
||||
from ttfrog.db.schema import metadata
|
||||
|
@ -32,17 +36,39 @@ class SQLDatabaseManager:
|
|||
def query(self, *args, **kwargs):
|
||||
return self.DBSession.query(*args, **kwargs)
|
||||
|
||||
def update(self, table, **kwargs):
|
||||
stmt = table.update().values(**kwargs)
|
||||
logging.debug(stmt)
|
||||
result = self.DBSession.execute(stmt)
|
||||
def execute(self, statement) -> tuple:
|
||||
logging.debug(statement)
|
||||
result = None
|
||||
error = None
|
||||
try:
|
||||
result = self.DBSession.execute(statement)
|
||||
self.DBSession.commit()
|
||||
return result
|
||||
except IntegrityError as exc:
|
||||
logging.error(exc)
|
||||
error = "An error occurred when saving changes."
|
||||
return result, error
|
||||
|
||||
def insert(self, table, **kwargs) -> tuple:
|
||||
stmt = table.insert().values(**kwargs)
|
||||
return self.execute(stmt)
|
||||
|
||||
def update(self, table, **kwargs):
|
||||
primary_key = kwargs.pop('id')
|
||||
stmt = table.update().values(**kwargs).where(table.columns.id == primary_key)
|
||||
return self.execute(stmt)
|
||||
|
||||
def init_model(self, engine=None):
|
||||
metadata.create_all(bind=engine or self.engine)
|
||||
return self.DBSession
|
||||
|
||||
def slugify(self, rec: dict) -> str:
|
||||
"""
|
||||
Create a uniquish slug from a dictionary.
|
||||
"""
|
||||
sha1bytes = hashlib.sha1(str(rec['id']).encode())
|
||||
return base64.urlsafe_b64encode(sha1bytes.digest()).decode("ascii")[:10]
|
||||
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
try:
|
||||
return self.tables[name]
|
||||
|
|
|
@ -14,7 +14,8 @@ metadata = MetaData()
|
|||
Ancestry = Table(
|
||||
"ancestry",
|
||||
metadata,
|
||||
Column("name", String, primary_key=True),
|
||||
Column("id", Integer, primary_key=True, autoincrement=True),
|
||||
Column("name", String, index=True, unique=True),
|
||||
Column("slug", String, index=True, unique=True),
|
||||
Column("description", UnicodeText),
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@ from wsgiref.simple_server import make_server
|
|||
import webhelpers2
|
||||
import tw2.core
|
||||
|
||||
from ttfrog.webserver.controllers import RootController
|
||||
from ttfrog.webserver.controllers.root import RootController
|
||||
from ttfrog.db import db
|
||||
import ttfrog.path
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from .root import RootController
|
||||
|
||||
__ALL__ = [RootController]
|
19
ttfrog/webserver/controllers/base.py
Normal file
19
ttfrog/webserver/controllers/base.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
import inspect
|
||||
|
||||
from tg import flash
|
||||
from tg import TGController
|
||||
from tg import tmpl_context
|
||||
from markupsafe import Markup
|
||||
|
||||
|
||||
class BaseController(TGController):
|
||||
|
||||
def _before(self, *args, **kwargs):
|
||||
tmpl_context.project_name = 'TableTop Frog'
|
||||
|
||||
def output(self, **kwargs) -> dict:
|
||||
return dict(
|
||||
page=inspect.stack()[1].function,
|
||||
flash=Markup(flash.render('flash', use_js=False)),
|
||||
**kwargs,
|
||||
)
|
|
@ -1,29 +1,72 @@
|
|||
import base64
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from tg import expose
|
||||
from tg import TGController
|
||||
from tg import flash
|
||||
from tg import validate
|
||||
from tg.controllers.util import redirect
|
||||
from ttfrog.db import db
|
||||
from ttfrog.db.schema import Character
|
||||
from ttfrog.webserver.controllers.base import BaseController
|
||||
from ttfrog.webserver.widgets import CharacterSheet
|
||||
|
||||
|
||||
class CharacterSheetController(TGController):
|
||||
class CharacterSheetController(BaseController):
|
||||
@expose()
|
||||
def _lookup(self, *parts):
|
||||
return FormController(parts[0]), parts[1:]
|
||||
slug = parts[0] if parts else ''
|
||||
return FormController(slug), parts[1:] if len(parts) > 1 else []
|
||||
|
||||
|
||||
class FormController:
|
||||
class FormController(BaseController):
|
||||
|
||||
def __init__(self, slug: str):
|
||||
super().__init__()
|
||||
self.character = dict()
|
||||
if slug:
|
||||
self.load(slug)
|
||||
self.load_from_slug(slug)
|
||||
|
||||
def load(self, slug: str) -> None:
|
||||
@property
|
||||
def uri(self):
|
||||
if self.character:
|
||||
return f"/sheet/{self.character['slug']}/{self.character['name']}"
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def all_characters(self):
|
||||
return [row._mapping for row in db.query(Character).all()]
|
||||
|
||||
def load_from_slug(self, slug) -> None:
|
||||
self.character = db.query(Character).filter(Character.columns.slug == slug)[0]._mapping
|
||||
|
||||
def save(self, fields) -> str:
|
||||
rec = dict()
|
||||
if not self.character:
|
||||
result, error = db.insert(Character, **fields)
|
||||
if error:
|
||||
return error
|
||||
fields['id'] = result.inserted_primary_key[0]
|
||||
fields['slug'] = db.slugify(fields)
|
||||
else:
|
||||
rec = dict(**self.character)
|
||||
|
||||
rec.update(**fields)
|
||||
result, error = db.update(Character, **rec)
|
||||
self.load_from_slug(rec['slug'])
|
||||
if not error:
|
||||
flash(f"{self.character['name']} updated!")
|
||||
return redirect(self.uri)
|
||||
flash(error)
|
||||
|
||||
@expose('character_sheet.html')
|
||||
def _default(self, *args, **kwargs):
|
||||
if kwargs:
|
||||
db.update(Character, **kwargs)
|
||||
self.load(self.character['slug'])
|
||||
return dict(page='sheet', form=CharacterSheet, character=self.character)
|
||||
@validate(form=CharacterSheet)
|
||||
def _default(self, *args, **fields):
|
||||
if fields:
|
||||
return self.save(fields)
|
||||
return self.output(
|
||||
form=CharacterSheet,
|
||||
character=self.character,
|
||||
all_characters=self.all_characters,
|
||||
)
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
from tg import expose
|
||||
from tg import TGController
|
||||
from tg import tmpl_context
|
||||
|
||||
from ttfrog.db import db
|
||||
from ttfrog.webserver.controllers.base import BaseController
|
||||
from ttfrog.webserver.controllers.character_sheet import CharacterSheetController
|
||||
|
||||
|
||||
class RootController(TGController):
|
||||
class RootController(BaseController):
|
||||
|
||||
# serve character sheet interface on /sheet
|
||||
sheet = CharacterSheetController()
|
||||
|
||||
def _before(self, *args, **kwargs):
|
||||
tmpl_context.project_name = 'TableTop Frog'
|
||||
|
||||
@expose('index.html')
|
||||
def index(self):
|
||||
ancestries = [row._mapping for row in db.query(db.ancestry).all()]
|
||||
return dict(page='index', content=str(ancestries))
|
||||
return self.output(content=str(ancestries))
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
import tw2.core
|
||||
import tw2.core as twc
|
||||
import tw2.forms
|
||||
from tg import lurl
|
||||
from ttfrog.db import db
|
||||
|
||||
|
||||
class CharacterSheet(tw2.forms.Form):
|
||||
class child(tw2.forms.TableLayout):
|
||||
name = tw2.forms.TextField()
|
||||
level = tw2.forms.TextField()
|
||||
ancestry_name = tw2.forms.TextField(label='Ancestry')
|
||||
id = tw2.forms.HiddenField()
|
||||
|
||||
action = ''
|
||||
|
||||
class child(tw2.forms.TableLayout):
|
||||
name = tw2.forms.TextField(validator=twc.Required)
|
||||
level = tw2.forms.SingleSelectField(
|
||||
prompt_text=None,
|
||||
options=range(1, 21),
|
||||
validator=twc.validation.IntValidator(min=1, max=20)
|
||||
)
|
||||
ancestry_name = tw2.forms.SingleSelectField(
|
||||
label='Ancestry',
|
||||
prompt_text=None,
|
||||
options=twc.Deferred(lambda: [a.name for a in db.query(db.ancestry)]),
|
||||
validator=twc.Required,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user