Загрузка данных
# ====================================================
# Файл №1: Dozor/gui/main_interface_old.py
# - старый файл, который подвергается рефакторингу.
# ====================================================
# ---*--- Dozor/gui/main_interface_old.py ---*---
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import os
from gui.auth_interface import AuthInterface
from modules.administrator.admin_user_inter import AdminUserInter
from modules.utils.window_behavior import LevelOneBehavior
from modules.administrator.admin_db_inter import AdminDBInter
from modules.project.project_create_inter import ProjectCreateInterface
from modules.project.project_edit_inter import ProjectEditInterface
from modules.project.project_create_work import ProjectCreateController
from modules.project.services.project_service import ProjectService
def open_window(title):
print(f"[DEBUG] Вызван универсальный open_window для: {title}")
temp_win = tk.Toplevel()
temp_win.title(title)
temp_win.geometry("300x150")
ttk.Label(temp_win, text=f"Интерфейс '{title}'", font=("Arial", 14)).pack(pady=20)
ttk.Button(temp_win, text="Закрыть", command=temp_win.destroy).pack(pady=10)
class MainInterface(tk.Tk):
def __init__(self):
super().__init__()
# Флаг-предохранитель от ошибок winfo при смене пользователя
self._ui_blocked_by_logout = False
# Текущий пользователь (единый источник истины)
self.current_user = {"login": None}
behavior = LevelOneBehavior()
behavior.setup_level_one(self)
self.title("ДОЗОР -- Heri-Hodie-Cras")
self.geometry("458x360")
self.resizable(False, False)
bg_color = "#EEEEEE"
self.configure(bg=bg_color)
main_frame = tk.Frame(self, bg=bg_color)
main_frame.pack(padx=1, pady=1, fill='both', expand=True)
button_width = 20
button_height = 2
button_font = ("Arial", 12, "bold")
for i in range(7):
main_frame.grid_rowconfigure(i, weight=0)
main_frame.grid_columnconfigure(0, minsize=210, weight=0)
main_frame.grid_columnconfigure(1, minsize=235, weight=0)
self.active_popup = None
try:
from modules.project.project_create_work import ProjectCreateController
from modules.data.database_module import get_db_connection
db_conn = get_db_connection()
self.manager = ProjectCreateController(db_conn)
except Exception as e:
print(f"[FATAL] Не удалось инициализировать менеджер проектов: {e}")
self.manager = None
self.bind("<Button-1>", self.close_popup_global)
left_texts = [
["Администратор", [("Пользователи", "users"), ("Базы данных", "db")]],
["Менеджер базы", [("Менеджер группы", "group"), ("Менеджер показателей", "metrics")]],
["Эксперты", [("Создание файлов", "create_files"), ("Агрегирование файлов", "aggregate")]],
["Проект", [("Создать", "new"), ("Редактировать", "edit")]],
["Прогнозирование", []]
]
# --- ЛЕВАЯ ПАНЕЛЬ С УТОЧНЕНИЕМ ДОСТУПА ---
for row_index, item in enumerate(left_texts):
btn_text = item[0]
def execute_button(title=btn_text, index=row_index):
if title == "Администратор":
user_role = getattr(self, 'current_user', {}).get("role")
clean_role = str(user_role).strip().lower() if user_role else ""
if clean_role != "администратор":
from tkinter import messagebox
answer = messagebox.askyesno(
"Требуются права администратора",
f"У вашей учетной записи ('{clean_role.capitalize()}') недостаточно прав.\n\nХотите сменить пользователя и войти как Администратор?"
)
if answer:
self.switch_user_radical()
return
self.show_dropdown(title, index)
btn = tk.Button(
main_frame,
text=btn_text,
font=button_font,
width=button_width,
height=button_height,
anchor="w",
command=execute_button
)
btn.grid(row=row_index, column=0, padx=5, pady=5, sticky="ew")
right_box = tk.Frame(main_frame, bg=bg_color, width=235, height=300)
right_box.grid(row=0, column=1, rowspan=6, pady=5, sticky="nsew", padx=(0, 5))
right_box.grid_propagate(False)
right_box.columnconfigure(0, weight=1)
picture_frame = tk.Frame(right_box, bg="#d9ead3", highlightbackground="gray", highlightthickness=1, width=225, height=160)
picture_frame.place(relx=0.5, y=5, anchor="n")
picture_frame.grid_propagate(False)
current_dir = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(current_dir, "images", "Сова1.png")
try:
original_img = Image.open(image_path)
target_w, target_h = 215, 130
img_ratio = original_img.width / original_img.height
box_ratio = target_w / target_h
if img_ratio > box_ratio:
new_w, new_h = target_w, int(target_w / img_ratio)
else:
new_w, new_h = int(target_h * img_ratio), target_h
resized_img = original_img.resize((new_w, new_h), resample=Image.LANCZOS)
self.tk_image = ImageTk.PhotoImage(resized_img)
label = tk.Label(picture_frame, image=self.tk_image, bg="#d9ead3", borderwidth=0)
label.image = self.tk_image
label.place(relx=0.5, rely=0.5, anchor='center', width=new_w, height=new_h)
except Exception as e:
print(f"[FATAL IMAGE ERROR]: {e}")
canvas = tk.Canvas(picture_frame, width=225, height=160, bg="red", highlightthickness=0)
canvas.create_text(112, 80, text="IMG\nFAIL", fill="white")
canvas.place(x=0, y=0, relwidth=1, relheight=1)
spacer = tk.Frame(right_box, bg=bg_color, height=1)
spacer.grid(row=0, column=0, pady=(170, 10), sticky="ew")
upper_padding = tk.Frame(right_box, bg=bg_color, height=3)
upper_padding.grid(row=1, column=0)
btn6 = tk.Button(right_box, text="Руководство", font=button_font, width=button_width, height=button_height)
btn6.grid(row=2, column=0, ipadx=10, pady=(0, 5), sticky="ew")
btn7 = tk.Button(right_box, text="О программе", font=button_font, width=button_width, height=button_height)
btn7.grid(row=3, column=0, ipadx=10, pady=(5, 15), sticky="ew")
footer_frame = tk.Frame(main_frame, bg="#0078D7")
footer_frame.grid(row=6, column=0, columnspan=2, sticky="ew", pady=(10, 0))
footer_label = tk.Label(footer_frame, text="Copyright © Минск, НИИ, 2027 год", fg="#FF8C00", bg="#0078D7", font=("Arial", 12, "bold"))
footer_label.pack(expand=True, ipady=4)
self.bind("<B1-Motion>", self._on_drag_main_window)
self._drag_start_x = None
self._drag_start_y = None
def _on_drag_main_window(self, event):
if self._drag_start_x is None:
self._drag_start_x = event.x_root
self._drag_start_y = event.y_root
return
delta_x = event.x_root - self._drag_start_x
delta_y = event.y_root - self._drag_start_y
if abs(delta_x) > 2 or abs(delta_y) > 2:
if self.active_popup and self.active_popup.winfo_exists():
geo = self.active_popup.geometry()
parts = geo.split('+')
if len(parts) == 3:
new_x = int(parts[1]) + delta_x
new_y = int(parts[2]) + delta_y
self.active_popup.geometry(f"+{new_x}+{new_y}")
self._update_child_positions(event.x_root - self.winfo_rootx(), event.y_root - self.winfo_rooty())
self._drag_start_x = event.x_root
self._drag_start_y = event.y_root
def show_dropdown(self, menu_name, base_row):
from modules.utils.app_state import disable_app, enable_app
if self.active_popup is not None:
if hasattr(self.active_popup, '_child_windows'):
for child_data in list(self.active_popup._child_windows):
win, _, _ = child_data
try:
win.destroy()
except Exception:
pass
self.active_popup.destroy()
self.active_popup = None
root_win = self.winfo_toplevel()
data = {
"Администратор": [("Пользователи", "users"), ("Базы данных", "db")],
"Менеджер базы": [("Менеджер группы", "group"), ("Менеджер показателей", "metrics")],
"Эксперты": [("Создание файлов", "create_files"), ("Агрегирование файлов", "aggregate")],
"Проект": [("Создать", "new"), ("Редактировать", "edit")],
}.get(menu_name, [])
if not data:
return
x = self.winfo_rootx() + 5
y = self.winfo_rooty() + (base_row * 35) + 50
popup = tk.Toplevel(self)
popup.overrideredirect(True)
popup.geometry(f"+{x}+{y}")
self.active_popup = popup
popup_frame = tk.Frame(popup, bg="#F9F9F9", bd=1, relief="solid")
popup_frame.pack()
def make_command(action_title, target_popup=None):
def execute():
if target_popup:
target_popup.destroy()
disable_app(root_win)
app_instance = self
def handle_logout(restart=False):
global current_user_session, current_user
current_user_session = {"login": None}
current_user = {"login": None}
if hasattr(app_instance, 'current_user'):
app_instance.current_user = {"login": None}
print("[DEBUG] handle_logout вызван")
if restart:
# === КРИТИЧЕСКИ ВАЖНО: Ставим блок ПЕРЕД уничтожением окна ===
app_instance._ui_blocked_by_logout = True
disable_app(root_win)
root_win.withdraw()
login_callback = app_instance.do_login
new_auth = AuthInterface(root_win, on_login_click=login_callback)
root_win.wait_window(new_auth)
print(f"[DEBUG] Авторизация завершена. Текущий юзер: {current_user.get('login')}")
if current_user.get("login"):
root_win.deiconify()
enable_app(root_win)
role_display = current_user.get("role", "Без роли")
root_win.title(f"ДОЗОР -- {current_user['login']} ({role_display})")
# Разблокируем только после успешного входа
app_instance._ui_blocked_by_logout = False
else:
try:
root_win.destroy()
except Exception:
pass
else:
root_win.destroy()
if action_title == "Администратор":
user_role = getattr(self, 'current_user', {}).get("role")
clean_role = str(user_role).strip().lower() if user_role else ""
if clean_role != "администратор":
enable_app(root_win)
from tkinter import messagebox
messagebox.showwarning("Доступ запрещен", f"Раздел '{action_title}' доступен только пользователю с ролью Администратор.")
return
if action_title == "Пользователи":
users_win = AdminUserInter(root_win)
root_win.register_child_window(users_win, offset_x=20, offset_y=20)
root_win.wait_window(users_win)
# === ГЛАВНАЯ ПРОВЕРКА В ОКНЕ АДМИНКИ ===
# Если флаг блокировки стоит — значит, идет смена юзера, просто выходим.
if app_instance._ui_blocked_by_logout:
return
final_login = getattr(app_instance, 'current_user', {}).get("login")
if final_login is None:
print("[INFO] Пользователь инициировал смену через закрытие окна. Перезапуск...")
disable_app(root_win)
root_win.withdraw()
login_callback = app_instance.do_login
new_auth = AuthInterface(root_win, on_login_click=login_callback)
root_win.wait_window(new_auth)
after_login = getattr(app_instance, 'current_user', {}).get("login")
if after_login:
root_win.deiconify()
enable_app(root_win)
role_display = app_instance.current_user.get("role", "Без роли")
root_win.title(f"ДОЗОР -- {app_instance.current_user['login']} ({role_display})")
app_instance._ui_blocked_by_logout = False
else:
try:
root_win.destroy()
except Exception:
pass
return
else:
enable_app(root_win)
return
elif action_title == "Базы данных":
db_win = AdminDBInter(root_win)
root_win.register_child_window(db_win, offset_x=20, offset_y=20)
root_win.wait_window(db_win)
elif action_title == "Менеджер группы":
from modules.manager.manager_group_inter import ManagerGroupInter
win = ManagerGroupInter(root_win)
root_win.register_child_window(win, offset_x=20, offset_y=20)
win.lift()
elif action_title == "Менеджер показателей":
from modules.manager.manager_metrics_inter import ManagerMetricsInter
win = ManagerMetricsInter(root_win)
root_win.register_child_window(win, offset_x=20, offset_y=20)
win.lift()
elif action_title == "Создание файлов":
from modules.excel.excel_export_inter import ExpertExportInterface
export_win = ExpertExportInterface(root_win)
root_win.register_child_window(export_win, offset_x=20, offset_y=20)
export_win.lift()
elif action_title == "Агрегирование файлов":
from modules.excel.excel_aggregate_inter import ExpertAggregateInterface
agg_win = ExpertAggregateInterface(root_win)
root_win.register_child_window(agg_win, offset_x=20, offset_y=20)
agg_win.lift()
elif action_title == "Создать":
from modules.project.project_create_inter import ProjectCreateInterface
create_win = ProjectCreateInterface(root_win, self.manager)
root_win.register_child_window(create_win, offset_x=20, offset_y=20)
create_win.lift()
elif action_title == "Редактировать":
from modules.project.project_edit_inter import ProjectEditInterface
def on_project_selected(data_dict):
from modules.project.project_event_inter import ProjectEventWindow
event_win = ProjectEventWindow(manager=self.manager, selected_data=data_dict, root_window=root_win)
root_win.register_child_window(event_win, offset_x=20, offset_y=20)
event_win.lift()
edit_win = ProjectEditInterface(root_win, manager=self.manager, on_select_callback=on_project_selected)
root_win.register_child_window(edit_win, offset_x=20, offset_y=20)
edit_win.lift()
else:
open_window(action_title)
enable_app(root_win)
return execute
# --- ЦИКЛ СОЗДАНИЯ КНОПОК В ОКНЕ ---
for sub_text, action_key in data:
btn_command = make_command(sub_text)
def wrap_execute(cmd=None, pop_ref=popup, r_win=root_win):
if pop_ref and pop_ref.winfo_exists():
if hasattr(pop_ref, '_child_windows') and pop_ref._child_windows:
pop_ref.after(10, pop_ref.destroy)
else:
pop_ref.destroy()
# === ФИНАЛЬНЫЙ БАРЬЕР ДЛЯ ЛЯМБДА-ФУНКЦИИ ===
# Проверяем флаг перед тем, как вообще пытаться запустить команду cmd()
if cmd is not None and not r_win._ui_blocked_by_logout:
cmd()
else:
enable_app(r_win)
sub_btn = tk.Button(
popup_frame,
text=sub_text,
font=("Arial", 11),
width=20,
height=1,
anchor="w",
bd=0,
relief="flat",
cursor="hand2",
command=lambda c=btn_command: wrap_execute(c)
)
sub_btn.pack(fill="x", ipadx=5, pady=3)
sep = tk.Frame(popup_frame, bg="#E0E0E0", height=1)
sep.pack(fill="x")
def check_leave(e):
if not popup.winfo_exists():
return
mouse_x = e.x_root
mouse_y = e.y_root
pop_x = popup.winfo_rootx()
pop_y = popup.winfo_rooty()
pop_w = popup.winfo_width()
pop_h = popup.winfo_height()
safe_zone_x1 = pop_x - 30
safe_zone_y1 = pop_y - 30
safe_zone_x2 = pop_x + pop_w + 30
safe_zone_y2 = pop_y + pop_h + 30
if not (safe_zone_x1 <= mouse_x <= safe_zone_x2 and safe_zone_y1 <= mouse_y <= safe_zone_y2):
popup.destroy()
popup.bind("<Motion>", check_leave)
def switch_user_radical(self):
"""Радикальная смена пользователя: полный выход + немедленный вход."""
root_win = self.winfo_toplevel()
from modules.utils.app_state import disable_app
disable_app(root_win)
if hasattr(self, 'current_user'):
self.current_user = {"login": None}
if hasattr(self, '_child_windows'):
self._child_windows.clear()
try:
root_win.destroy()
except Exception:
pass
import sys, subprocess, os, shutil
script_path = os.path.abspath("main.py")
gui_python = shutil.which("pythonw.exe")
if not gui_python:
current_dir = os.path.dirname(sys.executable)
potential_path = os.path.join(current_dir, "pythonw.exe")
gui_python = potential_path if os.path.exists(potential_path) else sys.executable
try:
subprocess.Popen([gui_python, script_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
except Exception as e:
print(f"[FATAL] Не удалось запустить новую копию Dozor: {e}")
sys.exit(0)
def open_admin_users(self):
from modules.utils.app_state import disable_app, enable_app
root = self.winfo_toplevel()
if not root or not hasattr(root, 'winfo_exists') or not root.winfo_exists():
return
disable_app(root)
users_win = AdminUserInter(self)
self.wait_window(users_win)
enable_app(root)
def open_admin_dbs(self):
from modules.utils.app_state import disable_app, enable_app
root = self.winfo_toplevel()
if not root or not hasattr(root, 'winfo_exists') or not root.winfo_exists():
return
disable_app(root)
db_win = AdminDBInter(root)
self.wait_window(db_win)
enable_app(root)
def close_popup_global(self, event):
if self.active_popup is not None:
widget = event.widget
while widget:
if widget == self.active_popup:
return
widget = widget.master
self.active_popup.destroy()
self.active_popup = None
def _update_child_positions(self, new_x, new_y):
if hasattr(self, '_child_windows'):
for child_data in self._child_windows:
win, offset_x, offset_y = child_data
try:
win.geometry(f"+{new_x + offset_x}+{new_y + offset_y}")
except Exception:
pass
def register_child_window(self, window, offset_x=0, offset_y=0):
if not hasattr(self, '_child_windows'):
self._child_windows = []
for item in self._child_windows:
if item[0] == window:
return
self._child_windows.append((window, offset_x, offset_y))
if __name__ == "__main__":
app = MainInterface()
app.mainloop()
# ====================================================
# Файл №2: Dozor/modules/manager/manager_group_inter.py
# - файл организации интерфейса в Менеджере групп, как донор
# ====================================================
# ---*--- Dozor/modules/manager/manager_group_inter.py ---*---
# -*- coding: utf-8 -*-
import sys
import sqlite3
from pathlib import Path
try:
potential_base = Path.cwd()
if "Dozor" not in str(potential_base):
BASE_DIR = Path(__file__).resolve().parents[4]
else:
BASE_DIR = potential_base
DB_FOLDER = BASE_DIR / "db"
ACTIVE_FLAG_PATH = DB_FOLDER / "active.flag"
DEFAULT_DB_PATH = DB_FOLDER / "database.db"
MASTERDATA_PATH = DB_FOLDER / "masterdata.db"
except Exception as e:
print(f"[FATAL] Не удалось вычислить путь к базе данных: {e}")
sys.exit(1)
def _determine_active_path():
try:
if ACTIVE_FLAG_PATH.exists():
with open(ACTIVE_FLAG_PATH, 'r') as f:
name = f.read().strip()
if name == "masterdata":
return MASTERDATA_PATH
except Exception:
pass
return DEFAULT_DB_PATH
DB_PATH = _determine_active_path()
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session as SqlAlchemySession, declarative_base
engine = create_engine(f"sqlite:///{DB_PATH}", echo=False, connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db_session = SessionLocal()
try:
yield db_session
finally:
db_session.close()
def get_db_connection():
active_path = _determine_active_path()
global DB_PATH
DB_PATH = active_path
if not DB_FOLDER.exists():
print(f"[ACTION] Создаю папку для базы: {DB_FOLDER}")
DB_FOLDER.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(active_path)
conn.execute("PRAGMA foreign_keys = ON;")
return conn
def _create_tables(cursor):
cursor.execute('''
CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
code TEXT NOT NULL UNIQUE,
gain_group REAL DEFAULT 0.0
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS indicators (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
code TEXT NOT NULL UNIQUE,
group_code TEXT NOT NULL,
FOREIGN KEY (group_code) REFERENCES groups(code) ON DELETE CASCADE
)
''')
def _populate_test_data(conn):
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM groups")
count = cursor.fetchone()[0]
if count == 0:
print("[INFO] База данных пуста. Заполняю тестовыми данными...")
groups_data = [("Клубы", "KB", 1.5), ("Фанаты", "FAN", 2.0)]
cursor.executemany("INSERT INTO groups (name, code, gain_group) VALUES (?, ?, ?)", groups_data)
indicators_data = [
("ЦСКА", "KB-1", "KB"), ("Спартак", "KB-2", "KB"), ("Зенит", "KB-3", "KB"),
("Мужчины", "FAN-1", "FAN"), ("Женщины", "FAN-2", "FAN"), ("Дети", "FAN-3", "FAN")
]
cursor.executemany("INSERT INTO indicators (name, code, group_code) VALUES (?, ?, ?)", indicators_data)
conn.commit()
print("[SUCCESS] Тестовые данные успешно записаны.")
else:
print("[INFO] База данных уже содержит данные. Пропускаю инициализацию.")
def init_database():
print("[INIT] Проверка наличия рабочих баз данных...")
try:
conn = get_db_connection()
cursor = conn.cursor()
_create_tables(cursor)
_populate_test_data(conn)
except sqlite3.Error as e:
print(f"[ERROR] Ошибка при инициализации DATABASE.DB: {e}")
if conn:
conn.rollback()
finally:
if conn:
conn.close()
master_conn = None
try:
if not MASTERDATA_PATH.exists():
print(f"[ACTION] База мастер-данных не найдена. Создаю новую: {MASTERDATA_PATH.name}")
master_conn = sqlite3.connect(MASTERDATA_PATH)
master_conn.execute("PRAGMA foreign_keys = ON;")
master_cursor = master_conn.cursor()
_create_tables(master_cursor)
_populate_test_data(master_conn)
except sqlite3.Error as e:
print(f"[ERROR] Ошибка при инициализации MASTERDATA.DB: {e}")
if master_conn:
master_conn.rollback()
finally:
if master_conn:
master_conn.close()
class Session:
def __init__(self):
pass
# Эти функции объявлены СТАТИЧЕСКИМИ и НА УРОВНЕ МОДУЛЯ ниже
pass
# --- ФУНКЦИИ ДЛЯ ИМПОРТА (лежат строго вне класса Session) ---
def list_groups():
"""Функция-заглушка для прямого импорта"""
return [(1, "Клубы"), (2, "Фанаты")]
def get_all_indicators_by_group(group_code):
"""Функция-заглушка для прямого импорта"""
mock_data = {
"KB": [(1, "ЦСКА"), (2, "Спартак"), (3, "Зенит")],
"FAN": [(4, "Мужчины"), (5, "Женщины"), (6, "Дети")]
}
return mock_data.get(group_code, [])
if __name__ == "__main__":
init_database()
# ====================================================
# Файл №3: Dozor/modules/manager/manager_group_work.py
# - файл организации функционала в Менеджере групп, как донор
# ====================================================
# ---*--- Dozor/modules/manager/manager_group_work.py ---*---
# -*- coding: utf-8 -*-
# Dozor/modules/manager/manager_group_work.py
import logging
from typing import List, Optional
class GroupDTO:
"""Простой объект данных для передачи информации о группе между интерфейсом и логикой."""
def __init__(self, code: str, name: str, kov: Optional[float] = None):
self.code = code
self.name = name
self.kov = kov
def load_config():
"""Загрузка конфигурации усилителя. Заглушка возвращает тестовое значение."""
print("[WORK LOGIC] Вызов: load_config")
return {"amplifier": "25.5"}
def save_config(data):
"""Сохранение конфигурации. Заглушка выводит данные в консоль."""
print(f"[WORK LOGIC] Вызов: save_config | Данные: {data}")
def list_groups() -> List[GroupDTO]:
"""Получение списка групп. Заглушка возвращает статичные данные."""
print("[WORK LOGIC] Вызов: list_groups")
# TODO: Заменить на реальный запрос к БД через Session()
return [
GroupDTO(code="GRP-01", name="Группа А", kov=1.2),
GroupDTO(code="GRP-02", name="Группа Б", kov=0.8),
]
def add_group(name: str, code: str, kov: Optional[float]):
"""Добавление группы. Заглушка имитирует запись в БД."""
print(f"[WORK LOGIC] Вызов: add_group | Имя: {name}, Код: {code}, КОВ: {kov}")
# TODO: Реализовать вызов modules.database_module.add_group(...)
def edit_group(group_code: str, new_name: str, new_code: str, new_kov: Optional[float]):
"""Редактирование группы. Заглушка имитирует обновление в БД."""
print(f"[WORK LOGIC] Вызов: edit_group | Старый код: {group_code} -> Новый код: {new_code}")
# TODO: Реализовать сессию БД и session.query(Group)...
def delete_group(group_code: str):
"""Удаление группы. Заглушка имитирует удаление из БД."""
print(f"[WORK LOGIC] Вызов: delete_group | Код: {group_code}")
# TODO: Реализовать сессию БД и session.delete(...)
# --- ЗАГУШКИ ДЛЯ СУЩЕСТВУЮЩИХ ОШИБОК ИМПОРТА ---
def get_all_indicators_by_group(group_code: str) -> List:
"""Заглушка для получения индикаторов группы (твой старый метод использует его при смене кода)."""
print(f"[WORK LOGIC] Вызов: get_all_indicators_by_group | Группа: {group_code}")
return [] # Возвращаем пустой список, чтобы цикл редактирования не упал
def edit_indicator(ind_id, new_code: str):
"""Заглушка для обновления кода индикатора."""
print(f"[WORK LOGIC] Вызов: edit_indicator | ID: {ind_id}, Новый код: {new_code}")
# ====================================================
# Файл №4: Dozor/modules/manager/manager_metrics_inter.py
# - файл организации интерфейса в Менеджере показателей, как донор
# ====================================================
# ---*--- Dozor/modules/manager/manager_metrics_inter.py ---*---
# -*- coding: utf-8 -*-
import random
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from docx.shared import Cm, Pt
from docx import Document
from modules.manager.manager_metrics_work import (
get_all_indicators_by_group,
insert_indicator,
delete_indicator_by_code,
save_docx_template,
generate_unique_code,
IndicatorDTO,
)
from modules.manager.manager_group_work import list_groups
from modules.utils.window_behavior import LevelTwoBehavior
class ManagerMetricsInter(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.parent_main = parent
behavior = LevelTwoBehavior()
behavior.setup_level_two(self)
self.title("Менеджер показателей")
self.geometry("600x450")
# Теперь проверяем наличие login у переданного родителя (который и есть MainInterface)
if not hasattr(self.parent_main, 'current_user') or not self.parent_main.current_user.get("login"):
messagebox.showwarning("Ошибка", "Сессия пользователя не найдена.")
self.destroy()
return
# Отключаем использование master_db, так как в MainInterface его пока нет
self.use_master_db = False
# Создаем заглушку сессии БД для работы list_groups без ошибок SQLAlchemy
class FakeSession:
def query(self, *args, **kwargs):
class QueryObj:
def filter_by(self, **kw): return self
def all(self): return []
return QueryObj()
def close(self): pass
self.session = FakeSession()
self.group_names_to_codes = {}
self.create_widgets()
self.update_group_combobox()
self.update_indicators_list()
def paste_from_clipboard(self):
try:
clipboard_text = self.parent_main.clipboard_get()
self.name_entry.insert(tk.END, clipboard_text)
except tk.TclError:
messagebox.showwarning("Буфер обмена", "Буфер обмена пуст.")
def create_widgets(self):
top_panel = tk.Frame(self)
top_panel.pack(padx=10, pady=10, fill="x")
tk.Label(top_panel, text="Группа:").pack(anchor="w")
self.group_var = tk.StringVar()
self.group_combobox = ttk.Combobox(
top_panel, textvariable=self.group_var, state="readonly"
)
self.group_combobox.pack(fill="x", pady=5)
self.group_combobox.bind("<<ComboboxSelected>>", self.update_indicators_list)
tk.Label(top_panel, text="Название показателя:").pack(anchor="w")
self.name_entry = tk.Text(top_panel, height=3, width=40)
self.name_entry.pack(fill="x", pady=5)
paste_button = tk.Button(
top_panel, text="Вставить", command=self.paste_from_clipboard
)
paste_button.pack()
buttons_frame = tk.Frame(top_panel)
buttons_frame.pack(fill="x", pady=5)
tk.Button(buttons_frame, text="Добавить", command=self.add_indicator).pack(side="left", padx=2)
tk.Button(buttons_frame, text="Удалить", command=self.delete_indicator).pack(side="left", padx=2)
tk.Button(buttons_frame, text="Экспорт в Word", command=self.print_to_docx).pack(side="left", padx=5, pady=5)
middle_panel = tk.Frame(self)
middle_panel.pack(fill="both", expand=True, padx=10, pady=5)
scrollbar = ttk.Scrollbar(middle_panel)
scrollbar.pack(side="right", fill="y")
columns = ("number", "name", "code")
self.indicators_tree = ttk.Treeview(
middle_panel, columns=columns, show="headings", yscrollcommand=scrollbar.set, selectmode="browse"
)
self.indicators_tree.heading("number", text="№ п/п"); self.indicators_tree.column("number", width=50, anchor="center")
self.indicators_tree.heading("name", text="Название"); self.indicators_tree.column("name", width=400)
self.indicators_tree.heading("code", text="Код"); self.indicators_tree.column("code", width=150)
self.indicators_tree.pack(fill="both", expand=True)
scrollbar.config(command=self.indicators_tree.yview)
self.indicators_tree.bind("<Double-Button-1>", self.edit_indicator)
def update_group_combobox(self):
groups = list_groups()
self.group_names_to_codes.clear()
for group in groups:
self.group_names_to_codes[group.name] = group.code
group_names = [g.name for g in groups]
self.group_combobox["values"] = group_names
if group_names:
self.group_combobox.current(0)
self.update_indicators_list()
def update_indicators_list(self, event=None):
selected_group_name = self.group_var.get()
selected_group_code = self.group_names_to_codes.get(selected_group_name)
for item in self.indicators_tree.get_children():
self.indicators_tree.delete(item)
if not selected_group_code:
return
indicators = get_all_indicators_by_group(selected_group_code)
for idx, ind in enumerate(indicators, start=1):
self.indicators_tree.insert("", "end", iid=ind.code, values=(f"{idx}.", ind.name, ind.code))
def add_indicator(self):
group_name = self.group_var.get()
group_code = self.group_names_to_codes.get(group_name)
indicator_name = self.name_entry.get("1.0", tk.END).rstrip("\n").strip()
if not group_code or not indicator_name:
messagebox.showwarning("Ошибка", "Выберите группу и введите название!")
return
new_code = generate_unique_code(group_code)
insert_indicator(new_code, indicator_name, group_code)
self.name_entry.delete("1.0", tk.END)
self.update_indicators_list()
def edit_indicator(self, event):
selected_items = self.indicators_tree.selection()
if not selected_items:
return
code = selected_items[0]
# Для заглушек ищем имя прямо в дереве
current_name = ""
item_values = self.indicators_tree.item(code)["values"]
if len(item_values) > 1:
current_name = item_values[1]
edit_win = tk.Toplevel(self)
edit_win.title("Редактировать")
edit_win.geometry("350x150")
tk.Label(edit_win, text="Новое название:").pack(pady=5)
new_name_entry = tk.Text(edit_win, height=2, width=40)
new_name_entry.insert(tk.END, current_name)
new_name_entry.pack(pady=5)
tk.Button(
edit_win,
text="Сохранить",
command=lambda: self.save_changes(code, new_name_entry.get("1.0", tk.END), edit_win),
).pack(pady=5)
def save_changes(self, code, new_name_raw, edit_win):
new_name = new_name_raw.rstrip("\n").strip()
if not new_name:
messagebox.showwarning("Ошибка", "Название не может быть пустым.")
return
# В реальном коде тут был бы session.query...commit()
messagebox.showinfo("Готово", "Изменения сохранены (заглушка).")
self.update_indicators_list()
edit_win.destroy()
def delete_indicator(self):
selected_items = self.indicators_tree.selection()
if not selected_items:
messagebox.showwarning("Ошибка", "Выберите индикатор для удаления.")
return
code = selected_items[0]
if messagebox.askyesno("Подтверждение", f"Удалить индикатор с кодом {code}?"):
success = delete_indicator_by_code(code)
if success:
messagebox.showinfo("Готово", "Удалено успешно.")
self.update_indicators_list()
else:
messagebox.showerror("Ошибка", "Не удалось удалить.")
def print_to_docx(self):
group_name = self.group_var.get()
if not group_name:
messagebox.showwarning("Ошибка", "Выберите группу.")
return
group_code = self.group_names_to_codes.get(group_name)
indicators = get_all_indicators_by_group(group_code)
if not indicators:
messagebox.showwarning("Ошибка", "В группе нет показателей.")
return
filename = f"{group_name.replace(' ', '_')}_indicators.docx"
path = filedialog.asksaveasfilename(
initialfile=filename,
defaultextension=".docx",
filetypes=[("Word files", "*.docx"), ("All files", "*.*")],
)
if path:
success = save_docx_template(group_name, indicators, path)
if success:
messagebox.showinfo("Готово", f"Файл сохранен как:\n{os.path.basename(path)}")
def on_close(self):
self.destroy()
# ====================================================
# Файл №5: Dozor/modules/manager/manager_metrics_work.py
# - файл организации функционала в Менеджере показателей, как донор
# ====================================================
# ---*--- Dozor/modules/manager/manager_metrics_work.py ---*---
# -*- coding: utf-8 -*-
import os
import random
from typing import List, Optional
class IndicatorDTO:
"""Объект данных для передачи информации об индикаторе."""
def __init__(self, code: str, name: str):
self.code = code
self.name = name
def generate_unique_code(group_code: str) -> str:
"""Генерация заглушечного уникального кода."""
print(f"[WORK LOGIC] Вызов: generate_unique_code | Группа: {group_code}")
return f"{group_code}-{random.randint(100, 999)}"
def list_groups():
"""Заглушка списка групп из manager_group_work."""
from modules.manager.manager_group_work import list_groups as lg
return lg()
def get_all_indicators_by_group(group_code: str) -> List[IndicatorDTO]:
"""Получение показателей группы. Заглушка возвращает статичные данные."""
print(f"[WORK LOGIC] Вызов: get_all_indicators_by_group | Код: {group_code}")
# TODO: Реальный запрос через DBSession.query(Indicator)...
return [
IndicatorDTO(code=f"{group_code}-1", name="Показатель А"),
IndicatorDTO(code=f"{group_code}-2", name="Показатель Б"),
]
def insert_indicator(code: str, name: str, group_code: str):
"""Добавление показателя. Заглушка имитирует запись в БД."""
print(f"[WORK LOGIC] Вызов: insert_indicator | Код: {code}, Имя: {name}, Группа: {group_code}")
def delete_indicator_by_code(code: str):
"""Удаление показателя. Заглушка имитирует удаление из БД."""
print(f"[WORK LOGIC] Вызов: delete_indicator_by_code | Код: {code}")
return True
def save_docx_template(group_name: str, indicators: List[IndicatorDTO], path: str):
"""Сохранение заглушечного DOCX файла."""
print(f"[WORK LOGIC] Вызов: save_docx_template | Путь: {path} | Группа: {group_name}")
try:
with open(path, "w", encoding="utf-8") as f:
f.write(f"Группа: {group_name}\n\n")
for idx, ind in enumerate(indicators, start=1):
f.write(f"{idx}. [{ind.code}] {ind.name}\n")
return True
except Exception as e:
print(f"[WORK LOGIC ERROR] Не удалось сохранить файл: {e}")
return False
# ====================================================
# Файл №6: Dozor/modules/data/database_module.py
# Файл работы с базами данных
# ====================================================
# ---*--- Dozor/modules/data/database_module.py ---*---
# -*- coding: utf-8 -*-
import sys
import sqlite3
from pathlib import Path
try:
potential_base = Path.cwd()
if "Dozor" not in str(potential_base):
BASE_DIR = Path(__file__).resolve().parents[4]
else:
BASE_DIR = potential_base
DB_FOLDER = BASE_DIR / "db"
ACTIVE_FLAG_PATH = DB_FOLDER / "active.flag"
DEFAULT_DB_PATH = DB_FOLDER / "database.db"
MASTERDATA_PATH = DB_FOLDER / "masterdata.db"
except Exception as e:
print(f"[FATAL] Не удалось вычислить путь к базе данных: {e}")
sys.exit(1)
def _determine_active_path():
try:
if ACTIVE_FLAG_PATH.exists():
with open(ACTIVE_FLAG_PATH, 'r') as f:
name = f.read().strip()
if name == "masterdata":
return MASTERDATA_PATH
except Exception:
pass
return DEFAULT_DB_PATH
DB_PATH = _determine_active_path()
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session as SqlAlchemySession, declarative_base
engine = create_engine(f"sqlite:///{DB_PATH}", echo=False, connect_args={"check_same_thread": False})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db_session = SessionLocal()
try:
yield db_session
finally:
db_session.close()
def get_db_connection():
active_path = _determine_active_path()
global DB_PATH
DB_PATH = active_path
if not DB_FOLDER.exists():
print(f"[ACTION] Создаю папку для базы: {DB_FOLDER}")
DB_FOLDER.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(active_path)
conn.execute("PRAGMA foreign_keys = ON;")
return conn
def _create_tables(cursor):
cursor.execute('''
CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
code TEXT NOT NULL UNIQUE,
gain_group REAL DEFAULT 0.0
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS indicators (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
code TEXT NOT NULL UNIQUE,
group_code TEXT NOT NULL,
FOREIGN KEY (group_code) REFERENCES groups(code) ON DELETE CASCADE
)
''')
def _populate_test_data(conn):
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM groups")
count = cursor.fetchone()[0]
if count == 0:
print("[INFO] База данных пуста. Заполняю тестовыми данными...")
groups_data = [("Клубы", "KB", 1.5), ("Фанаты", "FAN", 2.0)]
cursor.executemany("INSERT INTO groups (name, code, gain_group) VALUES (?, ?, ?)", groups_data)
indicators_data = [
("ЦСКА", "KB-1", "KB"), ("Спартак", "KB-2", "KB"), ("Зенит", "KB-3", "KB"),
("Мужчины", "FAN-1", "FAN"), ("Женщины", "FAN-2", "FAN"), ("Дети", "FAN-3", "FAN")
]
cursor.executemany("INSERT INTO indicators (name, code, group_code) VALUES (?, ?, ?)", indicators_data)
conn.commit()
print("[SUCCESS] Тестовые данные успешно записаны.")
else:
print("[INFO] База данных уже содержит данные. Пропускаю инициализацию.")
def init_database():
print("[INIT] Проверка наличия рабочих баз данных...")
try:
conn = get_db_connection()
cursor = conn.cursor()
_create_tables(cursor)
_populate_test_data(conn)
except sqlite3.Error as e:
print(f"[ERROR] Ошибка при инициализации DATABASE.DB: {e}")
if conn:
conn.rollback()
finally:
if conn:
conn.close()
master_conn = None
try:
if not MASTERDATA_PATH.exists():
print(f"[ACTION] База мастер-данных не найдена. Создаю новую: {MASTERDATA_PATH.name}")
master_conn = sqlite3.connect(MASTERDATA_PATH)
master_conn.execute("PRAGMA foreign_keys = ON;")
master_cursor = master_conn.cursor()
_create_tables(master_cursor)
_populate_test_data(master_conn)
except sqlite3.Error as e:
print(f"[ERROR] Ошибка при инициализации MASTERDATA.DB: {e}")
if master_conn:
master_conn.rollback()
finally:
if master_conn:
master_conn.close()
class Session:
def __init__(self):
pass
# Эти функции объявлены СТАТИЧЕСКИМИ и НА УРОВНЕ МОДУЛЯ ниже
pass
# --- ФУНКЦИИ ДЛЯ ИМПОРТА (лежат строго вне класса Session) ---
def list_groups():
"""Функция-заглушка для прямого импорта"""
return [(1, "Клубы"), (2, "Фанаты")]
def get_all_indicators_by_group(group_code):
"""Функция-заглушка для прямого импорта"""
mock_data = {
"KB": [(1, "ЦСКА"), (2, "Спартак"), (3, "Зенит")],
"FAN": [(4, "Мужчины"), (5, "Женщины"), (6, "Дети")]
}
return mock_data.get(group_code, [])
if __name__ == "__main__":
init_database()
# ====================================================
# Файл №7: Dozor/gui/main_interface.py
# - НОВЫЙ ОСНОВНОЙ ФАЙЛ с которым работаем.
# - это основной файл для главного окна интерфейса
# - в этом файле организовываем второю кнопку Менеджер базы
# ====================================================
# ---*--- Dozor/gui/main_interface.py ---*---
# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import os
class MainInterface(tk.Tk):
def __init__(self):
super().__init__()
self.current_user = {"login": None}
# === ТОЛЬКО ГЕОМЕТРИЯ И ВИДЖЕТЫ ===
self.geometry("458x360")
self.resizable(False, False)
bg_color = "#EEEEEE"
self.configure(bg=bg_color)
main_frame = tk.Frame(self, bg=bg_color)
main_frame.pack(padx=1, pady=1, fill='both', expand=True)
button_width = 20
button_height = 2
button_font = ("Arial", 12, "bold")
for i in range(7):
main_frame.grid_rowconfigure(i, weight=0)
main_frame.grid_columnconfigure(0, minsize=210, weight=0)
main_frame.grid_columnconfigure(1, minsize=235, weight=0)
# --- ЛЕВАЯ ПАНЕЛЬ ---
left_buttons = [
("Администратор", self._show_admin_menu),
("Менеджер базы", self._show_manager_menu),
("Эксперты", self._show_experts_menu),
("Проект", self._show_project_menu),
("Прогнозирование", self._show_forecast_menu)
]
for row_index, (btn_text, command_func) in enumerate(left_buttons):
btn = tk.Button(
main_frame,
text=btn_text,
font=button_font,
width=button_width,
height=button_height,
anchor="w",
command=command_func
)
btn.grid(row=row_index, column=0, padx=5, pady=5, sticky="ew")
# --- ПРАВАЯ ПАНЕЛЬ ---
right_box = tk.Frame(main_frame, bg=bg_color, width=235, height=300)
right_box.grid(row=0, column=1, rowspan=6, pady=5, sticky="nsew", padx=(0, 5))
right_box.grid_propagate(False)
right_box.columnconfigure(0, weight=1)
picture_frame = tk.Frame(right_box, bg="#d9ead3", highlightbackground="gray",
highlightthickness=1, width=225, height=160)
picture_frame.place(relx=0.5, y=5, anchor="n")
picture_frame.grid_propagate(False)
current_dir = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(current_dir, "images", "Сова1.png")
try:
original_img = Image.open(image_path)
target_w, target_h = 215, 130
img_ratio = original_img.width / original_img.height
box_ratio = target_w / target_h
if img_ratio > box_ratio:
new_w, new_h = target_w, int(target_w / img_ratio)
else:
new_w, new_h = int(target_h * img_ratio), target_h
resized_img = original_img.resize((new_w, new_h), resample=Image.LANCZOS)
self.tk_image = ImageTk.PhotoImage(resized_img)
label = tk.Label(picture_frame, image=self.tk_image, bg="#d9ead3", borderwidth=0)
label.image = self.tk_image
label.place(relx=0.5, rely=0.5, anchor='center', width=new_w, height=new_h)
except Exception as e:
print(f"[FATAL IMAGE ERROR]: {e}")
canvas = tk.Canvas(picture_frame, width=225, height=160, bg="red", highlightthickness=0)
canvas.create_text(112, 80, text="IMG\nFAIL", fill="white")
canvas.place(x=0, y=0, relwidth=1, relheight=1)
spacer = tk.Frame(right_box, bg=bg_color, height=1)
spacer.grid(row=0, column=0, pady=(170, 10), sticky="ew")
upper_padding = tk.Frame(right_box, bg=bg_color, height=3)
upper_padding.grid(row=1, column=0)
btn6 = tk.Button(right_box, text="Руководство", font=button_font, width=button_width, height=button_height)
btn6.grid(row=2, column=0, ipadx=10, pady=(0, 5), sticky="ew")
btn7 = tk.Button(right_box, text="О программе", font=button_font, width=button_width, height=button_height)
btn7.grid(row=3, column=0, ipadx=10, pady=(5, 15), sticky="ew")
footer_frame = tk.Frame(main_frame, bg="#0078D7")
footer_frame.grid(row=6, column=0, columnspan=2, sticky="ew", pady=(10, 0))
footer_label = tk.Label(footer_frame, text="Copyright © Минск, НИИ, 2027 год", fg="#FF8C00",
bg="#0078D7", font=("Arial", 12, "bold"))
footer_label.pack(expand=True, ipady=4)
# --- КОНТЕЙНЕРЫ ДЛЯ ПОДМЕНЮ ---
self.menu_container = tk.Frame(main_frame, bg=bg_color)
self.menu_container.grid_rowconfigure(0, weight=1)
self.menu_container.grid_columnconfigure(0, weight=1)
def _create_menu_panel(container, title_text):
panel = tk.Frame(container, bg="#f0f0f0", bd=2, relief="sunken")
panel.grid_propagate(False)
header_frame = tk.Frame(panel, bg="#4a4a4a", height=30)
header_frame.pack(fill='x')
close_btn = tk.Button(
header_frame,
text="← Назад",
font=("Arial", 9),
fg="white",
bg="#888888",
borderwidth=0,
command=self._show_main_screen
)
close_btn.pack(side='right', padx=5, pady=2)
content_frame = tk.Frame(panel, bg="#f0f0f0")
content_frame.pack(fill='both', expand=True, padx=10, pady=10)
ttk.Label(content_frame, text=f"Здесь будет функционал:\n{title_text}").pack(expand=True)
return panel
self.admin_panel = _create_menu_panel(self.menu_container, "Подменю Администратор")
self.manager_panel = _create_menu_panel(self.menu_container, "Подменю Менеджер базы")
self.experts_panel = _create_menu_panel(self.menu_container, "Подменю Эксперты")
self.project_panel = _create_menu_panel(self.menu_container, "Подменю Проект")
self.forecast_panel = _create_menu_panel(self.menu_container, "Подменю Прогнозирование")
self.guide_panel = _create_menu_panel(self.menu_container, "Подменю Руководство")
self.about_panel = _create_menu_panel(self.menu_container, "Подменю О программе")
self._current_menu_visible = None
# --- МЕТОД ВОЗВРАТА НА ГЛАВНЫЙ ЭКРАН ---
def _show_main_screen(self):
self._hide_current_menu()
self.menu_container.grid_remove()
self.grid_columnconfigure(0, minsize=210, weight=0)
self.grid_columnconfigure(1, minsize=235, weight=0)
def _hide_current_menu(self):
panels = {
'admin': self.admin_panel,
'manager': self.manager_panel,
'experts': self.experts_panel,
'project': self.project_panel,
'forecast': self.forecast_panel,
'guide': self.guide_panel,
'about': self.about_panel
}
if self._current_menu_visible and self._current_menu_visible in panels:
panels[self._current_menu_visible].grid_remove()
self._current_menu_visible = None
# --- ОБРАБОТЧИКИ КНОПОК (БЕЗ .title()) ---
def _show_admin_menu(self):
self._show_main_screen()
self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
self.admin_panel.grid(row=0, column=0, sticky="nsew")
self._current_menu_visible = 'admin'
def _show_manager_menu(self):
self._show_main_screen()
self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
self.manager_panel.grid(row=0, column=0, sticky="nsew")
self._current_menu_visible = 'manager'
def _show_experts_menu(self):
self._show_main_screen()
self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
self.experts_panel.grid(row=0, column=0, sticky="nsew")
self._current_menu_visible = 'experts'
def _show_project_menu(self):
self._show_main_screen()
self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
self.project_panel.grid(row=0, column=0, sticky="nsew")
self._current_menu_visible = 'project'
def _show_forecast_menu(self):
self._show_main_screen()
self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
self.forecast_panel.grid(row=0, column=0, sticky="nsew")
self._current_menu_visible = 'forecast'
def _show_guide_menu(self):
self._show_main_screen()
self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
self.guide_panel.grid(row=0, column=0, sticky="nsew")
self._current_menu_visible = 'guide'
def _show_about_menu(self):
self._show_main_screen()
self.menu_container.grid(row=0, column=0, rowspan=6, columnspan=2, sticky="nsew")
self.about_panel.grid(row=0, column=0, sticky="nsew")
self._current_menu_visible = 'about'
def switch_user_radical(self):
root_win = self.winfo_toplevel()
from modules.utils.app_state import disable_app
disable_app(root_win)
if hasattr(self, 'current_user'):
self.current_user = {"login": None}
if hasattr(self, '_child_windows'):
self._child_windows.clear()
try:
root_win.destroy()
except Exception:
pass
import sys, subprocess, os, shutil
script_path = os.path.abspath("main.py")
gui_python = shutil.which("pythonw.exe")
if not gui_python:
current_dir = os.path.dirname(sys.executable)
potential_path = os.path.join(current_dir, "pythonw.exe")
gui_python = potential_path if os.path.exists(potential_path) else sys.executable
try:
subprocess.Popen([gui_python, script_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0)
except Exception as e:
print(f"[FATAL] Не удалось запустить новую копию Dozor: {e}")
sys.exit(0)