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

переделаны все парсеры на ссылки из базы
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

View File

@@ -0,0 +1,361 @@
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",
"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",
"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,
},
]
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 "",
"teams_url": row[8] or "",
"schedule_url": row[9] or "",
"standings_url": row[10] or "",
"match_base_url": row[11] or "",
"base_url": row[12] or "https://wfl.rfs.ru",
"sort_order": row[13] or 0,
"is_active": bool(row[14]),
}
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,
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;")
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,
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, 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["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"]),
)
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(
"""
SELECT key, title, tournament_id, round_id, season, calendar_type,
logo_base_path, photo_base_path, teams_url, schedule_url, standings_url,
match_base_url, base_url, sort_order, is_active
FROM parser_sources
WHERE is_active = TRUE
ORDER BY sort_order ASC, title ASC;
"""
)
else:
cur.execute(
"""
SELECT key, title, tournament_id, round_id, season, calendar_type,
logo_base_path, photo_base_path, teams_url, schedule_url, standings_url,
match_base_url, base_url, sort_order, is_active
FROM parser_sources
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(
"""
SELECT key, title, tournament_id, round_id, season, calendar_type,
logo_base_path, photo_base_path, teams_url, schedule_url, standings_url,
match_base_url, base_url, sort_order, is_active
FROM parser_sources
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": "",
"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, 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, 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,
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("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()