Загрузка данных
'''
День добрый!
Необходимо решить 2 задачи после анализа высланных файлов.
Приложение работает нормально. Но при сборке приложения в единый ехе файл (руинсталлером на виндовс) компиляция проходит нормально, а картинка пропадает.
Задача - решить данный вопрос, чтобы и в разработке приложения до компиляции картинки показывало и после компеляции. Я вышлю 3 файла - один просто запускающий, второй главный интерфейсный файл, где и расположена картинка.
Кроме картинки в других местах также возникают периодически проблемы с путями. В данный момент не работает вход по авторизации, так как не находит базу данных по адресу db/users.db
Интерфейсный файл не трогать, разве что изменить пути правильно. Но для того чтобы эта проблема была решена раз и навсегда при компеляции, возможно необходимо создать дополнительный файл путей (не только для картинок).
Прошу учитывать, что я вайб кодер.
Все действия не спеша и по шагам - шаг сделали - проверили - дальше.
Для того чтобы двигаться дальше необходимо сначала организовать вход (проблемма с путями к базе данных). Вот ошибка
(dozor_env) (base) E:\dukora>python main.py
Exception in Tkinter callback
Traceback (most recent call last):
File "E:\Anaconda3\Lib\tkinter\__init__.py", line 2068, in __call__
return self.func(*args)
~~~~~~~~~^^^^^^^
File "E:\dozor_env\Lib\site-packages\customtkinter\windows\widgets\ctk_button.py", line 564, in _on_release
self._command()
~~~~~~~~~~~~~^^
File "E:\dukora\gui\main_interface.py", line 158, in open_admin_module
self._admin_win = AdminAccordionWindow(self)
~~~~~~~~~~~~~~~~~~~~^^^^^^
File "E:\dukora\modules\admin\admin_accordion_window.py", line 54, in __init__
self._fill_users_section(users_item["content"])
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "E:\dukora\modules\admin\admin_accordion_window.py", line 173, in _fill_users_section
panel.pack(fill="both", expand=True, padx=10, pady=10)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\dozor_env\Lib\site-packages\customtkinter\windows\widgets\core_widget_classes\ctk_base_class.py", line 298, in pack
return super().pack(**self._apply_argument_scaling(kwargs))
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\Anaconda3\Lib\tkinter\__init__.py", line 2596, in pack_configure
self.tk.call(
~~~~~~~~~~~~^
('pack', 'configure', self._w)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ self._options(cnf, kw))
^^^^^^^^^^^^^^^^^^^^^^^^^
_tkinter.TclError: bad window path name ".!adminaccordionwindow.!ctkframe.!ctkframe.!ctkframe.!ctkframe.!adminuserspanel"
(dozor_env) (base) E:\dukora>
'''
# gui\main_interface.py
import os/
import customtkinter as ctk
from PIL import Image
import sys
from modules.manager.manager_window import ManagerWindow
from modules.experts.experts_window import ExpertsWindow
from modules.project.project_window import ProjectWindow
from modules.forecasting.forecast_window import ForecastWindow
class MainApp(ctk.CTk):
# 1. Исправлено имя конструктора (было def init)
def __init__(self):
super().__init__()
self.title("Dozor System")
try:
if sys.platform == "win32":
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except Exception:
pass
target_w, target_h = 422, 335
self.geometry(f"{target_w}x{target_h}")
self.resizable(False, False)
bg_color = "#F0F0F0"
self.configure(fg_color=bg_color)
main_bg = ctk.CTkFrame(self, fg_color=bg_color)
main_bg.pack(fill="both", expand=True, padx=1, pady=1)
main_bg.grid_columnconfigure(0, minsize=210, weight=0)
main_bg.grid_columnconfigure(1, weight=1)
for i in range(6):
main_bg.grid_rowconfigure(i, weight=0)
main_bg.grid_rowconfigure(6, weight=1)
button_font = ("Arial", 13, "bold")
btn_height = 45
# === ЛЕВАЯ ПАНЕЛЬ МЕНЮ ===
sidebar = ctk.CTkFrame(main_bg, fg_color="#EEEEEE", width=210)
sidebar.grid(row=0, column=0, rowspan=7, sticky="nsew", padx=(5, 5), pady=5)
sidebar.grid_columnconfigure(0, weight=1)
actions = [
("Администратор", self.open_admin_module),
("Менеджер базы", self.open_manager_module),
("Эксперты", self.open_experts_module),
("Проект", self.open_project_module),
("Прогнозирование", self.open_forecasting_module)
]
# 2. Убран лишний блок if-elif. Команда задается сразу из словаря.
for i, (text, command_func) in enumerate(actions):
btn = ctk.CTkButton(
master=sidebar,
text=text,
font=button_font,
height=btn_height,
command=command_func
)
btn.grid(row=i, column=0, padx=10, pady=5, sticky="ew")
# === ПРАВАЯ ЧАСТЬ ВИТРИНЫ ===
content_area = ctk.CTkFrame(main_bg, fg_color=bg_color)
content_area.grid(row=0, column=1, rowspan=7, sticky="nsew", padx=(0, 10), pady=10)
content_area.grid_columnconfigure(0, weight=1)
content_area.grid_rowconfigure(0, weight=0)
content_area.grid_rowconfigure(1, minsize=2, weight=0)
content_area.grid_rowconfigure(2, minsize=27, weight=0)
content_area.grid_rowconfigure(3, weight=0)
pic_container = ctk.CTkFrame(content_area, fg_color="#d9ead3", width=220, height=130)
pic_container.grid(row=0, column=0, pady=(5, 0))
pic_container.grid_propagate(False)
img_loaded = False
try:
img_path = os.path.join(os.getcwd(), 'images', 'logo1.png')
pil_img = Image.open(img_path)
target_w_i, target_h_i = 210, 110
ratio = pil_img.width / pil_img.height
box_ratio = target_w_i / target_h_i
if ratio > box_ratio:
new_w, new_h = target_w_i, int(target_w_i / ratio)
else:
new_w, new_h = int(target_h_i * ratio), target_h_i
resized_img = pil_img.resize((new_w, new_h), resample=Image.LANCZOS)
tk_image = ctk.CTkImage(light_image=resized_img, size=(new_w, new_h))
lbl_pic = ctk.CTkLabel(pic_container, image=tk_image, text="", fg_color="#d9ead3")
lbl_pic.place(relx=0.5, rely=0.5, anchor='center', x=0, y=-3)
img_loaded = True
except Exception as e:
fail_lbl = ctk.CTkLabel(pic_container, text="Совa\nне найдена", font=("Arial", 14), fg_color="#d9ead3")
fail_lbl.place(relx=0.5, rely=0.5, anchor="center")
bottom_btns_frame = ctk.CTkFrame(content_area, fg_color=bg_color)
bottom_btns_frame.grid(row=3, column=0, sticky="ew", padx=0)
bottom_btns_frame.grid_columnconfigure(0, minsize=195, weight=0)
ctk.CTkButton(
bottom_btns_frame,
text="Руководство",
font=button_font,
height=btn_height,
command=self.open_guide
).grid(row=0, column=0, sticky="ew", pady=(0, 8))
ctk.CTkButton(
bottom_btns_frame,
text="О программе",
font=button_font,
height=btn_height,
command=self.show_about
).grid(row=1, column=0, sticky="ew")
footer_frame = ctk.CTkFrame(main_bg, fg_color="#0078D7", height=30)
footer_frame.grid(row=7, column=0, columnspan=2, sticky="ew", pady=(10, 0))
footer_label = ctk.CTkLabel(footer_frame,
text="Copyright © Минск, НИИ, 2027 год",
text_color="#FF8C00",
fg_color="#0078D7",
font=("Arial", 13, "bold"))
footer_label.pack(expand=True, ipady=4)
self.update_idletasks()
self.tk.call('wm', 'geometry', self._w, f"{target_w}x{target_h}")
screen_w = self.winfo_screenwidth()
screen_h = self.winfo_screenheight()
pos_x = (screen_w - target_w) // 2
pos_y = (screen_h - target_h) // 2
self.geometry(f"+{pos_x}+{pos_y}")
# 3. Оставлен только ОДИН рабочий метод open_admin_module
def open_admin_module(self):
if hasattr(self, '_admin_win') and self._admin_win.winfo_exists():
self._admin_win.lift()
return
# Открываем наше новое красивое окно-аккордеон
from modules.admin.admin_accordion_window import AdminAccordionWindow
self._admin_win = AdminAccordionWindow(self)
self.wait_window(self._admin_win)
def open_manager_module(self):
if hasattr(self, '_manager_win') and self._manager_win.winfo_exists():
self._manager_win.lift()
return
self._manager_win = ManagerWindow(master=self)
self.wait_window(self._manager_win)
def open_experts_module(self):
if hasattr(self, '_experts_win') and self._experts_win.winfo_exists():
self._experts_win.lift()
return
self._experts_win = ExpertsWindow(master=self)
self.wait_window(self._experts_win)
def open_project_module(self):
if hasattr(self, '_project_win') and self._project_win.winfo_exists():
self._project_win.lift()
return
self._project_win = ProjectWindow(master=self)
self.wait_window(self._project_win)
def open_forecasting_module(self):
if hasattr(self, '_forecast_win') and self._forecast_win.winfo_exists():
self._forecast_win.lift()
return
self._forecast_win = ForecastWindow(master=self)
self.wait_window(self._forecast_win)
def open_guide(self):
file_name = "manual.chm"
full_path = os.path.join(os.getcwd(), file_name)
print(f"Пытаюсь открыть: {full_path}")
if os.path.exists(full_path):
try:
os.startfile(full_path)
except Exception as e:
print(f"Система отказалась открывать файл: {e}")
else:
print("Ошибка: ФАЙЛА НЕТ в этой папке!")
def show_about(self):
from tkinter import messagebox
info_text = (
"Система Dozor V1.0\n"
"Разработано: Минск, НИИ\n"
"Версия сборки: 31.07.2026\n\n"
"(c) Все права защищены."
)
messagebox.showinfo(title="О программе", message=info_text)
# 4. Исправлено имя переменной (__name__)
if __name__ == "__main__":
app = MainApp()
app.mainloop()
# dukora\main.py
import customtkinter as ctk
from gui.main_interface import MainApp
from auth_window import AuthWindow # Теперь Питон легко найдет этот файл в той же папке
def start_application():
app = MainApp()
# Создаем модальное окно поверх замороженного главного
auth_win = AuthWindow(app)
app.wait_window(auth_win)
# Запускаем основной интерфейс только после успешной авторизации
app.mainloop()
if __name__ == "__main__":
try:
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
except Exception:
pass
start_application()
# modules/admin/users_tab.py
import customtkinter as ctk
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
from hashlib import sha256
from pathlib import Path
class AdminUsersPanel(ctk.CTkFrame):
def __init__(self, master):
# Исправлено: правильное имя конструктора
super().__init__(master)
# Определяем корень проекта один раз. Теперь база ищется по пути /db/users.db от этого файла
self.project_root = Path(__file__).resolve().parents[2]
self.db_path = self.project_root / "db" / "users.db"
# --- Твой оригинальный UI ---
container = ctk.CTkFrame(self)
container.pack(fill="both", expand=True, padx=10, pady=10)
style = ttk.Style()
style.theme_use("default")
style.configure("mystyle.Treeview", background="#2b2b2b", fieldbackground="#2b2b2b", foreground="white", borderwidth=0, font=("Arial", 10))
style.map('mystyle.Treeview', background=[('selected', '#4a4a4a')])
table_frame = ctk.CTkFrame(container)
table_frame.pack(fill="both", expand=True, side="top")
# УБРАЛИ "Пароль" из списка колонок, чтобы не путать пользователя
self.tree = ttk.Treeview(table_frame, columns=("ID", "Логин", "ФИО", "Роль"), show="headings", style="mystyle.Treeview")
self.tree.heading("ID", text="ID")
self.tree.heading("Логин", text="Логин")
self.tree.heading("ФИО", text="ФИО")
self.tree.heading("Роль", text="Роль")
self.tree.column("ID", width=40)
self.tree.column("Логин", width=100)
self.tree.column("ФИО", width=180)
self.tree.column("Роль", width=80)
self.tree.pack(side="left", fill="both", expand=True)
scrollbar = ctk.CTkScrollbar(table_frame, command=self.tree.yview)
scrollbar.pack(side="right", fill="y")
self.tree.configure(yscrollcommand=scrollbar.set)
form_frame = ctk.CTkFrame(container)
form_frame.pack(fill="x", pady=(10, 0))
# === ФОРМА ВВОДА (исправлены индексы grid) ===
self.login_entry = ctk.CTkEntry(form_frame, placeholder_text="Логин", width=100)
self.login_entry.grid(row=0, column=0, padx=5, pady=5)
self.fio_entry = ctk.CTkEntry(form_frame, placeholder_text="ФИО", width=180)
self.fio_entry.grid(row=0, column=1, padx=5, pady=5)
# Пароль ставим после ФИО (столбец 2)
self.pass_entry = ctk.CTkEntry(form_frame, placeholder_text="Пароль", width=120, show="*")
self.pass_entry.grid(row=0, column=2, padx=5, pady=5)
# Роль сдвигаем вправо (столбец 3), чтобы форма была ровной
self.role_cb = ctk.CTkComboBox(form_frame, values=["Админ", "Эксперт", "Пользователь"], width=100)
self.role_cb.grid(row=0, column=3, padx=5, pady=5)
self.role_cb.set("Эксперт")
btn_frame = ctk.CTkFrame(container)
btn_frame.pack(pady=10)
add_btn = ctk.CTkButton(btn_frame, text="Добавить", command=self.add_user)
add_btn.pack(side="left", padx=5)
edit_btn = ctk.CTkButton(btn_frame, text="Изменить", command=self.edit_user)
edit_btn.pack(side="left", padx=5)
del_btn = ctk.CTkButton(btn_frame, text="Удалить", command=self.delete_user, fg_color="#d9534f", hover_color="#c9302c")
del_btn.pack(side="left", padx=5)
# Загрузка данных при инициализации
self.load_users()
def load_users(self):
self.tree.delete(*self.tree.get_children())
# === ИСПРАВЛЕННЫЙ ПУТЬ ===
db_path = Path(r"D:\\dukora_V1_31_07_2026\\db") / "users.db"
try:
conn = sqlite3.connect(db_path)
# ... остальной код ...
cursor = conn.cursor()
# Создаем базу, если её нет
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
login TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
fio TEXT NOT NULL,
role TEXT NOT NULL
)
''')
conn.commit()
# Забираем данные, сортируя их по ID (чтобы порядок был стабильным)
cursor.execute('SELECT id, login, fio, role FROM users ORDER BY id')
rows = cursor.fetchall()
for index, row in enumerate(rows, start=1):
# Вместо технического ID из БД (row[0]) вставляем визуальный номер (index)
# Технический ID сохраняем внутри самого элемента Treeview через tags
self.tree.insert("", "end", values=(index, row[1], row[2], row[3]), tags=(str(row[0]),))
except Exception as e:
messagebox.showerror("Ошибка БД", f"Не удалось загрузить пользователей:\\n{e}")
finally:
if 'conn' in locals():
conn.close()
def get_form_data(self):
return {
"login": self.login_entry.get(),
"fio": self.fio_entry.get(),
"password": self.pass_entry.get(),
"role": self.role_cb.get()
}
def clear_form(self):
self.login_entry.delete(0, "end")
self.fio_entry.delete(0, "end")
self.pass_entry.delete(0, "end")
self.role_cb.set("Эксперт")
def add_user(self):
data = self.get_form_data()
if not all([data["login"], data["fio"], data["password"]]):
messagebox.showwarning("Ошибка", "Заполните Логин, ФИО и Пароль")
return
password_hash = sha256(data["password"].encode()).hexdigest()
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO users (login, password_hash, fio, role)
VALUES (?, ?, ?, ?)
''', (data["login"], password_hash, data["fio"], data["role"]))
conn.commit()
messagebox.showinfo("Успех", f"Пользователь {data['login']} добавлен")
self.clear_form()
self.load_users()
except sqlite3.IntegrityError:
messagebox.showwarning("Ошибка", "Пользователь с таким логином уже существует.")
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось сохранить:\n{e}")
finally:
if 'conn' in locals():
conn.close()
def _get_selected_db_id(self):
"""Возвращает настоящий ID пользователя из базы для выбранной строки"""
selected = self.tree.selection()
if not selected:
return None
# Достаём технический ID, который мы сохранили в теги при создании строки
item = self.tree.item(selected)
db_id_str = item['tags'][0]
return int(db_id_str)
def delete_user(self):
db_id = self._get_selected_db_id()
if not db_id:
messagebox.showwarning("Ошибка", "Выберите строку")
return
# Для сообщения пользователю берем логин из видимых ячеек
login = self.tree.item(self.tree.selection())['values'][1]
db_path = Path(__file__).resolve().parents[2] / "db" / "users.db"
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM users WHERE id=?", (db_id,))
conn.commit()
messagebox.showinfo("Успех", f"Пользователь '{login}' удален")
self.clear_form()
self.load_users() # <--- После этой строки вызовется наш новый enumeratе и всё красиво пересчитается
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось удалить:\n{e}")
finally:
if 'conn' in locals():
conn.close()
def edit_user(self):
db_id = self._get_selected_db_id()
if not db_id:
messagebox.showwarning("Ошибка", "Выберите строку")
return
# Получаем текущие данные из скрытого ID
db_path = Path(__file__).resolve().parents[2] / "db" / "users.db"
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('SELECT login, fio, role FROM users WHERE id=?', (db_id,))
user_data = cursor.fetchone()
if user_data:
# Заполняем форму реальными данными для редактирования
self.login_entry.delete(0, "end")
self.login_entry.insert(0, user_data[0])
self.fio_entry.delete(0, "end")
self.fio_entry.insert(0, user_data[1])
self.role_cb.set(user_data[2])
# Поле пароля оставляем пустым при редактировании (если пароль менять не надо)
self.pass_entry.delete(0, "end")
# Можно заблокировать поле логина, чтобы его нельзя было сменить mid-edit
self.login_entry.configure(state="disabled")
except Exception as e:
messagebox.showerror("Ошибка", f"Не удалось прочитать данные:\n{e}")
finally:
if 'conn' in locals():
conn.close()