Загрузка данных
# -*- coding: utf-8 -*-
"""
Модуль управления учетными записями пользователей.
Реализует функционал для роли 'Администратор'.
Работает с базой db/users.db.
"""
import hashlib
import os
import sqlite3
import tkinter as tk
import uuid
from datetime import datetime
from tkinter import filedialog, messagebox, ttk
from modules.utils.window_behavior import LevelTwoBehavior
def center_on_parent(child_window):
child_window.update_idletasks()
window_width = child_window.winfo_reqwidth()
window_height = child_window.winfo_reqheight()
if child_window.master is not None:
parent = child_window.master
x = parent.winfo_rootx() + (parent.winfo_width() // 2) - (window_width // 2)
y = parent.winfo_rooty() + (parent.winfo_height() // 2) - (window_height // 2)
else:
screen_width = child_window.winfo_screenwidth()
screen_height = child_window.winfo_screenheight()
x = (screen_width // 2) - (window_width // 2)
y = (screen_height // 2) - (window_height // 2)
child_window.geometry(f"+{x}+{y}")
class AddUserDialog(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.title("Добавление пользователя")
self.result = None
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.resizable(False, False)
self.geometry("465x365")
frame = ttk.Frame(self, padding="20")
frame.pack(expand=True, fill="both")
# Логин
ttk.Label(frame, text="Логин:").grid(row=0, column=0, sticky="w", pady=5)
self.entry_username = ttk.Entry(frame)
self.entry_username.grid(row=0, column=1, padx=10, pady=5, sticky="ew")
# Роль
ttk.Label(frame, text="Роль:").grid(row=1, column=0, sticky="w", pady=5)
self.combobox_role = ttk.Combobox(
frame, values=["Администратор", "Эксперт", "Пользователь"]
)
self.combobox_role.grid(row=1, column=1, padx=10, pady=5, sticky="ew")
# Пароль
ttk.Label(frame, text="Пароль:").grid(row=2, column=0, sticky="w", pady=5)
self.entry_password = ttk.Entry(frame, show="*")
self.entry_password.grid(row=2, column=1, padx=10, pady=5, sticky="ew")
# Кнопки
btn_frame = ttk.Frame(frame)
btn_frame.grid(row=3, columnspan=2, pady=20)
ttk.Button(btn_frame, text="OK", command=self.ok).pack(side="left", padx=10)
ttk.Button(btn_frame, text="Отмена", command=self.cancel).pack(side="right", padx=10)
for i in range(2):
frame.columnconfigure(i, weight=1)
def ok(self):
username = self.entry_username.get().strip()
role = self.combobox_role.get().strip()
password = self.entry_password.get()
if username and role and password:
salt = uuid.uuid4().hex
hashed_password = hashlib.sha256((password + salt).encode()).hexdigest()
self.result = (username, role, hashed_password, salt)
self.destroy()
else:
messagebox.showwarning("Ошибка", "Все поля обязательны для заполнения!")
def cancel(self):
self.result = None
self.destroy()
class UsersWindow(tk.Toplevel):
def __init__(self, parent, main_app_reference=None):
super().__init__(parent)
self.parent = parent
self.main_app = main_app_reference
self.title("Пользователи")
self.geometry("465x365")
behavior = LevelTwoBehavior()
behavior.setup_level_two(parent)
self.build_ui()
def build_ui(self):
container = ttk.Frame(self)
container.pack(fill="both", expand=True, padx=10, pady=10)
left_panel = ttk.Frame(container)
left_panel.pack(side="left", fill="y", padx=(0, 10))
right_panel = ttk.Frame(container)
right_pack = right_panel.pack(side="right", fill="both", expand=True)
# Кнопки действий
actions = [
("Добавить пользователя", self.add_user),
("Сменить пользователя", self.change_user),
("Удалить пользователя", self.remove_user)
]
for text, cmd in actions:
ttk.Button(left_panel, text=text, command=cmd).pack(fill="x", pady=5)
# Таблица
columns = ("ID", "Логин", "Роль")
self.users_tree = ttk.Treeview(right_panel, columns=columns, show="headings")
for col in columns:
self.users_tree.heading(col, text=col)
self.users_tree.column(col, anchor="center", width=100)
self.users_tree.pack(fill="both", expand=True)
self.load_users()
def load_users(self):
try:
conn = sqlite3.connect("users.db")
cursor = conn.cursor()
cursor.execute("SELECT id, username, role FROM users ORDER BY id ASC;")
rows = cursor.fetchall()
for i in self.users_tree.get_children():
self.users_tree.delete(i)
for row in rows:
self.users_tree.insert("", "end", values=row)
conn.close()
except Exception as e:
messagebox.showerror("DB Error", f"Не удалось загрузить базу данных.\n{e}")
def add_user(self):
dialog = AddUserDialog(self)
self.wait_window(dialog)
if dialog.result:
username, role, pwd_hash, salt = dialog.result
with sqlite3.connect("users.db") as conn:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO users (username, role, password_hash, salt) VALUES (?,?,?,?)",
(username, role, pwd_hash, salt)
)
conn.commit()
self.load_users()
def change_user(self):
"""
Открывает диалоговое окно для смены/выбора пользователя.
После успешного входа закрывает само окно управления пользователями.
"""
try:
# Локальный импорт: сработает только при нажатии кнопки
from gui.login_dialog import LoginDialog
except ImportError as e:
messagebox.showerror("Ошибка", f"Не найден модуль авторизации.\n{e}")
return
login_dialog = LoginDialog(parent=self)
self.wait_window(login_dialog)
if hasattr(login_dialog, 'successful') and login_dialog.successful:
messagebox.showinfo("Успех", "Пользователь сменен.")
if self.main_app:
try:
# Разблокируем главное окно через тот самый enable_app
from modules.utils.window_behavior import enable_app
enable_app(self.main_app)
except Exception:
pass
self.destroy()
def remove_user(self):
selected_item = self.users_tree.selection()
if not selected_item:
messagebox.showwarning("Ошибка", "Выберите пользователя для удаления!")
return
user_id = int(self.users_tree.item(selected_item)["values"][0])
answer = messagebox.askyesno(
"Подтверждение", f"Удалить пользователя с ID={user_id}?\nВнимание: ID будут пересчитаны."
)
if not answer:
return
try:
with sqlite3.connect("users.db") as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM users WHERE id=?", (user_id,))
# Перенумерация
cursor.execute("SELECT * FROM users ORDER BY id ASC;")
rows = cursor.fetchall()
new_id = 1
for row in rows:
old_id = row[0]
cursor.execute("UPDATE users SET id=? WHERE id=?", (new_id, old_id))
new_id += 1
cursor.execute("DELETE FROM sqlite_sequence WHERE name='users';")
conn.commit()
self.load_users()
except Exception as e:
messagebox.showerror("Error", str(e))