from flask import Response, g, redirect, render_template, request, session, url_for from tinydb import where from ttfrog import app, forms, schema 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(table: str, uri: str): try: parent_uri = uri.strip("/").rsplit("/", 1)[0] except IndexError: return None return get_page(parent_uri, table=table if "/" in parent_uri else "Page", create_okay=False) def get_page(path: str, table: str = "Page", create_okay: bool = False): """ Get one page, including its members, but not recursively. """ uri = relative_uri(path) if table not in app.db.tables(): app.web.logger.debug(f"Table {table} does not exist in {app.db.tables()}.") return None page = app.db.table(table).get(where("uri") == uri, recurse=False) if not page: app.web.logger.debug("Page does not exist.") if not create_okay: app.web.logger.debug("Page does not exist and creating is not okay.") return None parent = get_parent(table, uri) if not app.authorize(g.user, parent, schema.Permissions.WRITE): app.web.logger.debug(f"User {g.user} is not authorized to write {parent}") return None return getattr(schema, table)(name=uri.split("/")[-1], body="This page does not exist", parent=parent) if not app.authorize(g.user, page, schema.Permissions.READ): return None if hasattr(page, "members"): subpages = [] for pointer in page.members: table, pkey, pval = pointer.split("::") subpages += app.db.table(table).search(where(pkey) == pval, recurse=False) page.members = subpages return page 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(), root=g.root, user=g.user, g=g ) def breadcrumbs(): """ Return (uri, name) pairs for the parents leading from the VIEW_URI to the current request. """ uri = "" for name in relative_uri().split("/"): uri = "/".join([uri, name]) yield (uri, name) @app.web.route(app.config.VIEW_URI) def index(): return rendered(get_page(app.config.VIEW_URI, create_okay=False)) @app.web.route("/login", methods=["GET", "POST"]) def login(): app.web.session_interface.regenerate(session) if request.method == "POST": username = request.form.get("username") password = request.form.get("password") user = app.authenticate(username, password) if user: g.user = user session["user_id"] = g.user.doc_id session["user"] = dict(g.user.serialize()) return redirect(url_for("index")) g.messages.append(f"Invalid login for {username}") return rendered(schema.Page(name="Login", title="Please enter your login details"), "login.html") @app.web.route("/logout") def logout(): if "user_id" in session: del session["user_id"] del g.user return redirect(url_for("index")) @app.web.route(f"{app.config.VIEW_URI}//", methods=["GET"]) @app.web.route(f"{app.config.VIEW_URI}/", methods=["GET"], defaults={"table": "Page"}) def view(table, path): parent = get_parent(table, relative_uri()) if table not in app.db.tables(): table = parent.__class__.__name__ if parent else "Page" page = get_page(request.path, table=table, create_okay=(parent and parent.doc_id is not None)) return rendered(page) @app.web.route(f"{app.config.VIEW_URI}//", methods=["POST"]) @app.web.route(f"{app.config.VIEW_URI}/", methods=["POST"], defaults={"table": "Page"}) def edit(table, path): uri = relative_uri() parent = get_parent(table, uri) if not parent: return Response("You cannot create a page at this location.", status=403) # get or create the docoument at this uri page = get_page(uri, table=table, create_okay=True) if not app.authorize(g.user, page, schema.Permissions.WRITE): return Response("Permission denied.", status=403) save_data = getattr(forms, table)(page, request.form).prepare() # editing existing document if page.doc_id: if page.uid != request.form["uid"]: return Response("Invalid UID.", status=403) return rendered(app.db.save(save_data)) # saving a new document return rendered(parent.add_member(save_data)) @app.web.before_request def before_request(): g.messages = [] if not request.path.startswith('/static'): user_id = session.get("user_id", 1) g.user = app.db.User.get(doc_id=user_id) session["user_id"] = user_id session["user"] = dict(g.user.serialize()) g.root = get_page(app.config.VIEW_URI) @app.web.after_request def add_header(r): r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, public, max-age=0" r.headers["Pragma"] = "no-cache" r.headers["Expires"] = "0" return r