Files
putzliga/app/__init__.py

159 lines
5.2 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
from flask import Flask
from markupsafe import escape
try:
import pyphen
except ModuleNotFoundError: # pragma: no cover - optional dependency in local dev
pyphen = None
from config import Config
from .cli import register_cli, seed_badges
from .extensions import csrf, db, login_manager
from .forms import QuickTaskForm
from .models import QuickWin
from .routes import auth, main, scoreboard, settings, tasks
from .routes.main import load_icon_svg
from .services.app_settings import get_quick_task_config
from .services.badges import sync_existing_badges
from .services.bootstrap import ensure_schema_and_admins
from .services.dates import MONTH_NAMES, local_now
from .services.monthly import archive_months_missing_up_to_previous
DE_HYPHENATOR = pyphen.Pyphen(lang="de_DE") if pyphen else None
def _fallback_soft_hyphenate(word: str) -> str:
return word
def create_app(config_class: type[Config] = Config) -> Flask:
app = Flask(__name__, static_folder="static", template_folder="templates")
app.config.from_object(config_class)
manifest_path = Path(app.root_path).parent / "CloudronManifest.json"
try:
app.config["APP_VERSION"] = json.loads(manifest_path.read_text(encoding="utf-8")).get("version", "0.0.0")
except FileNotFoundError:
app.config["APP_VERSION"] = "0.0.0"
app.config["DATA_DIR"].mkdir(parents=True, exist_ok=True)
app.config["UPLOAD_FOLDER"].mkdir(parents=True, exist_ok=True)
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
with app.app_context():
db.create_all()
ensure_schema_and_admins()
seed_badges()
sync_existing_badges()
register_cli(app)
app.register_blueprint(main.bp)
app.register_blueprint(auth.bp)
app.register_blueprint(tasks.bp)
app.register_blueprint(scoreboard.bp)
app.register_blueprint(settings.bp)
app.jinja_env.globals["icon_svg"] = lambda name: load_icon_svg(name, app.static_folder)
@app.before_request
def ensure_archives():
archive_months_missing_up_to_previous()
@app.context_processor
def inject_globals():
quick_task_form = QuickTaskForm(prefix="quick")
quick_task_config = get_quick_task_config()
quick_task_form.effort.choices = [
(key, values["label"])
for key, values in quick_task_config.items()
]
quick_wins = QuickWin.query.filter_by(active=True).order_by(QuickWin.sort_order.asc(), QuickWin.id.asc()).all()
def asset_version(filename: str) -> int:
path = Path(app.static_folder) / filename
try:
return int(path.stat().st_mtime)
except FileNotFoundError:
return 1
return {
"app_name": app.config["APP_NAME"],
"app_version": app.config["APP_VERSION"],
"nav_items": [
("tasks.my_tasks", "Meine Aufgaben", "house"),
("tasks.all_tasks", "Alle", "list"),
("tasks.archive_view", "Archiv", "check-double"),
("tasks.create", "Neu", "plus"),
("tasks.calendar_view", "Kalender", "calendar"),
("scoreboard.index", "Highscore", "trophy"),
("settings.index", "Optionen", "gear"),
],
"mobile_nav_items": [
("tasks.my_tasks", "Meine Aufgaben", "house"),
("tasks.all_tasks", "Alle Aufgaben", "list"),
("tasks.calendar_view", "Kalender", "calendar"),
("scoreboard.index", "Highscore", "trophy"),
("settings.index", "Optionen", "gear"),
],
"icon_svg": lambda name: load_icon_svg(name, app.static_folder),
"asset_version": asset_version,
"now_local": local_now(),
"quick_task_form": quick_task_form,
"quick_task_config": quick_task_config,
"quick_wins": quick_wins,
}
@app.template_filter("date_de")
def date_de(value):
return value.strftime("%d.%m.%Y") if value else ""
@app.template_filter("datetime_de")
def datetime_de(value):
return value.strftime("%d.%m.%Y, %H:%M") if value else ""
@app.template_filter("month_name")
def month_name(value):
return MONTH_NAMES[value]
@app.template_filter("hyphenate_de")
def hyphenate_de(value):
if not value:
return ""
text = str(value)
parts: list[str] = []
current = []
def flush_word():
if not current:
return
word = "".join(current)
if len(word) >= 6:
if DE_HYPHENATOR:
parts.append(DE_HYPHENATOR.inserted(word, "\u00AD"))
else:
parts.append(_fallback_soft_hyphenate(word))
else:
parts.append(word)
current.clear()
for char in text:
if char.isalpha() or char in "ÄÖÜäöüß":
current.append(char)
else:
flush_word()
parts.append(char)
flush_word()
return escape("".join(parts))
return app