Files
WFL/repositories/project_settings_repository.py

350 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from typing import Any
from db import get_connection
DEFAULT_APP_SETTINGS = {
"default_parser_source_key": "SUPERLEAGUE",
"rfs_base_url": "https://wfl.rfs.ru",
}
DEFAULT_PARSER_SOURCES = [
{
"key": "SUPERLEAGUE",
"title": "Суперлига 2026",
"tournament_id": "1061879",
"round_id": "1117550",
"season": "2025/2026",
"calendar_type": "tours",
"logo_base_path": r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Teams Logos",
"photo_base_path": r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Photo",
"channel_logo_base_path": r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Лого каналов",
"teams_url": "https://wfl.rfs.ru/tournament/1061879/teams",
"schedule_url": "https://wfl.rfs.ru/tournament/1061879/calendar?round_id=1117550&type=tours",
"standings_url": "https://wfl.rfs.ru/tournament/1061879/tables",
"match_base_url": "https://wfl.rfs.ru/match/",
"base_url": "https://wfl.rfs.ru",
"sort_order": 10,
"is_active": True,
},
{
"key": "RUSSIAN_CUP",
"title": "Кубок России 2026",
"tournament_id": "1064908",
"round_id": "1125159",
"season": "2026",
"calendar_type": "stages",
"logo_base_path": r"D:\Графика\ФУТБОЛ\Кубок России 2026\Teams Logos",
"photo_base_path": r"D:\Графика\ФУТБОЛ\Кубок России 2026\Photo",
"channel_logo_base_path": r"D:\Графика\ФУТБОЛ\ЖФЛ Кубок России 2026\Лого каналов",
"teams_url": "https://wfl.rfs.ru/tournament/1064908/teams",
"schedule_url": "https://wfl.rfs.ru/tournament/1064908/calendar?round_id=1125159&type=stages",
"standings_url": "https://wfl.rfs.ru/tournament/1064908/tables",
"match_base_url": "https://wfl.rfs.ru/match/",
"base_url": "https://wfl.rfs.ru",
"sort_order": 20,
"is_active": True,
},
]
SOURCE_SELECT_SQL = """
SELECT key, title, tournament_id, round_id, season, calendar_type,
logo_base_path, photo_base_path, channel_logo_base_path,
teams_url, schedule_url, standings_url,
match_base_url, base_url, sort_order, is_active
FROM parser_sources
"""
def _row_to_source(row: tuple) -> dict[str, Any]:
return {
"key": row[0] or "",
"title": row[1] or "",
"tournament_id": row[2] or "",
"round_id": row[3] or "",
"season": row[4] or "",
"calendar_type": row[5] or "tours",
"logo_base_path": row[6] or "",
"photo_base_path": row[7] or "",
"channel_logo_base_path": row[8] or "",
"teams_url": row[9] or "",
"schedule_url": row[10] or "",
"standings_url": row[11] or "",
"match_base_url": row[12] or "",
"base_url": row[13] or "https://wfl.rfs.ru",
"sort_order": row[14] or 0,
"is_active": bool(row[15]),
}
def ensure_project_settings_tables() -> None:
"""Создаёт таблицы настроек проекта и добавляет стандартные источники, если их ещё нет."""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
CREATE TABLE IF NOT EXISTS app_settings (
key VARCHAR(100) PRIMARY KEY,
value TEXT NOT NULL DEFAULT '',
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
"""
)
cur.execute(
"""
CREATE TABLE IF NOT EXISTS parser_sources (
key VARCHAR(50) PRIMARY KEY,
title VARCHAR(255) NOT NULL,
tournament_id VARCHAR(100),
round_id VARCHAR(100),
season VARCHAR(50),
calendar_type VARCHAR(50) NOT NULL DEFAULT 'tours',
logo_base_path TEXT,
photo_base_path TEXT,
channel_logo_base_path TEXT,
teams_url TEXT,
schedule_url TEXT,
standings_url TEXT,
match_base_url TEXT,
base_url TEXT NOT NULL DEFAULT 'https://wfl.rfs.ru',
sort_order INTEGER NOT NULL DEFAULT 100,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
"""
)
cur.execute("ALTER TABLE parser_sources ADD COLUMN IF NOT EXISTS photo_base_path TEXT;")
cur.execute("ALTER TABLE parser_sources ADD COLUMN IF NOT EXISTS channel_logo_base_path TEXT;")
for key, value in DEFAULT_APP_SETTINGS.items():
cur.execute(
"""
INSERT INTO app_settings (key, value, updated_at)
VALUES (%s, %s, NOW())
ON CONFLICT (key) DO NOTHING;
""",
(key, value),
)
for source in DEFAULT_PARSER_SOURCES:
cur.execute(
"""
INSERT INTO parser_sources (
key, title, tournament_id, round_id, season, calendar_type,
logo_base_path, photo_base_path, channel_logo_base_path,
teams_url, schedule_url, standings_url, match_base_url, base_url,
sort_order, is_active, created_at, updated_at
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
ON CONFLICT (key) DO NOTHING;
""",
(
source["key"],
source["title"],
source["tournament_id"],
source["round_id"],
source["season"],
source["calendar_type"],
source["logo_base_path"],
source["photo_base_path"],
source["channel_logo_base_path"],
source["teams_url"],
source["schedule_url"],
source["standings_url"],
source["match_base_url"],
source["base_url"],
source["sort_order"],
source["is_active"],
),
)
for source in DEFAULT_PARSER_SOURCES:
cur.execute(
"""
UPDATE parser_sources
SET photo_base_path = %s, updated_at = NOW()
WHERE key = %s
AND (photo_base_path IS NULL OR TRIM(photo_base_path) = '');
""",
(source.get("photo_base_path") or "", source["key"]),
)
cur.execute(
"""
UPDATE parser_sources
SET channel_logo_base_path = %s, updated_at = NOW()
WHERE key = %s
AND (channel_logo_base_path IS NULL OR TRIM(channel_logo_base_path) = '');
""",
(source.get("channel_logo_base_path") or "", source["key"]),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def get_app_settings() -> dict[str, str]:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute("SELECT key, value FROM app_settings;")
rows = cur.fetchall()
settings = dict(DEFAULT_APP_SETTINGS)
settings.update({row[0]: row[1] or "" for row in rows})
return settings
finally:
conn.close()
def get_app_setting(key: str, default: str = "") -> str:
return get_app_settings().get(key, default)
def update_app_setting(key: str, value: str) -> None:
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO app_settings (key, value, updated_at)
VALUES (%s, %s, NOW())
ON CONFLICT (key)
DO UPDATE SET value = EXCLUDED.value, updated_at = NOW();
""",
(key, value or ""),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
def list_parser_sources_from_db(active_only: bool = True) -> list[dict[str, Any]]:
conn = get_connection()
try:
with conn.cursor() as cur:
if active_only:
cur.execute(SOURCE_SELECT_SQL + " WHERE is_active = TRUE ORDER BY sort_order ASC, title ASC;")
else:
cur.execute(SOURCE_SELECT_SQL + " ORDER BY sort_order ASC, title ASC;")
rows = cur.fetchall()
return [_row_to_source(row) for row in rows]
finally:
conn.close()
def get_parser_source_from_db(source_key: str) -> dict[str, Any] | None:
source_key = (source_key or "").upper().strip()
if not source_key:
return None
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(SOURCE_SELECT_SQL + " WHERE key = %s LIMIT 1;", (source_key,))
row = cur.fetchone()
return _row_to_source(row) if row else None
finally:
conn.close()
def update_parser_source(source_key: str, values: dict[str, str]) -> None:
source_key = (source_key or "").upper().strip()
if not source_key:
raise ValueError("Не указан ключ источника")
current = get_parser_source_from_db(source_key)
if not current:
current = next((item for item in DEFAULT_PARSER_SOURCES if item["key"] == source_key), None)
if not current:
current = {
"key": source_key,
"title": source_key,
"tournament_id": "",
"round_id": "",
"season": "",
"calendar_type": "tours",
"logo_base_path": "",
"photo_base_path": "",
"channel_logo_base_path": "",
"teams_url": "",
"schedule_url": "",
"standings_url": "",
"match_base_url": "",
"base_url": DEFAULT_APP_SETTINGS["rfs_base_url"],
"sort_order": 100,
"is_active": True,
}
merged = {**current, **{key: value for key, value in values.items() if value is not None}}
merged["key"] = source_key
merged["calendar_type"] = merged.get("calendar_type") or "tours"
merged["base_url"] = merged.get("base_url") or DEFAULT_APP_SETTINGS["rfs_base_url"]
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"""
INSERT INTO parser_sources (
key, title, tournament_id, round_id, season, calendar_type,
logo_base_path, photo_base_path, channel_logo_base_path,
teams_url, schedule_url, standings_url,
match_base_url, base_url, sort_order, is_active, created_at, updated_at
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
ON CONFLICT (key)
DO UPDATE SET
title = EXCLUDED.title,
tournament_id = EXCLUDED.tournament_id,
round_id = EXCLUDED.round_id,
season = EXCLUDED.season,
calendar_type = EXCLUDED.calendar_type,
logo_base_path = EXCLUDED.logo_base_path,
photo_base_path = EXCLUDED.photo_base_path,
channel_logo_base_path = EXCLUDED.channel_logo_base_path,
teams_url = EXCLUDED.teams_url,
schedule_url = EXCLUDED.schedule_url,
standings_url = EXCLUDED.standings_url,
match_base_url = EXCLUDED.match_base_url,
base_url = EXCLUDED.base_url,
sort_order = EXCLUDED.sort_order,
is_active = EXCLUDED.is_active,
updated_at = NOW();
""",
(
merged.get("key", source_key),
merged.get("title") or source_key,
merged.get("tournament_id") or "",
merged.get("round_id") or "",
merged.get("season") or "",
merged.get("calendar_type") or "tours",
merged.get("logo_base_path") or "",
merged.get("photo_base_path") or "",
merged.get("channel_logo_base_path") or "",
merged.get("teams_url") or "",
merged.get("schedule_url") or "",
merged.get("standings_url") or "",
merged.get("match_base_url") or "",
merged.get("base_url") or DEFAULT_APP_SETTINGS["rfs_base_url"],
int(merged.get("sort_order") or 100),
bool(merged.get("is_active", True)),
),
)
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()