Refaktor 4/4: admin kao Blueprint-ovi + auto-meni
Admin stranica app-a živi u folderu app-a: - idriz/apps/superheroji/admin.py (Flask Blueprint BP + NAV), template preseljen u idriz/apps/superheroji/templates/superheroji.html. - admin/app.py: registruje sve iz ADMIN_APPS, lijevi meni se puni iz njihovih NAV (context processor), globalni before_request štiti i Blueprint rute. - Uklonjena inline /superheroji ruta i hardkodiran link u base.html. Dodavanje admin stranice novom app-u = admin.py (BP+NAV) u folderu app-a + upis u ADMIN_APPS. Time je mini-app potpuno samostalan: prompt, tools, config i admin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzKpYaEsuWDevuNnvSjcFW
This commit is contained in:
58
admin/app.py
58
admin/app.py
@@ -17,9 +17,12 @@ from flask import (
|
|||||||
Flask, render_template, request, redirect, url_for, session, flash
|
Flask, render_template, request, redirect, url_for, session, flash
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pristup skladištu igre (bez openai zavisnosti) iz glavnog idriz paketa.
|
# Admin stranice mini-app-ova (lagani moduli iz glavnog paketa, bez openai zavisnosti).
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
from idriz.apps.superheroji import store as gamestore # noqa: E402
|
from idriz.apps.superheroji import admin as _superheroji_admin # noqa: E402
|
||||||
|
|
||||||
|
# Svaki app-admin modul ima BP (Blueprint) i NAV (stavka lijevog menija).
|
||||||
|
ADMIN_APPS = [_superheroji_admin]
|
||||||
|
|
||||||
# --- Konfiguracija -----------------------------------------------------------
|
# --- Konfiguracija -----------------------------------------------------------
|
||||||
|
|
||||||
@@ -52,6 +55,24 @@ app.config.update(
|
|||||||
PERMANENT_SESSION_LIFETIME=8 * 3600,
|
PERMANENT_SESSION_LIFETIME=8 * 3600,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Registruj admin stranice mini-app-ova i napuni lijevi meni iz njih.
|
||||||
|
for _m in ADMIN_APPS:
|
||||||
|
app.register_blueprint(_m.BP)
|
||||||
|
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def _inject_nav():
|
||||||
|
return {"nav_apps": [_m.NAV for _m in ADMIN_APPS]}
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def _require_login():
|
||||||
|
"""Sve rute (uključujući app Blueprint-ove) traže prijavu, osim logina i statike."""
|
||||||
|
if request.endpoint in (None, "login", "static"):
|
||||||
|
return
|
||||||
|
if not session.get("user"):
|
||||||
|
return redirect(url_for("login", next=request.path))
|
||||||
|
|
||||||
|
|
||||||
def login_required(f):
|
def login_required(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
@@ -201,39 +222,6 @@ def status():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/superheroji", methods=["GET", "POST"])
|
|
||||||
@login_required
|
|
||||||
def superheroji():
|
|
||||||
if request.method == "POST":
|
|
||||||
action = request.form.get("action")
|
|
||||||
if action == "rounds":
|
|
||||||
try:
|
|
||||||
n = int(request.form.get("rounds", ""))
|
|
||||||
saved = gamestore.save_rounds(n)
|
|
||||||
flash(f"Broj rundi postavljen na {saved}.")
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
flash("Neispravan broj rundi.")
|
|
||||||
elif action == "reset":
|
|
||||||
store = gamestore.HeroStore()
|
|
||||||
store.heroes = {}
|
|
||||||
store.aliases = {}
|
|
||||||
store.matchups = {}
|
|
||||||
store.save()
|
|
||||||
flash("Pamćenje jačina i dvoboja je obrisano.")
|
|
||||||
return redirect(url_for("superheroji"))
|
|
||||||
|
|
||||||
store = gamestore.HeroStore()
|
|
||||||
return render_template(
|
|
||||||
"superheroji.html",
|
|
||||||
rounds=gamestore.load_rounds(),
|
|
||||||
min_rounds=gamestore.MIN_ROUNDS,
|
|
||||||
max_rounds=gamestore.MAX_ROUNDS,
|
|
||||||
heroes=store.leaderboard(),
|
|
||||||
matchups=store.matchup_list(),
|
|
||||||
active_page="superheroji",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
print(f"Idriz admin na http://{LISTEN_HOST}:{LISTEN_PORT}")
|
print(f"Idriz admin na http://{LISTEN_HOST}:{LISTEN_PORT}")
|
||||||
|
|||||||
@@ -98,9 +98,11 @@
|
|||||||
<a href="{{ url_for('status') }}" class="{{ 'active' if active_page == 'status' else '' }}">
|
<a href="{{ url_for('status') }}" class="{{ 'active' if active_page == 'status' else '' }}">
|
||||||
<span class="icon">●</span> Status servisa
|
<span class="icon">●</span> Status servisa
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('superheroji') }}" class="{{ 'active' if active_page == 'superheroji' else '' }}">
|
{% for it in nav_apps %}
|
||||||
<span class="icon">★</span> Superheroji
|
<a href="{{ url_for(it.endpoint) }}" class="{{ 'active' if active_page == it.page else '' }}">
|
||||||
|
<span class="icon">{{ it.icon }}</span> {{ it.title }}
|
||||||
</a>
|
</a>
|
||||||
|
{% endfor %}
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sidebar-foot">
|
<div class="sidebar-foot">
|
||||||
{% if session.user %}<a href="{{ url_for('logout') }}">↩ Odjava</a>{% endif %}
|
{% if session.user %}<a href="{{ url_for('logout') }}">↩ Odjava</a>{% endif %}
|
||||||
|
|||||||
42
idriz/apps/superheroji/admin.py
Normal file
42
idriz/apps/superheroji/admin.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""Admin stranica za Superheroje: Flask Blueprint (bez openai zavisnosti).
|
||||||
|
|
||||||
|
Uvozi ga admin proces. BP = Blueprint, NAV = stavka za lijevi meni. Tako admin
|
||||||
|
jezgro ne mora znati ništa o pojedinom app-u — samo registruje ono što nađe.
|
||||||
|
"""
|
||||||
|
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||||
|
|
||||||
|
from . import store as gamestore
|
||||||
|
|
||||||
|
BP = Blueprint("superheroji", __name__, template_folder="templates", url_prefix="/superheroji")
|
||||||
|
NAV = {"endpoint": "superheroji.index", "page": "superheroji", "title": "Superheroji", "icon": "★"}
|
||||||
|
|
||||||
|
|
||||||
|
@BP.route("/", methods=["GET", "POST"])
|
||||||
|
def index():
|
||||||
|
if request.method == "POST":
|
||||||
|
action = request.form.get("action")
|
||||||
|
if action == "rounds":
|
||||||
|
try:
|
||||||
|
saved = gamestore.save_rounds(int(request.form.get("rounds", "")))
|
||||||
|
flash(f"Broj rundi postavljen na {saved}.")
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
flash("Neispravan broj rundi.")
|
||||||
|
elif action == "reset":
|
||||||
|
store = gamestore.HeroStore()
|
||||||
|
store.heroes = {}
|
||||||
|
store.aliases = {}
|
||||||
|
store.matchups = {}
|
||||||
|
store.save()
|
||||||
|
flash("Pamćenje jačina i dvoboja je obrisano.")
|
||||||
|
return redirect(url_for("superheroji.index"))
|
||||||
|
|
||||||
|
store = gamestore.HeroStore()
|
||||||
|
return render_template(
|
||||||
|
"superheroji.html",
|
||||||
|
rounds=gamestore.load_rounds(),
|
||||||
|
min_rounds=gamestore.MIN_ROUNDS,
|
||||||
|
max_rounds=gamestore.MAX_ROUNDS,
|
||||||
|
heroes=store.leaderboard(),
|
||||||
|
matchups=store.matchup_list(),
|
||||||
|
active_page="superheroji",
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user