Coverage for src/lektor_ng/admin/webui.py: 97%
35 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-03 19:18 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-03 19:18 +0000
1from __future__ import annotations
3from typing import TYPE_CHECKING
4from wsgiref.util import shift_path_info
6from flask import Flask, request
8from lektor_ng.admin.context import LektorApp, LektorInfo
9from lektor_ng.admin.modules import api, dash, livereload, serve
10from lektor_ng.environment import Environment
12if TYPE_CHECKING:
13 from _typeshed import StrPath
14 from _typeshed.wsgi import WSGIApplication
17def _common_configuration(app: Flask, debug: bool = False) -> None:
18 app.debug = debug
19 app.config["PROPAGATE_EXCEPTIONS"] = True
22def make_app(
23 env: Environment,
24 debug: bool = False,
25 output_path: StrPath | None = None,
26 ui_lang: str = "en",
27 verbosity: int = 0,
28 extra_flags: dict[str, str] | None = None,
29 *,
30 admin_path: str = "/admin",
31 static_folder: StrPath | None = "static", # testing
32) -> LektorApp:
33 if output_path is None:
34 output_path = env.project.get_output_path()
36 lektor_info = LektorInfo(env, output_path, verbosity, extra_flags)
38 # The top-level app has a route that matches anything
39 # ("/<path:path>" in the serve blueprint). That means that if
40 # there is another route whose doesn't match based on request
41 # method, the serve view will take over and try to serve it. To
42 # prevent this from happening for the paths under /admin, we
43 # structure them as a separate flask app.
44 admin_app = LektorApp(lektor_info)
45 _common_configuration(admin_app, debug=debug)
46 admin_app.config["lektor.ui_lang"] = ui_lang
47 admin_app.register_blueprint(dash.bp, url_prefix="/")
48 admin_app.register_blueprint(api.bp, url_prefix="/api")
50 # Serve static files from top-level app
51 app = LektorApp(lektor_info, static_url_path=f"{admin_path}/static", static_folder=static_folder)
52 _common_configuration(app, debug=debug)
53 app.register_blueprint(livereload.bp, url_prefix="/__reload__")
54 app.register_blueprint(serve.bp)
56 # Pass requests for /admin/... to the admin app
57 @app.route(f"{admin_path}/", defaults={"page": ""})
58 @app.route(f"{admin_path}/<path:page>", methods=["GET", "POST", "PUT"])
59 def admin_view(page: str) -> WSGIApplication:
60 environ = request.environ
61 # Save top-level SCRIPT_NAME (used by dash)
62 environ["lektor.site_root"] = request.root_path
63 while environ.get("PATH_INFO", "") != f"/{page}":
64 assert environ["PATH_INFO"]
65 shift_path_info(request.environ)
66 return admin_app.wsgi_app
68 # Add rule to construct URL to /admin/edit
69 app.add_url_rule(f"{admin_path}/edit", "url.edit", build_only=True)
71 return app
74WebAdmin = WebUI = make_app