Загрузка данных
# -*- coding: utf-8 -*-
# Файл: modules/project_edit.py
import tkinter as tk
from tkinter import Button, Radiobutton, Toplevel, messagebox, ttk
# Импортируем только функции и движок
from modules.database_module import engine, get_all_indicators_by_group, list_groups
class EditProjectWindow:
"""
Класс окна редактирования проекта.
Сессия SQLAlchemy открывается один раз при инициализации окна.
"""
def __init__(self, manager, root_window=None):
self.manager = manager # <-- НОВОЕ: Сохраняем ссылку на менеджер
self.root_window = root_window
self.window = Toplevel(root_window)
self.window.title("Редактирование проекта")
self.window.geometry("600x500")
self.window.protocol("WM_DELETE_WINDOW", self.on_close)
# --- ИЗМЕНЕНИЕ: Убираем создание своего движка ---
# self.engine = create_engine("sqlite:///database.db", echo=False)
# self.Session = sessionmaker(bind=self.engine)
# --- НОВОЕ: Используем сессию из основной базы (как было раньше) ---
from modules.database_module import Session
self.session = Session()
# ------------------------------------
self.group_names_to_codes = {}
self._create_widgets()
# Загружаем список проектов сразу
self.update_project_list()
# Если проекты есть, сразу грузим данные для первого из них
if self.project_combobox["values"]:
self._load_data()
# Словарь для быстрого поиска: Название проекта -> Код проекта (ID)
self.name_to_code_map = {}
def _create_widgets(self):
main_frame = tk.Frame(self.window)
main_frame.pack(padx=10, pady=2, fill="both", expand=True)
# 1. Выбор проекта
tk.Label(main_frame, text="Выбрать проект:").pack(anchor="w")
self.project_combobox = ttk.Combobox(main_frame, state="readonly")
self.project_combobox.pack(fill="x", padx=15)
self.project_combobox.bind("<<ComboboxSelected>>", self._load_data)
# 2. Выбор Группы
tk.Label(main_frame, text="Группа:").pack(anchor="w")
self.group_combobox = ttk.Combobox(main_frame, state="readonly")
self.group_combobox.bind("<<ComboboxSelected>>", self.update_indicators_list)
self.group_combobox.pack(fill="x", padx=15)
# 3. Выбор Показателя
tk.Label(main_frame, text="Показатель:").pack(anchor="w")
self.indicator_combobox = ttk.Combobox(main_frame, state="readonly")
self.indicator_combobox.pack(fill="x", padx=15)
# 4. Поле "Дата события"
date_frame = tk.Frame(main_frame)
date_frame.pack(fill="x", pady=3)
tk.Label(date_frame, text="Дата события:").pack(side="left")
self.date_entry = tk.Entry(date_frame, width=12)
self.date_entry.insert(0, "2026-05-08")
self.date_entry.pack(side="left", padx=(5, 0), ipadx=5)
# 5. Радиокнопки "Направленность"
direction_frame = tk.Frame(main_frame)
direction_frame.pack(fill="x", pady=3, anchor="w")
tk.Label(direction_frame, text="Направленность:").pack(side="left")
radio_frame = tk.Frame(direction_frame)
radio_frame.pack(side="left", padx=(5, 0))
self.direction_var = tk.IntVar(value=1)
Radiobutton(
radio_frame, text="+ К цели", variable=self.direction_var, value=1
).pack(side="left", padx=(0, 10))
Radiobutton(
radio_frame, text="- От цели", variable=self.direction_var, value=-1
).pack(side="left", padx=(0, 10))
# 6. Поле "Событие"
tk.Label(main_frame, text="Событие:").pack(anchor="w")
self.event_text = tk.Text(main_frame, height=4, wrap="word")
self.event_text.pack(fill="x", pady=(2, 5), padx=15)
# Кнопка "Вставить из буфера"
btn_frame = tk.Frame(main_frame)
btn_frame.pack(anchor="e", padx=15, pady=(0, 5))
Button(
btn_frame, text="Вставить", command=self.paste_from_clipboard, width=10
).pack()
# Блок кнопок управления (Добавить, Удалить...)
ctrl_btns = tk.Frame(main_frame)
ctrl_btns.pack(fill="x", pady=(5, 3)) # Отступ сверху стал меньше
Button(ctrl_btns, text="Добавить", width=15, command=self.add_entry).pack(
side="left", padx=(0, 10)
)
Button(ctrl_btns, text="Удалить", width=15).pack(side="left", padx=(10, 10))
Button(ctrl_btns, text="Печать в Word", width=15).pack(
side="left", padx=(10, 10)
)
Button(ctrl_btns, text="В архив", width=15, command=self.archive_project).pack(
side="left", padx=(10, 0)
)
# --- НОВЫЙ БЛОК: Список событий ---
events_frame = tk.Frame(main_frame)
events_frame.pack(
fill="both", expand=True, pady=(5, 0), padx=15
) # Отступ сверху стал меньше
tk.Label(events_frame, text="Список событий:").pack(anchor="w")
# Создаем виджет для списка (Listbox) с полосой прокрутки (Scrollbar)
self.events_listbox = tk.Listbox(events_frame, height=6)
self.events_listbox.pack(side="left", fill="both", expand=True)
scrollbar = tk.Scrollbar(
events_frame, orient="vertical", command=self.events_listbox.yview
)
self.events_listbox.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
def update_project_list(self):
"""Обновляет список проектов."""
projects = self.manager.list_projects()
try:
names = [p.name for p in projects]
self.project_combobox["values"] = names
return bool(names) # Вернем True/False для удобства
except Exception as e:
print(f"Ошибка: {e}")
return False
def _load_data(self, event=None):
"""Загружает данные (группы и показатели) при выборе проекта."""
# Получаем имя выбранного проекта
project_name = self.project_combobox.get()
# --- ОЧИЩАЕМ ВСЁ, ЕСЛИ ПРОЕКТ НЕ ВЫБРАН ---
if not project_name:
self.group_combobox.set("")
self.indicator_combobox.set("")
self.group_combobox["values"] = []
self.indicator_combobox["values"] = []
self.group_names_to_codes.clear() # Очищаем словарь групп
return
print("[ДИАГНОСТИКА] --- ЗАГРУЗКА ДАННЫХ ПО ПРОЕКТУ ---")
try:
# Получаем список объектов проектов (нужно для словаря)
project_objects = self.manager.list_projects()
# --- СОЗДАЕМ СЛОВАРЬ: Название -> Код (ID) ---
self.name_to_code_map.clear() # Очищаем на всякий случай
for p in project_objects:
# Сохраняем соответствие: Название проекта -> его Код (ID)
self.name_to_code_map[p.name] = p.id
# --- ЗАГРУЗКА ДАННЫХ ИЗ ОСНОВНОЙ БАЗЫ (группы и показатели) ---
groups = list_groups(session=self.session)
if groups:
group_names = [group.name for group in groups]
self.group_combobox["values"] = group_names
if group_names:
# Выбираем первую группу по умолчанию
first_group_name = group_names[0]
self.group_combobox.set(first_group_name)
# Обновляем словарь имен и кодов групп
self.group_names_to_codes.clear()
for group in groups:
self.group_names_to_codes[group.name] = group.code
# Загружаем показатели для первой группы
first_group_code = self.group_names_to_codes[first_group_name]
indicators = get_all_indicators_by_group(
first_group_code, session=self.session
)
if indicators:
indicator_names = [indicator.name for indicator in indicators]
self.indicator_combobox["values"] = indicator_names
if indicator_names:
self.indicator_combobox.set(indicator_names[0])
except Exception as e:
print(f"[ОШИБКА] Не удалось загрузить данные: {e}")
def update_indicators_list(self, event=None):
"""Обновляет список показателей при выборе новой группы."""
selected_group_name = self.group_combobox.get()
# Если группа не выбрана, очищаем список показателей
if not selected_group_name:
self.indicator_combobox.set("")
self.indicator_combobox["values"] = []
return
print(
f"[ДИАГНОСТИКА] Пользователь выбрал группу: '{selected_group_name}'. Загружаем показатели..."
)
try:
selected_group_code = self.group_names_to_codes.get(selected_group_name)
if selected_group_code:
indicators = get_all_indicators_by_group(
selected_group_code, session=self.session
)
if indicators:
indicator_names = [indicator.name for indicator in indicators]
self.indicator_combobox["values"] = indicator_names
if indicator_names:
# Сохраняем текущий выбор, если он есть в новом списке
current_val = self.indicator_combobox.get()
if current_val and current_val in indicator_names:
pass
else:
self.indicator_combobox.set(indicator_names[0])
print(
f"[ДИАГНОСТИКА] Список показателей обновлен: {indicator_names}"
)
else:
print("[ДИАГНОСТИКА] Показателей для этой группы нет.")
else:
print("[ДИАГНОСТИКА] Показателей для этой группы нет.")
# Очищаем поле и список, если показателей нет
if not indicators or len(indicators) == 0:
self.indicator_combobox.set("")
self.indicator_combobox["values"] = []
else:
print("[ДИАГНОСТИКА] Код группы не найден в словаре.")
self.indicator_combobox.set("")
self.indicator_combobox["values"] = []
except Exception as e:
print(f"[ОШИБКА] Не удалось загрузить показатели: {e}")
def add_entry(self):
"""Обработка нажатия кнопки 'Добавить'."""
# --- ИМПОРТЫ ---
import os
import sqlite3
# --- 1. ПРОВЕРКА ВХОДНЫХ ДАННЫХ ---
project_name = self.project_combobox.get()
if not project_name:
messagebox.showwarning("Ошибка", "Выберите проект.")
return
group_name = self.group_combobox.get()
if not group_name:
messagebox.showwarning("Ошибка", "Выберите группу.")
return
indicator_name = self.indicator_combobox.get()
if not indicator_name:
messagebox.showwarning("Ошибка", "Выберите показатель.")
return
event_text_content = self.event_text.get("1.0", tk.END).strip()
if not event_text_content:
messagebox.showwarning("Ошибка", "Поле 'Событие' пустое.")
return
# --- 2. ПОДГОТОВКА ДАННЫХ ---
from modules.database_module import Group, Indicator
# Получаем данные ГРУППЫ из основной БД
group_db = self.session.query(Group).filter_by(name=group_name).first()
if not group_db:
messagebox.showerror(
"Критическая ошибка", "Группа не найдена в базе данных."
)
return
group_kov = group_db.kov
# Получаем код ПОКАЗАТЕЛЯ
indicator_db = (
self.session.query(Indicator).filter_by(name=indicator_name).first()
)
if not indicator_db:
messagebox.showerror("Критическая ошибка", "Показатель не найден.")
return
indicator_code = indicator_db.code
direction = self.direction_var.get()
date_event = self.date_entry.get()
# --- 3. ЗАПИСЬ В БАЗУ (внутри try/except) ---
try:
# Получаем список проектов от менеджера и ищем нужный объект
project_objects = self.manager.list_projects()
found_project = None
for p in project_objects:
if p.name == project_name:
found_project = p
break
if not found_project:
messagebox.showerror(
"Ошибка", f"Проект '{project_name}' не найден в менеджере."
)
return
project_code = found_project.id
# Строим путь к базе данных проекта по коду (ID)
db_folder = os.path.join(os.getcwd(), "project_dbs")
db_path = os.path.join(db_folder, f"{project_code}.db")
if not os.path.exists(db_path):
messagebox.showerror(
"Ошибка", f"База данных не найдена по пути: {db_path}"
)
return
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Подсчет количества событий для генерации кода (IV-CODE_Номер)
cursor.execute(
"""
SELECT COUNT(*) FROM project_data
WHERE project_code = ? AND ivent_code LIKE ?
""",
(project_code, f"{indicator_code}_%"),
)
count = cursor.fetchone()[0]
event_code = f"{indicator_code}_{count + 1}"
cursor.execute(
"""
INSERT INTO project_data (
project_code, created_at, group_name, group_kov,
indicator_name, indicator_code, ivent_data, ivent_sign,
ivent_code, event_text_content
) VALUES (?, datetime('now'), ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
project_code,
group_name,
group_kov,
indicator_name,
indicator_code,
date_event,
direction,
event_code,
event_text_content,
),
)
conn.commit()
# --- 4. ОБНОВЛЕНИЕ ИНТЕРФЕЙСА (только если запись в БД успешна) ---
# Этот код находится ВНУТРИ блока try, поэтому выполнится только
# при успехе.
# Добавляем запись в список на экране
self.events_listbox.insert(
tk.END, f"{event_code}: {event_text_content[:40]}..."
)
# Очищаем поле события после добавления
self.event_text.delete("1.0", tk.END)
# Показываем сообщение об успехе
messagebox.showinfo(
"Успех", f"Данные добавлены.\nКод события: {event_code}"
)
except Exception as e:
# Если что-то пошло не так (например, нет таблицы или ошибка записи),
# мы выводим сообщение об ошибке и ВЫХОДИМ из метода.
# Код обновления интерфейса выше не выполнится.
messagebox.showerror("Ошибка БД", f"Не удалось записать данные: {e}")
return # <--- МАРКЕР: ЗДЕСЬ ПРОИСХОДИТ ВОЗВРАТ ПРИ ОШИБКЕ!
finally:
if "conn" in locals():
conn.close()
def paste_from_clipboard(self):
"""Вставляет текст из буфера обмена в поле события."""
try:
txt = self.window.clipboard_get()
# Вставляем текст в текущую позицию курсора или в начало поля
self.event_text.insert(tk.INSERT, txt)
except Exception as e:
pass
def archive_project(self):
"""Обработка нажатия кнопки 'В архив'."""
name = self.project_combobox.get()
if not name:
messagebox.showwarning("Ошибка", "Выберите проект.")
return
messagebox.showinfo("Успех", f"Проект '{name}' архивирован.")
def on_close(self):
"""Корректно закрывает сессию базы данных при закрытии окна."""
try:
if hasattr(self, "session") and self.session is not None:
self.session.close()
print("[ДИАГНОСТИКА] Сессия базы данных закрыта.")
# Это обязательно для модальных окон (grab_set), чтобы избежать
# зависаний главного окна
if hasattr(self.window, "destroy"):
self.window.destroy()
except Exception as e:
print(f"[ДИАГНОСТИКА] Ошибка при закрытии сессии: {e}")