обновление для Кубка России

переделаны все парсеры на ссылки из базы
This commit is contained in:
2026-07-02 12:46:29 +03:00
parent b0fe3ad5a4
commit 57907fd86b
29 changed files with 1838 additions and 240 deletions

196
app.py
View File

@@ -33,6 +33,10 @@ from parsers.parser_game import run_parser_game
from parsers.parser_players import run_parser_players
from parsers.parser_schedule import run_parser_schedule
from parsers.parser_standings import run_parser_standings
from parsers.parser_teams import run_parser_teams
from parsers.parser_sources import build_empty_photo_path, list_parser_sources, get_default_source_key, get_parser_source
from services.project_settings_service import build_project_settings_context, save_project_settings
from repositories.project_settings_repository import ensure_project_settings_tables
from db import get_connection
@@ -55,7 +59,7 @@ from repositories.match_session_repository import (
from repositories.match_view_repository import get_match_lineups_grouped
from vmix.vmix_service import build_vmix_project_bytes, build_vmix_filename
from repositories.match_referee_repository import get_match_referees
from repositories.match_repository import get_tour_schedule_by_match_id
from repositories.match_repository import get_tour_schedule_by_match_id, ensure_match_source_key_column
from repositories.standings_repository import get_standings_by_match_id
from repositories.match_coach_repository import get_match_coaches_grouped
from repositories.player_repository import (
@@ -186,7 +190,9 @@ def start_scheduler():
import threading
try:
ensure_project_settings_tables()
ensure_player_photo_enabled_column()
ensure_match_source_key_column()
except Exception:
traceback.print_exc()
@@ -700,10 +706,13 @@ def download_vmix_project(request: Request, session_token: str):
operator_login = current_user.get("username") or None
try:
source_key = session_row[21] if len(session_row) > 21 else None
vmix_bytes = build_vmix_project_bytes(
session_token=session_token,
match_id=session_row[1],
operator_login=operator_login,
source_key=source_key,
)
filename = build_vmix_filename(session_row)
@@ -852,16 +861,6 @@ def close_session(session_token: str):
return RedirectResponse(url="/admin/matches", status_code=303)
@app.get("/admin/db", response_class=HTMLResponse)
def admin_db_index(request: Request):
return templates.TemplateResponse(
name="admin_db_index.html",
request=request,
context={
"current_user": getattr(request.state, "current_user", None),
},
)
@app.get("/admin/db/players", response_class=HTMLResponse)
def admin_db_players(
@@ -1459,6 +1458,12 @@ EMPTY_PLAYER = {
}
def build_empty_player(source_key=None):
item = EMPTY_PLAYER.copy()
item["photo"] = build_empty_photo_path(source_key) or EMPTY_PHOTO_PATH
return item
def get_roster_data(session_token: str, name: str, count_player: int):
session_row = get_match_session_by_token(session_token)
if not session_row:
@@ -1469,14 +1474,21 @@ def get_roster_data(session_token: str, name: str, count_player: int):
home_team_name = session_row[14].replace("«", "").replace("»", "")
away_team_id = session_row[17]
away_team_name = session_row[18].replace("«", "").replace("»", "")
source_key = session_row[21] if len(session_row) > 21 else None
# print(session_row)
data = build_lineup_json(
match_id, home_team_id, away_team_id, name, home_team_name, away_team_name
match_id,
home_team_id,
away_team_id,
name,
home_team_name,
away_team_name,
source_key=source_key,
)
players = data.get("players", [])
players.extend([EMPTY_PLAYER.copy() for _ in range(count_player - len(players))])
players.extend([build_empty_player(source_key) for _ in range(count_player - len(players))])
data["players"] = players[:count_player]
return data
@@ -1687,7 +1699,13 @@ EMPTY_PLAYER_FORMATION = {
}
def build_vmix_formation_response(rows, team_id, team_name):
def build_empty_player_formation(source_key=None):
item = EMPTY_PLAYER_FORMATION.copy()
item["photo"] = build_empty_photo_path(source_key) or EMPTY_PHOTO_PATH
return item
def build_vmix_formation_response(rows, team_id, team_name, source_key=None):
result = []
for p in rows:
@@ -1719,18 +1737,21 @@ def build_vmix_formation_response(rows, team_id, team_name):
"first_name": p[4] if len(p) > 4 else "",
"number": number,
"position": position,
"photo": p[5] if len(p) > 5 else "",
"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 "",
source_key=source_key,
),
fallback_team_id=team_id,
source_key=source_key,
),
}
)
result.extend([EMPTY_PLAYER_FORMATION.copy() for _ in range(11 - len(result))])
result.extend([build_empty_player_formation(source_key) for _ in range(11 - len(result))])
return result
@@ -1743,8 +1764,9 @@ def vmix_home_formations(session_token: str):
home_team_id = session_row[13]
home_team = session_row[14].replace("«", "").replace("»", "")
source_key = session_row[21] if len(session_row) > 21 else None
rows = get_vmix_team_formations(session_row, home_team_id)
return build_vmix_formation_response(rows, home_team_id, home_team)
return build_vmix_formation_response(rows, home_team_id, home_team, source_key=source_key)
@app.get("/vmix/session/{session_token}/away-formations")
@@ -1755,8 +1777,9 @@ def vmix_away_formations(session_token: str):
away_team_id = session_row[17]
away_team = session_row[18].replace("«", "").replace("»", "")
source_key = session_row[21] if len(session_row) > 21 else None
rows = get_vmix_team_formations(session_row, away_team_id)
return build_vmix_formation_response(rows, away_team_id, away_team)
return build_vmix_formation_response(rows, away_team_id, away_team, source_key=source_key)
@app.get("/vmix/session/{session_token}/scoreboard")
@@ -2263,17 +2286,49 @@ def render_admin_db_index(
parser_success: bool | None = None,
update_output: str | None = None,
update_success: bool | None = None,
settings_output: str | None = None,
settings_success: bool | None = None,
):
try:
parser_sources = list_parser_sources()
default_parser_source_key = get_default_source_key()
except Exception:
parser_sources = [
{
"key": "SUPERLEAGUE",
"title": "Суперлига 2026",
"logo_base_path": r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Teams Logos",
"photo_base_path": r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Photo",
},
{
"key": "RUSSIAN_CUP",
"title": "Кубок России 2026",
"logo_base_path": r"D:\Графика\ФУТБОЛ\Кубок России 2026\Teams Logos",
"photo_base_path": r"D:\Графика\ФУТБОЛ\Кубок России 2026\Photo",
},
]
default_parser_source_key = "SUPERLEAGUE"
try:
project_settings = build_project_settings_context()
except Exception:
project_settings = {"storage_title": "База данных", "groups": [], "editable_keys": []}
return templates.TemplateResponse(
name="admin_db_index.html",
request=request,
context={
"current_user": getattr(request.state, "current_user", None),
"parser_sources": parser_sources,
"default_parser_source_key": default_parser_source_key,
"parser_output": parser_output,
"parser_name": parser_name,
"parser_success": parser_success,
"update_output": update_output,
"update_success": update_success,
"settings_output": settings_output,
"settings_success": settings_success,
"project_settings": project_settings,
},
)
@@ -2284,45 +2339,78 @@ def admin_db_index(request: Request):
@app.post("/admin/db/run-parser", response_class=HTMLResponse)
def admin_db_run_parser(request: Request, parser_name: str = Form(...)):
def admin_db_run_parser(
request: Request,
parser_name: str | None = Form(default=None),
parser_names: List[str] = Form(default=[]),
parser_source: str | None = Form(default=None),
):
current_user = getattr(request.state, "current_user", None)
if not current_user or current_user.get("role") != "admin":
return RedirectResponse(url="/login", status_code=303)
parser_map = {
"teams": ("Команды", run_parser_teams),
"players": ("Игроки", run_parser_players),
"schedule": ("Расписание", run_parser_schedule),
"standings": ("Турнирка", run_parser_standings),
}
parser_meta = parser_map.get(parser_name)
if not parser_meta:
selected_parser_names = list(parser_names or [])
if not selected_parser_names and parser_name:
selected_parser_names = [parser_name]
selected_parser_names = [name for name in selected_parser_names if name in parser_map]
if not selected_parser_names:
return render_admin_db_index(
request,
parser_output="Неизвестный парсер.",
parser_name=parser_name,
parser_output="Выберите хотя бы один тип данных для парсинга.",
parser_name="Импорт данных",
parser_success=False,
)
title, parser_func = parser_meta
try:
source = get_parser_source(parser_source)
except Exception as exc:
return render_admin_db_index(
request,
parser_output=str(exc),
parser_name="Импорт данных",
parser_success=False,
)
titles = [parser_map[name][0] for name in selected_parser_names]
title = f"{source['title']}: {', '.join(titles)}"
buffer = io.StringIO()
success = True
with contextlib.redirect_stdout(buffer), contextlib.redirect_stderr(buffer):
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Запуск парсера: {title}"
)
try:
parser_func()
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Парсер завершён успешно"
)
except Exception:
success = False
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Ошибка при выполнении парсера"
)
print(traceback.format_exc())
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Источник: {source['title']}")
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Запуск: {', '.join(titles)}")
for selected_name in selected_parser_names:
item_title, parser_func = parser_map[selected_name]
print()
print(f"===== {item_title} =====")
try:
parser_func(source_key=source["key"])
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {item_title}: успешно"
)
except Exception:
success = False
print(
f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {item_title}: ошибка"
)
print(traceback.format_exc())
if success:
print()
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Импорт завершён успешно")
else:
print()
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Импорт завершён с ошибками")
output = buffer.getvalue().strip() or "Парсер не вернул сообщений."
return render_admin_db_index(
@@ -2334,6 +2422,40 @@ def admin_db_run_parser(request: Request, parser_name: str = Form(...)):
@app.post("/admin/db/project-settings", response_class=HTMLResponse)
async def admin_db_save_project_settings(request: Request):
current_user = getattr(request.state, "current_user", None)
if not current_user or current_user.get("role") != "admin":
return RedirectResponse(url="/login", status_code=303)
try:
form = await request.form()
form_values = {key: str(value) for key, value in form.items()}
updated_keys = save_project_settings(form_values)
output = [
"Настройки проекта сохранены в базе данных.",
f"Сохранено полей: {len(updated_keys)}",
]
if updated_keys:
output.append("Поля:")
output.extend([f"- {key}" for key in updated_keys])
output.append("")
output.append(".env больше не используется для ссылок, сезонов, путей логотипов и фотографий.")
return render_admin_db_index(
request,
settings_output="\n".join(output),
settings_success=True,
)
except Exception:
return render_admin_db_index(
request,
settings_output="Ошибка при сохранении настроек проекта:\n" + traceback.format_exc(),
settings_success=False,
)
@app.post("/admin/db/add-update", response_class=HTMLResponse)
def admin_db_add_update(
request: Request,