05/23 Phase 18,19

This commit is contained in:
2026-05-23 16:31:55 -04:00
parent 7506e94d61
commit 2553406254
9 changed files with 674 additions and 5 deletions
+65 -3
View File
@@ -7,15 +7,16 @@ on_fetch: callable injected by main.py to trigger the shared fetch thread.
"""
import tkinter as tk
from tkinter import ttk, messagebox
from tkinter import ttk, messagebox, filedialog
import logging
from db.database import get_db_stats
from db.database import get_db_stats, backup_db, restore_db
from db.models import (
get_all_games, get_draw_count, set_game_active,
get_last_fetch_per_source, get_predictions,
add_game, delete_game, _BUILTIN_GAMES,
)
from core.importer import import_draws_csv
logger = logging.getLogger(__name__)
@@ -80,6 +81,7 @@ class SettingsScreen(ttk.Frame):
self._sources_body = self._section("Data Sources")
self._fetch_body = self._build_fetch_section()
self._db_body = self._section("Database")
self._build_db_actions()
def _build_fetch_section(self):
body = self._section("Fetch Schedule")
@@ -132,11 +134,16 @@ class SettingsScreen(ttk.Frame):
foreground="#777777",
).pack(side="left", padx=(16, 0))
ttk.Button(
row, text="Import CSV",
command=lambda gid=game["id"], gname=game["name"]: self._import_csv(gid, gname),
).pack(side="left", padx=(12, 0))
if game["name"] not in _BUILTIN_GAMES:
ttk.Button(
row, text="Delete",
command=lambda gid=game["id"], gname=game["name"]: self._delete_game(gid, gname),
).pack(side="left", padx=(12, 0))
).pack(side="left", padx=(6, 0))
ttk.Button(
self._games_body, text="+ Add Custom Game",
@@ -187,6 +194,13 @@ class SettingsScreen(ttk.Frame):
ttk.Label(row, text="Predictions:", width=18, anchor="w").pack(side="left")
ttk.Label(row, text=str(pred_count), foreground="#555555").pack(side="left")
def _build_db_actions(self):
body = self._section("Database Actions")
row = ttk.Frame(body)
row.pack(fill="x")
ttk.Button(row, text="Backup DB", command=self._backup_db ).pack(side="left", padx=(0, 8))
ttk.Button(row, text="Restore DB", command=self._restore_db).pack(side="left")
# ── Actions ───────────────────────────────────────────────────────────────
def _toggle_game(self, game_id: int, var: tk.BooleanVar):
@@ -203,6 +217,54 @@ class SettingsScreen(ttk.Frame):
else:
self._fetch_msg_var.set("Fetch not available.")
def _import_csv(self, game_id: int, game_name: str):
fp = filedialog.askopenfilename(
title=f"Import draws for {game_name}",
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
)
if not fp:
return
result = import_draws_csv(game_id, fp)
added = result["added"]
skipped = result["skipped"]
errors = result["errors"]
msg = f"Added {added:,} draw{'s' if added != 1 else ''}."
if skipped:
msg += f"\nSkipped {skipped:,} duplicate{'s' if skipped != 1 else ''}."
if errors:
preview = "\n".join(errors[:5])
suffix = f"\n… and {len(errors) - 5} more." if len(errors) > 5 else ""
msg += f"\n\n{len(errors)} row error(s):\n{preview}{suffix}"
messagebox.showinfo("Import Complete", msg)
self._refresh_games()
def _backup_db(self):
try:
dest = backup_db()
messagebox.showinfo("Backup Complete", f"Database backed up to:\n{dest}")
except Exception as e:
messagebox.showerror("Backup Failed", str(e))
def _restore_db(self):
fp = filedialog.askopenfilename(
title="Select backup file to restore",
filetypes=[("SQLite DB", "*.db"), ("All files", "*.*")],
)
if not fp:
return
if not messagebox.askyesno(
"Confirm Restore",
"Restoring will overwrite the current database.\n"
"This cannot be undone. Continue?",
):
return
try:
restore_db(fp)
messagebox.showinfo("Restore Complete",
"Database restored. Please restart the app.")
except Exception as e:
messagebox.showerror("Restore Failed", str(e))
def _open_add_game_dialog(self):
_AddGameDialog(self, on_save=self._refresh_games)