fixing slugs
This commit is contained in:
parent
9cdf28502a
commit
32d9c42847
|
@ -20,6 +20,9 @@ pyramid-jinja2 = "^2.10"
|
||||||
pyramid-sqlalchemy = "^1.6"
|
pyramid-sqlalchemy = "^1.6"
|
||||||
wtforms-sqlalchemy = "^0.4.1"
|
wtforms-sqlalchemy = "^0.4.1"
|
||||||
transaction = "^4.0"
|
transaction = "^4.0"
|
||||||
|
unicode-slugify = "^0.1.5"
|
||||||
|
nanoid = "^2.0.0"
|
||||||
|
nanoid-dictionary = "^2.4.0"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{{ config['project_name'] }}{% block title %}{% endblock %}</title>
|
<title>{{ c['config']['project_name'] }}{% block title %}{% endblock %}</title>
|
||||||
<meta name="og:provider_name" content="{{ config['project_name'] }}">
|
<meta name="og:provider_name" content="{{ c['config']['project_name'] }}">
|
||||||
<link rel='stylesheet' href="{{config['static_url']}}/styles.css" />
|
<link rel='stylesheet' href="{{c['config']['static_url']}}/styles.css" />
|
||||||
{% block headers %}{% endblock %}
|
{% block headers %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -3,19 +3,19 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{{ build_list(all_records) }}
|
{{ build_list(c) }}
|
||||||
|
|
||||||
<div style='float:left;'>
|
<div style='float:left;'>
|
||||||
<h1>{{ record.name }}</h1>
|
<h1>{{ c['record'].name }}</h1>
|
||||||
|
|
||||||
<form name="character_sheet" method="post" novalidate class="form">
|
<form name="character_sheet" method="post" novalidate class="form">
|
||||||
{{ form.csrf_token }}
|
{{ c['form'].csrf_token }}
|
||||||
|
|
||||||
{% if 'process' in form.errors %}
|
{% if 'process' in c['form'].errors %}
|
||||||
Error: {{ form.errors['process'] |join(',') }}
|
Error: {{ c['form'].errors['process'] |join(',') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for field in form %}
|
{% for field in c['form'] %}
|
||||||
<li>{{ field.label }}: {{ field }} {{ field.errors|join(',') }}</li>
|
<li>{{ field.label }}: {{ field }} {{ field.errors|join(',') }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -27,9 +27,7 @@
|
||||||
{% block debug %}
|
{% block debug %}
|
||||||
<div style='clear:both;display:block;'>
|
<div style='clear:both;display:block;'>
|
||||||
<h2>Debug</h2>
|
<h2>Debug</h2>
|
||||||
<h3>Record</h3>
|
<pre>
|
||||||
<pre>{{ record }}</pre>
|
{{ c }}
|
||||||
<h3>Config</h3>
|
</pre>
|
||||||
<pre>{{ config }}</pre>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{% macro build_list(records) %}
|
{% macro build_list(c) %}
|
||||||
<div style='float:left; min-height: 90%; margin-right:5em;'>
|
<div style='float:left; min-height: 90%; margin-right:5em;'>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/sheet/">Create a Character</a></li>
|
<li><a href="{{c['routes']['sheet']}}">Create a Character</a></li>
|
||||||
{% for rec in records %}
|
{% for rec in c['all_records'] %}
|
||||||
<li><a href="/sheet/{{rec['slug']}}/{{rec['name']}}">{{ rec['name'] }}</a></li>
|
<li><a href="{{c['routes']['sheet']}}/{{rec['uri']}}">{{ rec['uri'] }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,26 @@
|
||||||
|
import nanoid
|
||||||
|
from nanoid_dictionary import human_alphabet
|
||||||
|
|
||||||
from pyramid_sqlalchemy import BaseObject
|
from pyramid_sqlalchemy import BaseObject
|
||||||
from wtforms import validators
|
from wtforms import validators
|
||||||
|
from slugify import slugify
|
||||||
|
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy import String
|
||||||
|
|
||||||
|
def genslug():
|
||||||
|
return nanoid.generate(human_alphabet[2:], 5)
|
||||||
|
|
||||||
|
|
||||||
|
class SlugMixin:
|
||||||
|
slug = Column(String, index=True, unique=True, default=genslug)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uri(self):
|
||||||
|
return '-'.join([
|
||||||
|
self.slug,
|
||||||
|
slugify(self.name.title().replace(' ', ''), ok='', only_ascii=True, lower=False)
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
class IterableMixin:
|
class IterableMixin:
|
||||||
|
@ -50,4 +71,4 @@ class FormValidatorMixin:
|
||||||
|
|
||||||
|
|
||||||
# class Table(*Bases):
|
# class Table(*Bases):
|
||||||
Bases = [BaseObject, IterableMixin, FormValidatorMixin]
|
Bases = [BaseObject, IterableMixin, FormValidatorMixin, SlugMixin]
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import logging
|
import logging
|
||||||
import transaction
|
|
||||||
|
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.manager import db
|
||||||
from ttfrog.db import schema
|
from ttfrog.db import schema
|
||||||
|
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.inspection import inspect
|
|
||||||
|
|
||||||
# move this to json or whatever
|
# move this to json or whatever
|
||||||
data = {
|
data = {
|
||||||
|
@ -33,7 +31,6 @@ def bootstrap():
|
||||||
try:
|
try:
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
db.session.add(obj)
|
db.session.add(obj)
|
||||||
obj.slug = db.slugify(rec)
|
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
if 'UNIQUE constraint failed' in str(e):
|
if 'UNIQUE constraint failed' in str(e):
|
||||||
logging.info(f"Skipping existing {table} {obj}")
|
logging.info(f"Skipping existing {table} {obj}")
|
||||||
|
|
|
@ -30,7 +30,7 @@ class SQLDatabaseManager:
|
||||||
def engine(self):
|
def engine(self):
|
||||||
return create_engine(self.url)
|
return create_engine(self.url)
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def session(self):
|
def session(self):
|
||||||
return Session
|
return Session
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ from sqlalchemy import Column
|
||||||
from sqlalchemy import Integer
|
from sqlalchemy import Integer
|
||||||
from sqlalchemy import String
|
from sqlalchemy import String
|
||||||
from sqlalchemy import ForeignKey
|
from sqlalchemy import ForeignKey
|
||||||
from sqlalchemy import CheckConstraint
|
|
||||||
# from sqlalchemy import PrimaryKeyConstraint
|
# from sqlalchemy import PrimaryKeyConstraint
|
||||||
# from sqlalchemy import DateTime
|
# from sqlalchemy import DateTime
|
||||||
|
|
||||||
|
@ -14,20 +13,18 @@ class Ancestry(*Bases):
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, index=True, unique=True)
|
name = Column(String, index=True, unique=True)
|
||||||
slug = Column(String, index=True, unique=True)
|
|
||||||
|
|
||||||
|
|
||||||
class Character(*Bases):
|
class Character(*Bases):
|
||||||
__tablename__ = "character"
|
__tablename__ = "character"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
slug = Column(String, index=True, unique=True)
|
|
||||||
ancestry = Column(String, ForeignKey("ancestry.name"), nullable=False)
|
ancestry = Column(String, ForeignKey("ancestry.name"), nullable=False)
|
||||||
name = Column(String(255), nullable=False)
|
name = Column(String(255), nullable=False)
|
||||||
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
level = Column(Integer, nullable=False, info={'min': 1, 'max': 20})
|
||||||
str = Column(Integer, info={'min': 1})
|
str = Column(Integer, info={'min': 0, 'max': 30})
|
||||||
dex = Column(Integer, info={'min': 1})
|
dex = Column(Integer, info={'min': 0, 'max': 30})
|
||||||
con = Column(Integer, info={'min': 1})
|
con = Column(Integer, info={'min': 0, 'max': 30})
|
||||||
int = Column(Integer, info={'min': 1})
|
int = Column(Integer, info={'min': 0, 'max': 30})
|
||||||
wis = Column(Integer, info={'min': 1})
|
wis = Column(Integer, info={'min': 0, 'max': 30})
|
||||||
cha = Column(Integer, info={'min': 1})
|
cha = Column(Integer, info={'min': 0, 'max': 30})
|
||||||
|
|
|
@ -1,11 +1,28 @@
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from wtforms_sqlalchemy.orm import model_form
|
from wtforms_sqlalchemy.orm import model_form
|
||||||
|
|
||||||
|
from pyramid.httpexceptions import HTTPFound
|
||||||
|
from pyramid.interfaces import IRoutesMapper
|
||||||
|
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.manager import db
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_routes(request):
|
||||||
|
uri_pattern = re.compile(r"^([^\{\*]+)")
|
||||||
|
mapper = request.registry.queryUtility(IRoutesMapper)
|
||||||
|
routes = {}
|
||||||
|
for route in mapper.get_routes():
|
||||||
|
if route.name.startswith('__'):
|
||||||
|
continue
|
||||||
|
m = uri_pattern.search(route.pattern)
|
||||||
|
if m:
|
||||||
|
routes[route.name] = m .group(0)
|
||||||
|
return routes
|
||||||
|
|
||||||
|
|
||||||
class BaseController:
|
class BaseController:
|
||||||
model = None
|
model = None
|
||||||
|
|
||||||
|
@ -13,15 +30,15 @@ class BaseController:
|
||||||
self.request = request
|
self.request = request
|
||||||
self.attrs = defaultdict(str)
|
self.attrs = defaultdict(str)
|
||||||
self.record = None
|
self.record = None
|
||||||
|
self.form = None
|
||||||
self.model_form = None
|
self.model_form = None
|
||||||
|
|
||||||
self.config = {
|
self.config = {
|
||||||
'static_url': '/static',
|
'static_url': '/static',
|
||||||
'project_name': 'TTFROG'
|
'project_name': 'TTFROG'
|
||||||
}
|
}
|
||||||
|
|
||||||
self.configure()
|
|
||||||
self.configure_for_model()
|
self.configure_for_model()
|
||||||
|
self.configure()
|
||||||
|
|
||||||
def configure_for_model(self):
|
def configure_for_model(self):
|
||||||
if not self.model:
|
if not self.model:
|
||||||
|
@ -29,7 +46,7 @@ class BaseController:
|
||||||
if not self.model_form:
|
if not self.model_form:
|
||||||
self.model_form = model_form(self.model, db_session=db.session)
|
self.model_form = model_form(self.model, db_session=db.session)
|
||||||
if not self.record:
|
if not self.record:
|
||||||
self.record = self.load_from_slug() or self.load_from_id()
|
self.record = self.get_record_from_slug()
|
||||||
|
|
||||||
if 'all_records' not in self.attrs:
|
if 'all_records' not in self.attrs:
|
||||||
self.attrs['all_records'] = db.query(self.model).all()
|
self.attrs['all_records'] = db.query(self.model).all()
|
||||||
|
@ -37,50 +54,58 @@ class BaseController:
|
||||||
def configure(self):
|
def configure(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def load_from_slug(self):
|
def get_record_from_slug(self):
|
||||||
if not self.model:
|
if not self.model:
|
||||||
return
|
return
|
||||||
|
parts = self.request.matchdict.get('uri', '').split('-')
|
||||||
parts = self.request.matchdict.get('uri', '').split('/')
|
|
||||||
if not parts:
|
if not parts:
|
||||||
return
|
return
|
||||||
|
slug = parts[0].replace('/', '')
|
||||||
|
if not slug:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
return db.query(self.model).filter(self.model.slug == parts[0])[0]
|
return db.query(self.model).filter(self.model.slug == slug)[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logging.warning(f"Could not load record with slug {parts[0]}")
|
logging.warning(f"Could not load record with slug {slug}")
|
||||||
|
|
||||||
def load_from_id(self):
|
def process_form(self):
|
||||||
post_id = self.request.POST.get('id', None)
|
|
||||||
if not post_id:
|
|
||||||
return
|
|
||||||
return db.query(self.model).get(post_id)
|
|
||||||
|
|
||||||
def form(self) -> str:
|
|
||||||
if not self.model:
|
if not self.model:
|
||||||
return
|
return False
|
||||||
|
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'POST':
|
||||||
|
|
||||||
|
# if we haven't loaded a record, we're creating a new one
|
||||||
if not self.record:
|
if not self.record:
|
||||||
self.record = self.model()
|
self.record = self.model()
|
||||||
form = self.model_form(self.request.POST, obj=self.record)
|
|
||||||
if self.model.validate(form):
|
# generate a form object using the POST form data and the db record
|
||||||
form.populate_obj(self.record)
|
self.form = self.model_form(self.request.POST, obj=self.record)
|
||||||
|
if self.model.validate(self.form):
|
||||||
|
# update the record. If it's a record bound to the session
|
||||||
|
# updates will be commited automatically. Otherwise we must
|
||||||
|
# add and commit the record.
|
||||||
|
self.form.populate_obj(self.record)
|
||||||
if not self.record.id:
|
if not self.record.id:
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
db.session.add(self.record)
|
db.session.add(self.record)
|
||||||
logging.debug(f"Added {self.record = }")
|
logging.debug(f"Added {self.record = }")
|
||||||
return form
|
return True
|
||||||
return self.model_form(obj=self.record)
|
return False
|
||||||
|
self.form = self.model_form(obj=self.record)
|
||||||
|
return False
|
||||||
|
|
||||||
def output(self, **kwargs) -> dict:
|
def output(self, **kwargs) -> dict:
|
||||||
return dict(
|
return dict(c=dict(
|
||||||
config=self.config,
|
config=self.config,
|
||||||
request=self.request,
|
request=self.request,
|
||||||
record=self.record or '',
|
form=self.form,
|
||||||
form=self.form() or '',
|
record=self.record,
|
||||||
|
routes=get_all_routes(self.request),
|
||||||
**self.attrs,
|
**self.attrs,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
))
|
||||||
|
|
||||||
def response(self):
|
def response(self):
|
||||||
|
if self.process_form():
|
||||||
|
return HTTPFound(location=f"{self.request.current_route_path}/{self.record.uri}")
|
||||||
return self.output()
|
return self.output()
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
def routes(config):
|
def routes(config):
|
||||||
config.add_route('index', '/')
|
config.add_route('index', '/')
|
||||||
config.add_route('sheet', '/sheet/{uri:.*}', factory='ttfrog.webserver.controllers.CharacterSheet')
|
config.add_route('sheet', '/c{uri:.*}', factory='ttfrog.webserver.controllers.CharacterSheet')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user