Загрузка данных
import mysql.connector
from mysql.connector import Error
import tkinter as tk
from tkinter import ttk, messagebox, Toplevel
import re
from datetime import datetime
# ==================== КОНФИГУРАЦИЯ БАЗЫ ДАННЫХ ====================
DB_CONFIG = {
'host': 'localhost',
'user': 'root', # измените при необходимости
'password': '', # ваш пароль MySQL
'database': 'autoservice',
'port': 3306
}
# ==================== ИНИЦИАЛИЗАЦИЯ БАЗЫ ДАННЫХ ====================
def init_db():
"""Создаёт таблицы, если они не существуют"""
connection = None
try:
connection = mysql.connector.connect(
host=DB_CONFIG['host'],
user=DB_CONFIG['user'],
password=DB_CONFIG['password'],
port=DB_CONFIG['port']
)
cursor = connection.cursor()
# Создаём БД, если её нет
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {DB_CONFIG['database']}")
cursor.execute(f"USE {DB_CONFIG['database']}")
# Таблица владельцев
cursor.execute("""
CREATE TABLE IF NOT EXISTS owners (
id INT AUTO_INCREMENT PRIMARY KEY,
full_name VARCHAR(255) NOT NULL,
phone VARCHAR(50),
address TEXT
)
""")
# Таблица автомобилей
cursor.execute("""
CREATE TABLE IF NOT EXISTS cars (
id INT AUTO_INCREMENT PRIMARY KEY,
license_plate VARCHAR(20) NOT NULL UNIQUE,
brand VARCHAR(100) NOT NULL,
model VARCHAR(100) NOT NULL,
year INT,
color VARCHAR(50),
owner_id INT,
FOREIGN KEY (owner_id) REFERENCES owners(id) ON DELETE SET NULL
)
""")
connection.commit()
except Error as e:
messagebox.showerror("Ошибка БД", f"Не удалось инициализировать БД:\n{e}")
return False
finally:
if connection and connection.is_connected():
cursor.close()
connection.close()
return True
# ==================== ФУНКЦИИ РАБОТЫ С БАЗОЙ ДАННЫХ ====================
def get_db_connection():
"""Установка соединения с базой данных"""
try:
connection = mysql.connector.connect(**DB_CONFIG)
return connection
except Error as e:
messagebox.showerror("Ошибка БД", f"Не удалось подключиться к БД:\n{e}")
return None
def load_cars_data(tree):
"""Загрузка данных из БД в Treeview (с хранением ID автомобиля)"""
for row in tree.get_children():
tree.delete(row)
connection = get_db_connection()
if not connection:
return
cursor = connection.cursor()
query = """
SELECT c.id, c.license_plate, c.brand, c.model, c.year, c.color,
o.full_name, o.phone, o.address
FROM cars c
LEFT JOIN owners o ON c.owner_id = o.id
ORDER BY c.id
"""
try:
cursor.execute(query)
rows = cursor.fetchall()
for row in rows:
# Вставляем ID как скрытое значение (первый столбец)
tree.insert('', tk.END, values=row, iid=row[0])
except Error as e:
messagebox.showerror("Ошибка", f"Ошибка загрузки данных:\n{e}")
finally:
cursor.close()
connection.close()
def find_owner_by_name(full_name):
"""Поиск владельца по ФИО (возвращает id)"""
connection = get_db_connection()
if not connection:
return None
cursor = connection.cursor()
query = "SELECT id FROM owners WHERE full_name = %s"
try:
cursor.execute(query, (full_name,))
result = cursor.fetchone()
return result[0] if result else None
except Error as e:
print(f"Ошибка поиска владельца: {e}")
return None
finally:
cursor.close()
connection.close()
def add_owner(full_name, phone, address):
"""Добавление нового владельца в БД (возвращает id)"""
connection = get_db_connection()
if not connection:
return None
cursor = connection.cursor()
query = "INSERT INTO owners (full_name, phone, address) VALUES (%s, %s, %s)"
try:
cursor.execute(query, (full_name, phone, address))
connection.commit()
return cursor.lastrowid
except Error as e:
messagebox.showerror("Ошибка", f"Не удалось добавить владельца:\n{e}")
return None
finally:
cursor.close()
connection.close()
def update_owner(owner_id, full_name, phone, address):
"""Обновление данных владельца"""
connection = get_db_connection()
if not connection:
return False
cursor = connection.cursor()
query = "UPDATE owners SET full_name = %s, phone = %s, address = %s WHERE id = %s"
try:
cursor.execute(query, (full_name, phone, address, owner_id))
connection.commit()
return True
except Error as e:
messagebox.showerror("Ошибка", f"Не удалось обновить владельца:\n{e}")
return False
finally:
cursor.close()
connection.close()
def add_car(license_plate, brand, model, year, color, owner_id):
"""Добавление нового автомобиля"""
connection = get_db_connection()
if not connection:
return False
cursor = connection.cursor()
query = """
INSERT INTO cars (license_plate, brand, model, year, color, owner_id)
VALUES (%s, %s, %s, %s, %s, %s)
"""
try:
cursor.execute(query, (license_plate, brand, model, year, color, owner_id))
connection.commit()
return True
except Error as e:
if "Duplicate" in str(e):
messagebox.showerror("Ошибка", f"Автомобиль с госномером '{license_plate}' уже существует!")
else:
messagebox.showerror("Ошибка", f"Не удалось добавить автомобиль:\n{e}")
return False
finally:
cursor.close()
connection.close()
def update_car(car_id, license_plate, brand, model, year, color, owner_id):
"""Обновление данных автомобиля"""
connection = get_db_connection()
if not connection:
return False
cursor = connection.cursor()
query = """
UPDATE cars
SET license_plate = %s, brand = %s, model = %s, year = %s, color = %s, owner_id = %s
WHERE id = %s
"""
try:
cursor.execute(query, (license_plate, brand, model, year, color, owner_id, car_id))
connection.commit()
return True
except Error as e:
if "Duplicate" in str(e):
messagebox.showerror("Ошибка", f"Автомобиль с госномером '{license_plate}' уже существует!")
else:
messagebox.showerror("Ошибка", f"Не удалось обновить автомобиль:\n{e}")
return False
finally:
cursor.close()
connection.close()
def delete_car(car_id):
"""Удаление автомобиля по ID"""
connection = get_db_connection()
if not connection:
return False
cursor = connection.cursor()
query = "DELETE FROM cars WHERE id = %s"
try:
cursor.execute(query, (car_id,))
connection.commit()
return True
except Error as e:
messagebox.showerror("Ошибка", f"Не удалось удалить автомобиль:\n{e}")
return False
finally:
cursor.close()
connection.close()
def get_owner_data(owner_id):
"""Получить полные данные владельца по ID"""
if not owner_id:
return None
connection = get_db_connection()
if not connection:
return None
cursor = connection.cursor()
query = "SELECT full_name, phone, address FROM owners WHERE id = %s"
try:
cursor.execute(query, (owner_id,))
result = cursor.fetchone()
return result if result else None
except Error as e:
print(f"Ошибка получения данных владельца: {e}")
return None
finally:
cursor.close()
connection.close()
# ==================== ВАЛИДАЦИЯ ====================
def validate_fields(full_name, license_plate, brand, model):
if not full_name.strip():
messagebox.showwarning("Внимание", "Поле 'ФИО владельца' обязательно для заполнения!")
return False
if not license_plate.strip():
messagebox.showwarning("Внимание", "Поле 'Госномер' обязательно для заполнения!")
return False
if not brand.strip():
messagebox.showwarning("Внимание", "Поле 'Марка' обязательно для заполнения!")
return False
if not model.strip():
messagebox.showwarning("Внимание", "Поле 'Модель' обязательно для заполнения!")
return False
return True
def validate_phone(phone):
"""Улучшенная проверка телефона (только цифры, +, пробелы, дефисы, скобки)"""
if phone and not re.fullmatch(r'[\d\s\+\(\)\-]{10,20}', phone):
messagebox.showwarning("Внимание", "Телефон должен содержать 10-20 символов (цифры, +, -, пробелы, скобки)")
return False
return True
def validate_year(year):
if year:
try:
year_int = int(year)
current_year = datetime.now().year
if year_int < 1900 or year_int > current_year + 1:
messagebox.showwarning("Внимание", f"Год выпуска должен быть в диапазоне 1900-{current_year+1}")
return False
except ValueError:
messagebox.showwarning("Внимание", "Год выпуска должен быть числом!")
return False
return True
# ==================== ОКНО ДОБАВЛЕНИЯ/РЕДАКТИРОВАНИЯ ====================
class AddEditWindow:
def __init__(self, parent, tree, edit_mode=False, car_id=None):
self.parent = parent
self.tree = tree
self.edit_mode = edit_mode
self.car_id = car_id # ID автомобиля (если редактирование)
self.window = Toplevel(parent)
title = "Редактирование автомобиля" if edit_mode else "Добавление автомобиля"
self.window.title(title)
self.window.geometry("500x550")
self.window.resizable(False, False)
self.window.grab_set()
self.create_widgets()
if edit_mode and car_id:
self.load_data_for_edit()
def create_widgets(self):
main_frame = ttk.Frame(self.window, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# Данные владельца
ttk.Label(main_frame, text="ДАННЫЕ ВЛАДЕЛЬЦА", font=('Arial', 10, 'bold'))\
.grid(row=0, column=0, columnspan=2, pady=(0,10), sticky='w')
ttk.Label(main_frame, text="ФИО владельца:*").grid(row=1, column=0, pady=5, sticky='e')
self.owner_name_entry = ttk.Entry(main_frame, width=30)
self.owner_name_entry.grid(row=1, column=1, pady=5, sticky='w')
ttk.Label(main_frame, text="Телефон:").grid(row=2, column=0, pady=5, sticky='e')
self.phone_entry = ttk.Entry(main_frame, width=30)
self.phone_entry.grid(row=2, column=1, pady=5, sticky='w')
ttk.Label(main_frame, text="Адрес:").grid(row=3, column=0, pady=5, sticky='e')
self.address_entry = ttk.Entry(main_frame, width=30)
self.address_entry.grid(row=3, column=1, pady=5, sticky='w')
ttk.Separator(main_frame, orient='horizontal').grid(row=4, column=0, columnspan=2, pady=15, sticky='ew')
# Данные автомобиля
ttk.Label(main_frame, text="ДАННЫЕ АВТОМОБИЛЯ", font=('Arial', 10, 'bold'))\
.grid(row=5, column=0, columnspan=2, pady=(0,10), sticky='w')
ttk.Label(main_frame, text="Госномер:*").grid(row=6, column=0, pady=5, sticky='e')
self.plate_entry = ttk.Entry(main_frame, width=30)
self.plate_entry.grid(row=6, column=1, pady=5, sticky='w')
ttk.Label(main_frame, text="Марка:*").grid(row=7, column=0, pady=5, sticky='e')
self.brand_entry = ttk.Entry(main_frame, width=30)
self.brand_entry.grid(row=7, column=1, pady=5, sticky='w')
ttk.Label(main_frame, text="Модель:*").grid(row=8, column=0, pady=5, sticky='e')
self.model_entry = ttk.Entry(main_frame, width=30)
self.model_entry.grid(row=8, column=1, pady=5, sticky='w')
ttk.Label(main_frame, text="Год выпуска:").grid(row=9, column=0, pady=5, sticky='e')
self.year_entry = ttk.Entry(main_frame, width=30)
self.year_entry.grid(row=9, column=1, pady=5, sticky='w')
ttk.Label(main_frame, text="Цвет:").grid(row=10, column=0, pady=5, sticky='e')
self.color_entry = ttk.Entry(main_frame, width=30)
self.color_entry.grid(row=10, column=1, pady=5, sticky='w')
# Кнопки
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=11, column=0, columnspan=2, pady=20)
save_btn = ttk.Button(button_frame, text="Сохранить", command=self.save_data)
save_btn.pack(side=tk.LEFT, padx=5)
cancel_btn = ttk.Button(button_frame, text="Отмена", command=self.window.destroy)
cancel_btn.pack(side=tk.LEFT, padx=5)
ttk.Label(main_frame, text="* - обязательные поля", font=('Arial', 8))\
.grid(row=12, column=0, columnspan=2, pady=5)
def load_data_for_edit(self):
"""Загрузка данных автомобиля и владельца для редактирования"""
connection = get_db_connection()
if not connection:
return
cursor = connection.cursor()
# Получаем полные данные автомобиля
query = """
SELECT c.license_plate, c.brand, c.model, c.year, c.color,
c.owner_id, o.full_name, o.phone, o.address
FROM cars c
LEFT JOIN owners o ON c.owner_id = o.id
WHERE c.id = %s
"""
try:
cursor.execute(query, (self.car_id,))
row = cursor.fetchone()
if row:
self.plate_entry.insert(0, row[0] or '')
self.brand_entry.insert(0, row[1] or '')
self.model_entry.insert(0, row[2] or '')
self.year_entry.insert(0, str(row[3]) if row[3] else '')
self.color_entry.insert(0, row[4] or '')
# Данные владельца
self.owner_name_entry.insert(0, row[6] or '')
self.phone_entry.insert(0, row[7] or '')
self.address_entry.insert(0, row[8] or '')
except Error as e:
messagebox.showerror("Ошибка", f"Не удалось загрузить данные:\n{e}")
finally:
cursor.close()
connection.close()
def save_data(self):
# Сбор данных
owner_name = self.owner_name_entry.get().strip()
phone = self.phone_entry.get().strip()
address = self.address_entry.get().strip()
license_plate = self.plate_entry.get().strip().upper()
brand = self.brand_entry.get().strip()
model = self.model_entry.get().strip()
year_str = self.year_entry.get().strip()
color = self.color_entry.get().strip()
# Валидация
if not validate_fields(owner_name, license_plate, brand, model):
return
if not validate_phone(phone):
return
if not validate_year(year_str):
return
year = int(year_str) if year_str else None
if self.edit_mode:
# Режим редактирования
# Определяем owner_id (может быть NULL)
owner_id = None
# Если владелец указан, ищем или создаём
if owner_name:
owner_id = find_owner_by_name(owner_name)
if not owner_id:
owner_id = add_owner(owner_name, phone, address)
if not owner_id:
return
else:
# Обновляем данные существующего владельца
update_owner(owner_id, owner_name, phone, address)
# Обновляем автомобиль
if update_car(self.car_id, license_plate, brand, model, year, color, owner_id):
messagebox.showinfo("Успех", "Данные успешно обновлены!")
load_cars_data(self.tree)
self.window.destroy()
else:
# Режим добавления
owner_id = None
if owner_name:
owner_id = find_owner_by_name(owner_name)
if not owner_id:
owner_id = add_owner(owner_name, phone, address)
if not owner_id:
return
# Если владелец найден, НЕ обновляем его данные (оставляем как есть)
# При желании изменить данные владельца нужно использовать редактирование
if add_car(license_plate, brand, model, year, color, owner_id):
messagebox.showinfo("Успех", "Автомобиль успешно добавлен!")
load_cars_data(self.tree)
self.window.destroy()
# ==================== ГЛАВНОЕ ОКНО ====================
class MainApplication:
def __init__(self, root):
self.root = root
self.root.title("Автосервис - Учёт автомобилей")
self.root.geometry("1200x500")
self.root.resizable(True, True)
self.create_widgets()
self.load_data()
def create_widgets(self):
# Панель кнопок
button_frame = ttk.Frame(self.root, padding="10")
button_frame.pack(fill=tk.X)
ttk.Button(button_frame, text="Добавить авто", command=self.open_add_window).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Редактировать", command=self.open_edit_window).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Удалить", command=self.delete_car).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Обновить", command=self.load_data).pack(side=tk.LEFT, padx=5)
# Таблица
tree_frame = ttk.Frame(self.root, padding="10")
tree_frame.pack(fill=tk.BOTH, expand=True)
scroll_y = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL)
scroll_x = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL)
columns = ('id', 'license_plate', 'brand', 'model', 'year', 'color', 'owner_name', 'phone', 'address')
self.tree = ttk.Treeview(tree_frame, columns=columns, show='headings',
yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
# Настройка заголовков
self.tree.heading('id', text='ID')
self.tree.heading('license_plate', text='Госномер')
self.tree.heading('brand', text='Марка')
self.tree.heading('model', text='Модель')
self.tree.heading('year', text='Год')
self.tree.heading('color', text='Цвет')
self.tree.heading('owner_name', text='Владелец (ФИО)')
self.tree.heading('phone', text='Телефон')
self.tree.heading('address', text='Адрес')
# Ширина колонок
self.tree.column('id', width=40, anchor='center')
self.tree.column('license_plate', width=100)
self.tree.column('brand', width=100)
self.tree.column('model', width=100)
self.tree.column('year', width=60, anchor='center')
self.tree.column('color', width=80)
self.tree.column('owner_name', width=200)
self.tree.column('phone', width=120)
self.tree.column('address', width=200)
scroll_y.config(command=self.tree.yview)
scroll_x.config(command=self.tree.xview)
self.tree.grid(row=0, column=0, sticky='nsew')
scroll_y.grid(row=0, column=1, sticky='ns')
scroll_x.grid(row=1, column=0, sticky='ew')
tree_frame.grid_rowconfigure(0, weight=1)
tree_frame.grid_columnconfigure(0, weight=1)
def load_data(self):
load_cars_data(self.tree)
def open_add_window(self):
AddEditWindow(self.root, self.tree, edit_mode=False)
def open_edit_window(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("Предупреждение", "Выберите запись для редактирования")
return
car_id = int(selected[0]) # ID хранится как iid
AddEditWindow(self.root, self.tree, edit_mode=True, car_id=car_id)
def delete_car(self):
selected = self.tree.selection()
if not selected:
messagebox.showwarning("Предупреждение", "Выберите запись для удаления")
return
car_id = int(selected[0])
values = self.tree.item(selected[0])['values']
license_plate = values[1] # госномер
brand = values[2]
model = values[3]
result = messagebox.askyesno(
"Подтверждение удаления",
f"Вы уверены, что хотите удалить автомобиль {license_plate} ({brand} {model})?\n"
"Владелец останется в базе, если у него есть другие автомобили."
)
if result:
if delete_car(car_id):
messagebox.showinfo("Успех", "Автомобиль успешно удалён!")
self.load_data()
else:
messagebox.showerror("Ошибка", "Не удалось удалить автомобиль!")
# ==================== ТОЧКА ВХОДА ====================
if __name__ == "__main__":
if init_db():
root = tk.Tk()
app = MainApplication(root)
root.mainloop()
else:
print("Ошибка инициализации базы данных. Приложение закрыто.")