1. добавил в админку сортировку игроков и Фото (если галочка стоит, то путь к фото генерируется автоматически)
2. фото обновляются прям в json
This commit is contained in:
121
app.py
121
app.py
@@ -23,7 +23,7 @@ from pathlib import Path
|
||||
import io
|
||||
import asyncio
|
||||
import time
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import quote, urlencode
|
||||
import traceback
|
||||
import contextlib
|
||||
import os
|
||||
@@ -60,6 +60,8 @@ from repositories.player_repository import (
|
||||
search_players_for_admin,
|
||||
get_player_by_id,
|
||||
update_player_admin,
|
||||
update_player_photo_enabled,
|
||||
ensure_player_photo_enabled_column,
|
||||
)
|
||||
|
||||
from repositories.referee_repository import (
|
||||
@@ -117,6 +119,8 @@ from repositories.match_lineup_repository import (
|
||||
)
|
||||
|
||||
from services.vmix_json_service import (
|
||||
EMPTY_PHOTO_PATH,
|
||||
build_generated_player_photo_path,
|
||||
build_lineup_json,
|
||||
get_vmix_match_info_by_token,
|
||||
get_vmix_standings,
|
||||
@@ -124,6 +128,7 @@ from services.vmix_json_service import (
|
||||
get_vmix_team_formations,
|
||||
get_vmix_scoreboard_info,
|
||||
get_vmix_players_goal,
|
||||
resolve_player_photo_for_json,
|
||||
)
|
||||
|
||||
from scheduler import run_scheduler
|
||||
@@ -139,10 +144,50 @@ templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
||||
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
|
||||
|
||||
|
||||
PLAYER_ADMIN_SORT_COLUMNS = [
|
||||
"id",
|
||||
"full_name",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"external_id",
|
||||
"position",
|
||||
"team_name",
|
||||
"photo",
|
||||
]
|
||||
|
||||
|
||||
def build_player_admin_sort_links(q: str, current_sort: str, current_direction: str) -> dict:
|
||||
links = {}
|
||||
current_sort = current_sort if current_sort in PLAYER_ADMIN_SORT_COLUMNS else "id"
|
||||
current_direction = "asc" if current_direction == "asc" else "desc"
|
||||
|
||||
for column in PLAYER_ADMIN_SORT_COLUMNS:
|
||||
next_direction = (
|
||||
"desc"
|
||||
if current_sort == column and current_direction == "asc"
|
||||
else "asc"
|
||||
)
|
||||
indicator = ""
|
||||
if current_sort == column:
|
||||
indicator = "▲" if current_direction == "asc" else "▼"
|
||||
|
||||
links[column] = {
|
||||
"url": f"/admin/db/players?{urlencode({'q': q or '', 'sort': column, 'direction': next_direction})}",
|
||||
"indicator": indicator,
|
||||
}
|
||||
|
||||
return links
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
def start_scheduler():
|
||||
import threading
|
||||
|
||||
try:
|
||||
ensure_player_photo_enabled_column()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
thread = threading.Thread(
|
||||
target=run_scheduler, kwargs={"with_signal_handlers": False}, daemon=True
|
||||
)
|
||||
@@ -370,6 +415,12 @@ async def admin_auth_middleware(request: Request, call_next):
|
||||
}
|
||||
|
||||
response = await call_next(request)
|
||||
|
||||
if path.startswith("/vmix/session/"):
|
||||
response.headers["Cache-Control"] = "no-store, no-cache, max-age=0, must-revalidate"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Expires"] = "0"
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -803,23 +854,57 @@ def admin_db_index(request: Request):
|
||||
|
||||
|
||||
@app.get("/admin/db/players", response_class=HTMLResponse)
|
||||
def admin_db_players(request: Request, q: str = Query(default="")):
|
||||
def admin_db_players(
|
||||
request: Request,
|
||||
q: str = Query(default=""),
|
||||
sort: str = Query(default="id"),
|
||||
direction: str = Query(default="desc"),
|
||||
):
|
||||
# denied = require_role(request, {"admin"})
|
||||
# if denied:
|
||||
# return denied
|
||||
|
||||
players = search_players_for_admin(q)
|
||||
allowed_sort = set(PLAYER_ADMIN_SORT_COLUMNS)
|
||||
if sort not in allowed_sort:
|
||||
sort = "id"
|
||||
direction = "asc" if direction == "asc" else "desc"
|
||||
|
||||
players = search_players_for_admin(q, sort_by=sort, sort_dir=direction)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
name="admin_db_players.html",
|
||||
request=request,
|
||||
context={
|
||||
"q": q,
|
||||
"sort": sort,
|
||||
"direction": direction,
|
||||
"sort_links": build_player_admin_sort_links(q, sort, direction),
|
||||
"players": players,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.post("/admin/db/players/{player_id}/photo-enabled")
|
||||
def admin_db_player_photo_enabled_submit(
|
||||
player_id: int,
|
||||
q: str = Form(default=""),
|
||||
sort: str = Form(default="id"),
|
||||
direction: str = Form(default="desc"),
|
||||
photo_enabled: bool = Form(default=False),
|
||||
):
|
||||
allowed_sort = set(PLAYER_ADMIN_SORT_COLUMNS)
|
||||
if sort not in allowed_sort:
|
||||
sort = "id"
|
||||
direction = "asc" if direction == "asc" else "desc"
|
||||
|
||||
update_player_photo_enabled(player_id=player_id, photo_enabled=photo_enabled)
|
||||
|
||||
return RedirectResponse(
|
||||
url=f"/admin/db/players?{urlencode({'q': q or '', 'sort': sort, 'direction': direction})}",
|
||||
status_code=303,
|
||||
)
|
||||
|
||||
|
||||
@app.get("/admin/db/players/{player_id}/edit", response_class=HTMLResponse)
|
||||
def admin_db_player_edit(request: Request, player_id: int):
|
||||
player = get_player_by_id(player_id)
|
||||
@@ -846,6 +931,7 @@ def admin_db_player_edit_submit(
|
||||
birth_date: str = Form(default=""),
|
||||
photo: str = Form(default=""),
|
||||
video: str = Form(default=""),
|
||||
photo_enabled: bool = Form(default=False),
|
||||
):
|
||||
update_player_admin(
|
||||
player_id=player_id,
|
||||
@@ -857,6 +943,7 @@ def admin_db_player_edit_submit(
|
||||
birth_date=birth_date,
|
||||
photo=photo,
|
||||
video=video,
|
||||
photo_enabled=photo_enabled,
|
||||
)
|
||||
|
||||
return RedirectResponse(
|
||||
@@ -1358,7 +1445,7 @@ EMPTY_PLAYER = {
|
||||
"pos": " ",
|
||||
"number_lastname_amp_K": " ",
|
||||
"number_fullname": " ",
|
||||
"photo": r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Photo\EMPTY.png",
|
||||
"photo": EMPTY_PHOTO_PATH,
|
||||
}
|
||||
|
||||
|
||||
@@ -1586,7 +1673,7 @@ EMPTY_PLAYER_FORMATION = {
|
||||
"number": " ",
|
||||
"number_lastname_amp_K": " ",
|
||||
"position": " ",
|
||||
"photo": r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Photo\EMPTY.png",
|
||||
"photo": EMPTY_PHOTO_PATH,
|
||||
}
|
||||
|
||||
|
||||
@@ -1613,13 +1700,23 @@ def build_vmix_formation_response(rows, team_id, team_name):
|
||||
"number_lastname_amp_K": f"{number} {lastname}{suffix_str}".strip(),
|
||||
"number": number,
|
||||
"position": position,
|
||||
"photo": (
|
||||
r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Photo"
|
||||
+ "\\"
|
||||
+ team_name
|
||||
+ "\\"
|
||||
+ (p[0] + " " + p[4]).strip()
|
||||
+ ".png"
|
||||
"photo": resolve_player_photo_for_json(
|
||||
{
|
||||
"player_id": p[7] if len(p) > 7 else None,
|
||||
"team_id": p[8] if len(p) > 8 else team_id,
|
||||
"player_name": p[9] if len(p) > 9 else "",
|
||||
"last_name": lastname,
|
||||
"first_name": p[4] if len(p) > 4 else "",
|
||||
"number": number,
|
||||
"position": position,
|
||||
"photo_enabled": p[6] if len(p) > 6 else False,
|
||||
},
|
||||
generated_photo=build_generated_player_photo_path(
|
||||
team_name=team_name,
|
||||
last_name=lastname,
|
||||
first_name=p[4] if len(p) > 4 else "",
|
||||
),
|
||||
fallback_team_id=team_id,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user