unique constraint uses equals not matches
This commit is contained in:
parent
8ce642ba90
commit
120449386a
|
@ -44,6 +44,8 @@ ADMIN_EMAIL=
|
||||||
|
|
||||||
THEME=default
|
THEME=default
|
||||||
|
|
||||||
|
VIEW_URI=/
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -87,18 +89,14 @@ THEME=default
|
||||||
self.db = GrungDB.with_schema(schema, storage=MemoryStorage)
|
self.db = GrungDB.with_schema(schema, storage=MemoryStorage)
|
||||||
else:
|
else:
|
||||||
self.db = GrungDB.with_schema(
|
self.db = GrungDB.with_schema(
|
||||||
schema,
|
schema, self.path.database, sort_keys=True, indent=4, separators=(",", ": ")
|
||||||
self.path.database,
|
|
||||||
sort_keys=True,
|
|
||||||
indent=4,
|
|
||||||
separators=(',', ': ')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.theme = Path(__file__).parent / "themes" / "default"
|
self.theme = Path(__file__).parent / "themes" / "default"
|
||||||
|
|
||||||
self.web = Flask(self.config.NAME, template_folder=self.theme)
|
self.web = Flask(self.config.NAME, template_folder=self.theme)
|
||||||
self.web.config["SECRET_KEY"] = self.config.SECRET_KEY
|
self.web.config["SECRET_KEY"] = self.config.SECRET_KEY
|
||||||
self.web.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
|
self.web.config["SEND_FILE_MAX_AGE_DEFAULT"] = 0
|
||||||
self.web.config["DEBUG"] = True
|
self.web.config["DEBUG"] = True
|
||||||
|
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
|
@ -107,20 +105,32 @@ THEME=default
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
raise ApplicationNotInitializedError("This action requires the application to be initialized.")
|
raise ApplicationNotInitializedError("This action requires the application to be initialized.")
|
||||||
|
|
||||||
|
def add_page(self, parent: schema.Page, child: schema.Page):
|
||||||
|
parent.pages.append(self.db.save(child))
|
||||||
|
parent = self.db.save(parent)
|
||||||
|
return parent.get_child(child)
|
||||||
|
|
||||||
def bootstrap(self):
|
def bootstrap(self):
|
||||||
"""
|
"""
|
||||||
Bootstrap the database entries by populating the first Page, the Admin user and the Admins group.
|
Bootstrap the database entries by populating the first Page, the Admin user and the Admins group.
|
||||||
"""
|
"""
|
||||||
self.check_state()
|
self.check_state()
|
||||||
|
home = schema.Page(stub=self.config.VIEW_URI, title="Home", body="This is the home page")
|
||||||
|
npcs = schema.Page(stub="NPC", title="NPC", body="NPCs!")
|
||||||
|
sabetha = schema.Page(title="Sabetha", body="Sabetha!")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
about = self.db.save(schema.Page(title="About", body="About!"))
|
home = self.db.save(home)
|
||||||
except UniqueConstraintError:
|
except UniqueConstraintError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
home = self.db.save(schema.Page(title="Home", body="This is the home page", pages=[about]))
|
npcs = self.add_page(home, npcs)
|
||||||
about.parent = home
|
except UniqueConstraintError:
|
||||||
self.db.ssave(home)
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
sabetha = self.add_page(npcs, sabetha)
|
||||||
except UniqueConstraintError:
|
except UniqueConstraintError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from grung.types import Collection, Field, Record, Pointer
|
from grung.types import BackReference, Collection, Field, Record
|
||||||
|
|
||||||
|
|
||||||
class User(Record):
|
class User(Record):
|
||||||
|
@ -18,17 +18,35 @@ class Page(Record):
|
||||||
def fields(cls):
|
def fields(cls):
|
||||||
return [
|
return [
|
||||||
*super().fields(),
|
*super().fields(),
|
||||||
Field("stub", unique=True),
|
Field("uri", unique=True),
|
||||||
|
Field("stub"),
|
||||||
Field("title"),
|
Field("title"),
|
||||||
Field("body"),
|
Field("body"),
|
||||||
Pointer("parent", value_type=Page),
|
|
||||||
Collection("pages", Page),
|
Collection("pages", Page),
|
||||||
|
BackReference("parent", value_type=Page),
|
||||||
]
|
]
|
||||||
|
|
||||||
def before_insert(self):
|
def before_insert(self, db):
|
||||||
|
super().before_insert(db)
|
||||||
|
|
||||||
if not self.stub and not self.title:
|
if not self.stub and not self.title:
|
||||||
raise Exception("Must provide either a stub or a title!")
|
raise Exception("Must provide either a stub or a title!")
|
||||||
if not self.stub:
|
if not self.stub:
|
||||||
self.stub = self.title.title().replace(" ", "")
|
self.stub = self.title.title().replace(" ", "")
|
||||||
if not self.title:
|
if not self.title:
|
||||||
self.title = self.stub.title()
|
self.title = self.stub
|
||||||
|
|
||||||
|
self.uri = (self.parent.uri + "/" if self.parent and self.parent.uri != "/" else "") + self.stub
|
||||||
|
|
||||||
|
def after_insert(self, db):
|
||||||
|
super().after_insert(db)
|
||||||
|
for child in self.pages:
|
||||||
|
obj = BackReference.dereference(child, db)
|
||||||
|
obj.uri = f"{self.uri}/{obj.stub}"
|
||||||
|
child = db.save(obj)
|
||||||
|
|
||||||
|
def get_child(self, obj: Record):
|
||||||
|
for page in self.pages:
|
||||||
|
if page.uid == obj.uid:
|
||||||
|
return page
|
||||||
|
return None
|
||||||
|
|
|
@ -12,11 +12,12 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
{% for uri, stub in breadcrumbs %}
|
||||||
<li><a href="{{ url_for('index') }}">Home</a></li>
|
<span> / <a href="{{ app.config.VIEW_URI }}{{ uri }}">{{ stub }}</a></span>
|
||||||
</ul>
|
{% endfor %}
|
||||||
</nav>
|
</nav>
|
||||||
<nav>
|
<nav>
|
||||||
|
Menu:
|
||||||
<ul>
|
<ul>
|
||||||
{% block menu %}{% endblock %}
|
{% block menu %}{% endblock %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -2,16 +2,25 @@
|
||||||
|
|
||||||
{% block menu %}
|
{% block menu %}
|
||||||
{% for child in page.pages %}
|
{% for child in page.pages %}
|
||||||
<li><a href="/{{page.stub}}/{{ child.stub }}">{{ child.title }}</a></li>
|
<li><a href="{{ app.config.VIEW_URI }}{{ child.uri }}">{{ child.title }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{ page.title }}</h1>
|
<form method='POST'>
|
||||||
{{ page.body }}
|
<input type="hidden" name="uid" value="{{ page.uid }}">
|
||||||
|
<h1>{{ page.doc_id }}: <input name='title' type='text' value="{{ page.title }}"></h1>
|
||||||
|
<h3>{{ app.config.VIEW_URI }}{{ page.parent.uri if page.parent else "/" }} <input name='stub' type='text' value='{{ page.stub }}'></h3>
|
||||||
|
<textarea name='body'>{{ page.body }}</textarea>
|
||||||
|
<input type=submit>
|
||||||
|
</form>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
{{ page }}
|
{{ page }}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
{{ app.web.config }}
|
||||||
|
</pre>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,46 +1,99 @@
|
||||||
from flask import Response, render_template
|
from flask import Response, render_template, request
|
||||||
from tinydb import where
|
from tinydb import where
|
||||||
|
|
||||||
from ttfrog import app
|
from ttfrog import app, schema
|
||||||
|
from ttfrog.forms import PageForm
|
||||||
|
|
||||||
|
STATIC = ["static"]
|
||||||
|
|
||||||
|
|
||||||
def get_page(stub):
|
def relative_uri(path: str = ""):
|
||||||
|
"""
|
||||||
|
The request's URI relative to the VIEW_URI without the leading '/'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (path or request.path).replace(app.config.VIEW_URI, "", 1).strip("/") or "/"
|
||||||
|
|
||||||
|
|
||||||
|
def get_parent(uri: str):
|
||||||
|
try:
|
||||||
|
parent_uri = uri.strip("/").split("/", -1)[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
app.web.logger.debug(f"Looking for parent with {parent_uri = }")
|
||||||
|
return get_page(parent_uri or "/", create_okay=False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_page(path: str = "", create_okay: bool = False):
|
||||||
"""
|
"""
|
||||||
Get one page, including its subpages, but not recursively.
|
Get one page, including its subpages, but not recursively.
|
||||||
"""
|
"""
|
||||||
app.web.logger.debug(f"Looking for page with {stub = }")
|
uri = path or relative_uri(request.path)
|
||||||
matches = app.db.Page.search(where("stub") == stub, recurse=False)
|
matches = app.db.Page.search(where("uri") == uri, recurse=False)
|
||||||
|
app.web.logger.debug(f"Found {len(matches)} pages where {uri = }")
|
||||||
if not matches:
|
if not matches:
|
||||||
return
|
if not create_okay:
|
||||||
|
return None
|
||||||
|
return schema.Page(stub=uri.split("/")[-1], body="This page does not exist", parent=get_parent(uri))
|
||||||
uids = [pointer.split("::")[-1] for pointer in matches[0].pages]
|
uids = [pointer.split("::")[-1] for pointer in matches[0].pages]
|
||||||
subpages = app.db.Page.search(where("uid").one_of(uids), recurse=False)
|
subpages = app.db.Page.search(where("uid").one_of(uids), recurse=False)
|
||||||
matches[0].pages = subpages
|
matches[0].pages = subpages
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
|
def rendered(page: schema.Record, template: str = "page.html"):
|
||||||
|
if not page:
|
||||||
|
return Response("Page not found", status=404)
|
||||||
|
return render_template(template, page=page, app=app, breadcrumbs=breadcrumbs())
|
||||||
|
|
||||||
|
|
||||||
|
def get_static(path):
|
||||||
|
return Response("OK", status=200)
|
||||||
|
|
||||||
|
|
||||||
|
def breadcrumbs():
|
||||||
|
"""
|
||||||
|
Return (uri, stub) pairs for the parents leading from the VIEW_URI to the current request.
|
||||||
|
"""
|
||||||
|
if app.config.VIEW_URI != "/":
|
||||||
|
root = get_page()
|
||||||
|
yield (app.config.VIEW_URI, root.stub)
|
||||||
|
uri = ""
|
||||||
|
for stub in relative_uri().split("/"):
|
||||||
|
uri = "/".join([uri, stub]).lstrip("/")
|
||||||
|
yield (uri, stub)
|
||||||
|
|
||||||
|
|
||||||
@app.web.route("/")
|
@app.web.route("/")
|
||||||
def index():
|
def index():
|
||||||
page = get_page("Home")
|
return rendered(get_page(create_okay=False))
|
||||||
if not page:
|
|
||||||
return Response("Home page not found", status=404)
|
|
||||||
return render_template("page.html", page=page)
|
|
||||||
|
|
||||||
|
|
||||||
@app.web.route("/view/<path:path>")
|
@app.web.route(f"{app.config.VIEW_URI}/<path:path>", methods=["GET"])
|
||||||
def page_view(path):
|
def view(path):
|
||||||
path = path.rstrip("/")
|
return rendered(get_page(request.path, create_okay=True))
|
||||||
stub = path.split("/")[-1]
|
|
||||||
page = get_page(stub)
|
|
||||||
app.web.logger.debug(f"page_view: {path =} {stub =} {page =}")
|
@app.web.route(f"{app.config.VIEW_URI}/<path:path>", methods=["POST"])
|
||||||
if not page:
|
def edit(path):
|
||||||
return Response(f"{stub} ({path}) not found", status=404)
|
uri = relative_uri()
|
||||||
return render_template(
|
parent = get_parent(uri)
|
||||||
"page.html",
|
app.web.logger.debug(f"Handling form submission: {uri = }, {parent = }, {request.form = }")
|
||||||
page=page,
|
if not parent:
|
||||||
meta={
|
return Response(f"Parent for {uri} does not exist.", status=403)
|
||||||
'uri': path,
|
|
||||||
}
|
page = get_page(uri, create_okay=True)
|
||||||
)
|
app.web.logger.debug(f"Editing {page.doc_id} for {uri = }")
|
||||||
|
if page.doc_id:
|
||||||
|
if page.uid != request.form["uid"]:
|
||||||
|
return Response("Invalid UID.", status=403)
|
||||||
|
|
||||||
|
form = PageForm(page, request.form)
|
||||||
|
page = app.db.save(form.prepare())
|
||||||
|
app.web.logger.debug(f"Saved {page.doc_id}; now updating parent {parent.doc_id}")
|
||||||
|
parent.pages.append(page)
|
||||||
|
app.db.save(parent)
|
||||||
|
return get_page(stub)
|
||||||
|
|
||||||
|
|
||||||
@app.web.after_request
|
@app.web.after_request
|
||||||
|
|
Loading…
Reference in New Issue
Block a user