mobiles_dataset/scripts/exe/mobile_devices.py
2025-06-07 12:26:58 +03:00

938 lines
No EOL
40 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# main.py
import sys
import logging
from PyQt6.QtWidgets import QApplication
from PyQt6.QtCore import Qt
# ui/main_window.py
import sys
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QPushButton, QTabWidget,
QLabel, QLineEdit, QComboBox, QSpinBox, QMessageBox,
QDialog, QFormLayout, QDialogButtonBox, QHeaderView,
QToolBar, QStatusBar, QGroupBox, QTextEdit, QInputDialog
)
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
from PyQt6.QtGui import QAction, QIcon, QFont
from typing import Optional, Dict, Any
import logging
# db/database.py
import psycopg2
from psycopg2.extras import RealDictCursor
from typing import List, Dict, Any, Optional
import logging
from contextlib import contextmanager
logger = logging.getLogger(__name__)
CURRENCY_MAP = {
'Pakistan': ('PKR', ''),
'India': ('INR', ''),
'China': ('CNY', '¥'),
'USA': ('USD', '$'),
'Dubai': ('AED', 'د.إ')
}
class Database:
"""
Класс для управления подключением к PostgreSQL и выполнения операций
Использует паттерн Singleton для единственного экземпляра подключения
"""
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host='localhost', port=5432, database='mobile_devices_db',
user='admin', password='password'):
if not hasattr(self, 'initialized'):
self.connection_params = {
'host': host,
'port': port,
'database': database,
'user': user,
'password': password
}
self.connection = None
self.initialized = True
def connect(self):
"""Установка соединения с БД"""
try:
self.connection = psycopg2.connect(**self.connection_params)
logger.info("✅ Подключение к БД установлено")
return True
except Exception as e:
logger.error(f"❌ Ошибка подключения к БД: {e}")
return False
def disconnect(self):
"""Закрытие соединения с БД"""
if self.connection:
self.connection.close()
logger.info("🔒 Соединение с БД закрыто")
@contextmanager
def get_cursor(self, dict_cursor=True):
"""
Контекстный менеджер для безопасной работы с курсором
"""
cursor_factory = RealDictCursor if dict_cursor else None
cursor = self.connection.cursor(cursor_factory=cursor_factory)
try:
yield cursor
self.connection.commit()
except Exception as e:
self.connection.rollback()
logger.error(f"❌ Ошибка выполнения запроса: {e}")
raise
finally:
cursor.close()
# === CRUD операции для Companies ===
def get_all_companies(self) -> List[Dict[str, Any]]:
"""Получение всех компаний"""
with self.get_cursor() as cursor:
cursor.execute("""
SELECT c.company_id, c.company_name, COUNT(m.model_id) as models_count
FROM companies c
LEFT JOIN models m ON c.company_id = m.company_id
GROUP BY c.company_id, c.company_name
ORDER BY c.company_name
""")
return cursor.fetchall()
def add_company(self, company_name: str) -> int:
"""Добавление новой компании"""
with self.get_cursor() as cursor:
cursor.execute(
"INSERT INTO companies (company_name) VALUES (%s) RETURNING company_id",
(company_name,)
)
return cursor.fetchone()['company_id']
def update_company(self, company_id: int, company_name: str):
"""Обновление названия компании"""
with self.get_cursor() as cursor:
cursor.execute(
"UPDATE companies SET company_name = %s WHERE company_id = %s",
(company_name, company_id)
)
def delete_company(self, company_id: int):
"""Удаление компании (каскадно удалит все модели)"""
with self.get_cursor() as cursor:
cursor.execute("DELETE FROM companies WHERE company_id = %s", (company_id,))
# === CRUD операции для Models ===
def get_all_models(self, company_id: Optional[int] = None) -> List[Dict[str, Any]]:
"""Получение всех моделей (опционально по компании)"""
query = """
SELECT
m.model_id, m.model_name, c.company_name,
m.mobile_weight, m.ram, m.front_camera,
m.back_camera, pr.processor_name, m.battery_capacity,
m.screen_size, m.launched_year,
COUNT(DISTINCT p.region_id) as price_regions
FROM models m
JOIN companies c ON m.company_id = c.company_id
LEFT JOIN processors pr ON m.processor_id = pr.processor_id
LEFT JOIN prices p ON m.model_id = p.model_id
"""
params = []
if company_id:
query += " WHERE m.company_id = %s"
params.append(company_id)
query += " GROUP BY m.model_id, c.company_name, pr.processor_name ORDER BY c.company_name, m.model_name"
with self.get_cursor() as cursor:
cursor.execute(query, params)
return cursor.fetchall()
def get_model_by_id(self, model_id: int) -> Dict[str, Any]:
"""Получение модели по ID"""
with self.get_cursor() as cursor:
cursor.execute("""
SELECT m.*, c.company_name, pr.processor_name
FROM models m
JOIN companies c ON m.company_id = c.company_id
LEFT JOIN processors pr ON m.processor_id = pr.processor_id
WHERE m.model_id = %s
""", (model_id,))
return cursor.fetchone()
def add_model(self, model_data: Dict[str, Any]) -> int:
"""Добавление новой модели"""
# Сначала получаем или создаем процессор
processor_id = None
if model_data.get('processor_name'):
processor_id = self.get_or_create_processor(model_data['processor_name'])
with self.get_cursor() as cursor:
cursor.execute("""
INSERT INTO models
(model_name, company_id, processor_id, mobile_weight,
ram, front_camera, back_camera, battery_capacity,
screen_size, launched_year)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING model_id
""", (
model_data['model_name'],
model_data['company_id'],
processor_id,
model_data.get('mobile_weight'),
model_data.get('ram'),
model_data.get('front_camera'),
model_data.get('back_camera'),
model_data.get('battery_capacity'),
model_data.get('screen_size'),
model_data.get('launched_year')
))
return cursor.fetchone()['model_id']
def update_model(self, model_id: int, model_data: Dict[str, Any]):
"""Обновление модели"""
# Получаем или создаем процессор
processor_id = None
if model_data.get('processor_name'):
processor_id = self.get_or_create_processor(model_data['processor_name'])
with self.get_cursor() as cursor:
cursor.execute("""
UPDATE models SET
model_name = %s,
company_id = %s,
processor_id = %s,
mobile_weight = %s,
ram = %s,
front_camera = %s,
back_camera = %s,
battery_capacity = %s,
screen_size = %s,
launched_year = %s
WHERE model_id = %s
""", (
model_data['model_name'],
model_data['company_id'],
processor_id,
model_data.get('mobile_weight'),
model_data.get('ram'),
model_data.get('front_camera'),
model_data.get('back_camera'),
model_data.get('battery_capacity'),
model_data.get('screen_size'),
model_data.get('launched_year'),
model_id
))
def delete_model(self, model_id: int):
"""Удаление модели"""
with self.get_cursor() as cursor:
cursor.execute("DELETE FROM models WHERE model_id = %s", (model_id,))
# === CRUD операции для Prices ===
def get_model_prices(self, model_id: int) -> List[Dict[str, Any]]:
"""Получение всех цен для модели"""
with self.get_cursor() as cursor:
cursor.execute("""
SELECT p.price_id, p.model_id, p.region_id,
r.region_name, p.price, p.currency
FROM prices p
JOIN regions r ON p.region_id = r.region_id
WHERE p.model_id = %s
ORDER BY r.region_name
""", (model_id,))
return cursor.fetchall()
def add_or_update_price(self, model_id: int, region_id: int, price: float):
"""Добавление или обновление цены"""
with self.get_cursor() as cursor:
cursor.execute("""
INSERT INTO prices (model_id, region_id, price)
VALUES (%s, %s, %s)
ON CONFLICT (model_id, region_id)
DO UPDATE SET price = EXCLUDED.price
""", (model_id, region_id, price))
def delete_price(self, price_id: int):
"""Удаление цены"""
with self.get_cursor() as cursor:
cursor.execute("DELETE FROM prices WHERE price_id = %s", (price_id,))
# === Вспомогательные методы ===
def get_or_create_processor(self, processor_name: str) -> int:
"""Получение или создание процессора"""
with self.get_cursor() as cursor:
cursor.execute(
"SELECT processor_id FROM processors WHERE processor_name = %s",
(processor_name,)
)
result = cursor.fetchone()
if result:
return result['processor_id']
else:
cursor.execute(
"INSERT INTO processors (processor_name) VALUES (%s) RETURNING processor_id",
(processor_name,)
)
return cursor.fetchone()['processor_id']
def get_all_regions(self) -> List[Dict[str, Any]]:
"""Получение всех регионов"""
with self.get_cursor() as cursor:
cursor.execute("SELECT * FROM regions ORDER BY region_name")
return cursor.fetchall()
def get_all_processors(self) -> List[Dict[str, Any]]:
"""Получение всех процессоров"""
with self.get_cursor() as cursor:
cursor.execute("SELECT * FROM processors ORDER BY processor_name")
return cursor.fetchall()
# === Методы для аналитики ===
def get_price_statistics(self) -> List[Dict[str, Any]]:
"""Получение статистики цен по регионам"""
with self.get_cursor() as cursor:
cursor.execute("""
SELECT
r.region_name,
COUNT(p.price_id) as models_count,
AVG(p.price) as avg_price,
MIN(p.price) as min_price,
MAX(p.price) as max_price
FROM prices p
JOIN regions r ON p.region_id = r.region_id
GROUP BY r.region_name
ORDER BY avg_price DESC
""")
return cursor.fetchall()
def search_models(self, search_text: str) -> List[Dict[str, Any]]:
"""Поиск моделей по тексту"""
search_pattern = f"%{search_text}%"
with self.get_cursor() as cursor:
cursor.execute("""
SELECT DISTINCT
m.model_id, m.model_name, c.company_name,
m.ram, m.battery_capacity, m.launched_year
FROM models m
JOIN companies c ON m.company_id = c.company_id
WHERE
m.model_name ILIKE %s OR
c.company_name ILIKE %s OR
m.ram ILIKE %s OR
m.battery_capacity ILIKE %s
ORDER BY c.company_name, m.model_name
LIMIT 100
""", (search_pattern, search_pattern, search_pattern, search_pattern))
return cursor.fetchall()
class ModelDialog(QDialog):
"""Диалог для добавления/редактирования модели"""
def __init__(self, parent=None, model_data=None):
super().__init__(parent)
self.model_data = model_data
self.db = Database()
self.init_ui()
def init_ui(self):
self.setWindowTitle("Добавить модель" if not self.model_data else "Редактировать модель")
self.setModal(True)
self.setMinimumWidth(500)
layout = QFormLayout()
# Поля формы
self.company_combo = QComboBox()
companies = self.db.get_all_companies()
for company in companies:
self.company_combo.addItem(company['company_name'], company['company_id'])
self.model_name_edit = QLineEdit()
self.weight_edit = QLineEdit()
self.ram_edit = QLineEdit()
self.front_camera_edit = QLineEdit()
self.back_camera_edit = QLineEdit()
self.processor_edit = QLineEdit()
self.battery_edit = QLineEdit()
self.screen_edit = QLineEdit()
self.year_spin = QSpinBox()
self.year_spin.setRange(2000, 2030)
self.year_spin.setValue(2024)
# Добавляем поля в форму
layout.addRow("Компания:", self.company_combo)
layout.addRow("Название модели:", self.model_name_edit)
layout.addRow("Вес:", self.weight_edit)
layout.addRow("RAM:", self.ram_edit)
layout.addRow("Фронтальная камера:", self.front_camera_edit)
layout.addRow("Основная камера:", self.back_camera_edit)
layout.addRow("Процессор:", self.processor_edit)
layout.addRow("Батарея:", self.battery_edit)
layout.addRow("Размер экрана:", self.screen_edit)
layout.addRow("Год выпуска:", self.year_spin)
# Заполняем данные при редактировании
if self.model_data:
self.model_name_edit.setText(self.model_data.get('model_name', ''))
self.weight_edit.setText(self.model_data.get('mobile_weight', ''))
self.ram_edit.setText(self.model_data.get('ram', ''))
self.front_camera_edit.setText(self.model_data.get('front_camera', ''))
self.back_camera_edit.setText(self.model_data.get('back_camera', ''))
self.processor_edit.setText(self.model_data.get('processor_name', ''))
self.battery_edit.setText(self.model_data.get('battery_capacity', ''))
self.screen_edit.setText(self.model_data.get('screen_size', ''))
if self.model_data.get('launched_year'):
self.year_spin.setValue(self.model_data['launched_year'])
# Устанавливаем компанию
for i in range(self.company_combo.count()):
if self.company_combo.itemData(i) == self.model_data.get('company_id'):
self.company_combo.setCurrentIndex(i)
break
# Кнопки
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addRow(buttons)
self.setLayout(layout)
def get_data(self) -> Dict[str, Any]:
"""Получение данных из формы"""
return {
'company_id': self.company_combo.currentData(),
'model_name': self.model_name_edit.text(),
'mobile_weight': self.weight_edit.text() or None,
'ram': self.ram_edit.text() or None,
'front_camera': self.front_camera_edit.text() or None,
'back_camera': self.back_camera_edit.text() or None,
'processor_name': self.processor_edit.text() or None,
'battery_capacity': self.battery_edit.text() or None,
'screen_size': self.screen_edit.text() or None,
'launched_year': self.year_spin.value()
}
class PriceDialog(QDialog):
"""Диалог для управления ценами модели"""
def __init__(self, parent=None, model_id=None, model_name=""):
super().__init__(parent)
self.model_id = model_id
self.model_name = model_name
self.db = Database()
self.init_ui()
self.load_prices()
def init_ui(self):
self.setWindowTitle(f"Цены для: {self.model_name}")
self.setModal(True)
self.setMinimumSize(600, 400)
layout = QVBoxLayout()
# Таблица цен
self.prices_table = QTableWidget()
self.prices_table.setColumnCount(4)
self.prices_table.setHorizontalHeaderLabels(["Регион", "Цена", "Валюта", "Действия"])
self.prices_table.setSortingEnabled(True)
# Настройка размеров столбцов
header = self.prices_table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
layout.addWidget(self.prices_table)
# Форма добавления цены
add_group = QGroupBox("Добавить/Обновить цену")
add_layout = QHBoxLayout()
self.region_combo = QComboBox()
regions = self.db.get_all_regions()
for region in regions:
self.region_combo.addItem(region['region_name'], region['region_id'])
self.price_edit = QLineEdit()
self.price_edit.setPlaceholderText("Цена")
add_btn = QPushButton("Добавить/Обновить")
add_btn.clicked.connect(self.add_update_price)
add_layout.addWidget(QLabel("Регион:"))
add_layout.addWidget(self.region_combo)
add_layout.addWidget(QLabel("Цена:"))
add_layout.addWidget(self.price_edit)
add_layout.addWidget(add_btn)
add_group.setLayout(add_layout)
layout.addWidget(add_group)
# Кнопка закрытия
close_btn = QPushButton("Закрыть")
close_btn.clicked.connect(self.close)
layout.addWidget(close_btn)
self.setLayout(layout)
def format_price(self, price: float, region_name: str) -> str:
"""Форматирование цены с правильным символом валюты"""
currency_code, currency_symbol = CURRENCY_MAP.get(region_name, ('USD', '$'))
return f"{currency_symbol}{price:,.2f}"
def load_prices(self):
"""Загрузка цен из БД"""
prices = self.db.get_model_prices(self.model_id)
self.prices_table.setRowCount(len(prices))
for row, price_data in enumerate(prices):
# Регион
region_item = QTableWidgetItem(price_data['region_name'])
region_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.prices_table.setItem(row, 0, region_item)
# Цена с правильной валютой
price_str = self.format_price(price_data['price'], price_data['region_name'])
price_item = QTableWidgetItem(price_str)
price_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.prices_table.setItem(row, 1, price_item)
# Код валюты
currency_code, _ = CURRENCY_MAP.get(price_data['region_name'], ('USD', '$'))
currency_item = QTableWidgetItem(currency_code)
currency_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.prices_table.setItem(row, 2, currency_item)
# Кнопка удаления
delete_btn = QPushButton("Удалить")
delete_btn.clicked.connect(lambda checked, pid=price_data['price_id']: self.delete_price(pid))
self.prices_table.setCellWidget(row, 3, delete_btn)
def add_update_price(self):
"""Добавление или обновление цены"""
try:
region_id = self.region_combo.currentData()
price = float(self.price_edit.text())
self.db.add_or_update_price(self.model_id, region_id, price)
self.load_prices()
self.price_edit.clear()
QMessageBox.information(self, "Успех", "Цена успешно обновлена!")
except ValueError:
QMessageBox.warning(self, "Ошибка", "Введите корректную цену!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при обновлении цены: {str(e)}")
def delete_price(self, price_id):
"""Удаление цены"""
reply = QMessageBox.question(
self, "Подтверждение",
"Вы уверены, что хотите удалить эту цену?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
try:
self.db.delete_price(price_id)
self.load_prices()
QMessageBox.information(self, "Успех", "Цена удалена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при удалении: {str(e)}")
class MainWindow(QMainWindow):
"""Главное окно приложения"""
def __init__(self):
super().__init__()
self.db = Database()
self.init_ui()
self.connect_to_db()
def init_ui(self):
self.setWindowTitle("Датасет мобильных устройств")
self.setGeometry(100, 100, 1200, 600)
# Центральный виджет
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Основной layout
layout = QVBoxLayout(central_widget)
# Создаем панель инструментов
self.create_toolbar()
# Создаем вкладки
self.tabs = QTabWidget()
# Вкладка компаний
self.companies_tab = self.create_companies_tab()
self.tabs.addTab(self.companies_tab, "🏢 Компании")
# Вкладка моделей
self.models_tab = self.create_models_tab()
self.tabs.addTab(self.models_tab, "📱 Модели")
# Вкладка аналитики
self.analytics_tab = self.create_analytics_tab()
self.tabs.addTab(self.analytics_tab, "📊 Аналитика")
layout.addWidget(self.tabs)
# Статусная строка
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.status_bar.showMessage("Готов к работе")
def create_toolbar(self):
"""Создание панели инструментов"""
toolbar = QToolBar()
self.addToolBar(toolbar)
# Действия
refresh_action = QAction("🔄 Обновить", self)
refresh_action.triggered.connect(self.refresh_data)
toolbar.addAction(refresh_action)
toolbar.addSeparator()
add_company_action = QAction(" Добавить компанию", self)
add_company_action.triggered.connect(self.add_company)
toolbar.addAction(add_company_action)
toolbar.addSeparator()
add_model_action = QAction(" Добавить модель", self)
add_model_action.triggered.connect(self.add_model)
toolbar.addAction(add_model_action)
def create_companies_tab(self) -> QWidget:
"""Создание вкладки компаний"""
widget = QWidget()
layout = QVBoxLayout(widget)
# Панель управления
control_panel = QHBoxLayout()
control_panel.addStretch()
layout.addLayout(control_panel)
# Таблица компаний
self.companies_table = QTableWidget()
self.companies_table.setColumnCount(3)
self.companies_table.setHorizontalHeaderLabels(["ID", "Название компании", "Кол-во моделей"])
self.companies_table.setSortingEnabled(True)
# Настройка размеров столбцов
header = self.companies_table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
layout.addWidget(self.companies_table)
return widget
def create_models_tab(self) -> QWidget:
"""Создание вкладки моделей"""
widget = QWidget()
layout = QVBoxLayout(widget)
# Панель поиска
search_panel = QHBoxLayout()
self.search_edit = QLineEdit()
self.search_edit.setPlaceholderText("🔍 Поиск по названию, компании, RAM...")
self.search_edit.textChanged.connect(self.search_models)
search_panel.addWidget(self.search_edit)
layout.addLayout(search_panel)
# Таблица моделей
self.models_table = QTableWidget()
self.models_table.setColumnCount(11)
self.models_table.setHorizontalHeaderLabels([
"ID", "Компания", "Модель", "RAM", "Батарея",
"Экран", "Год", "Регионов с ценами", "Действия", "", ""
])
self.models_table.setSortingEnabled(True)
# Настройка размеров столбцов
header = self.models_table.horizontalHeader()
# ID - минимальный размер
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
# Компания и Модель - расширяемые
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
# Характеристики - по содержимому
for i in range(3, 8):
header.setSectionResizeMode(i, QHeaderView.ResizeMode.ResizeToContents)
# Кнопки действий - фиксированные
for i in range(8, 11):
header.setSectionResizeMode(i, QHeaderView.ResizeMode.Fixed)
header.resizeSection(i, 120)
layout.addWidget(self.models_table)
return widget
def create_analytics_tab(self) -> QWidget:
"""Создание вкладки аналитики"""
widget = QWidget()
layout = QVBoxLayout(widget)
# Статистика по регионам
stats_group = QGroupBox("📊 Статистика цен по регионам")
stats_layout = QVBoxLayout()
self.stats_text = QTextEdit()
self.stats_text.setReadOnly(True)
self.stats_text.setFont(QFont("Consolas", 10))
stats_layout.addWidget(self.stats_text)
refresh_stats_btn = QPushButton("🔄 Обновить статистику")
refresh_stats_btn.clicked.connect(self.update_statistics)
stats_layout.addWidget(refresh_stats_btn)
stats_group.setLayout(stats_layout)
layout.addWidget(stats_group)
return widget
def connect_to_db(self):
"""Подключение к БД"""
if self.db.connect():
self.status_bar.showMessage("✅ Подключено к БД")
self.refresh_data()
else:
QMessageBox.critical(self, "Ошибка", "Не удалось подключиться к БД!")
def refresh_data(self):
"""Обновление всех данных"""
self.load_companies()
self.load_models()
self.update_statistics()
self.status_bar.showMessage("✅ Данные обновлены")
def load_companies(self):
"""Загрузка списка компаний"""
companies = self.db.get_all_companies()
self.companies_table.setRowCount(len(companies))
for row, company in enumerate(companies):
# ID
id_item = QTableWidgetItem(str(company['company_id']))
id_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.companies_table.setItem(row, 0, id_item)
# Название
name_item = QTableWidgetItem(company['company_name'])
name_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.companies_table.setItem(row, 1, name_item)
# Количество моделей
count_item = QTableWidgetItem(str(company['models_count']))
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.companies_table.setItem(row, 2, count_item)
def load_models(self, search_text=""):
"""Загрузка списка моделей"""
if search_text:
models = self.db.search_models(search_text)
else:
models = self.db.get_all_models()
self.models_table.setRowCount(len(models))
for row, model in enumerate(models):
# ID
id_item = QTableWidgetItem(str(model['model_id']))
id_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 0, id_item)
# Компания
company_item = QTableWidgetItem(model['company_name'])
company_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.models_table.setItem(row, 1, company_item)
# Модель
model_item = QTableWidgetItem(model['model_name'])
model_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.models_table.setItem(row, 2, model_item)
# RAM
ram_item = QTableWidgetItem(model.get('ram', ''))
ram_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 3, ram_item)
# Батарея
battery_item = QTableWidgetItem(model.get('battery_capacity', ''))
battery_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 4, battery_item)
# Экран
screen_item = QTableWidgetItem(model.get('screen_size', ''))
screen_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 5, screen_item)
# Год
year_item = QTableWidgetItem(str(model.get('launched_year', '')))
year_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 6, year_item)
# Регионов с ценами
regions_item = QTableWidgetItem(str(model.get('price_regions', 0)))
regions_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.models_table.setItem(row, 7, regions_item)
# Кнопки действий
edit_btn = QPushButton("✏️ Редакт.")
edit_btn.clicked.connect(lambda checked, mid=model['model_id']: self.edit_model(mid))
self.models_table.setCellWidget(row, 8, edit_btn)
price_btn = QPushButton("💰 Цены")
price_btn.clicked.connect(
lambda checked, mid=model['model_id'], name=model['model_name']:
self.manage_prices(mid, name)
)
self.models_table.setCellWidget(row, 9, price_btn)
delete_btn = QPushButton("🗑️ Удалить")
delete_btn.clicked.connect(lambda checked, mid=model['model_id']: self.delete_model(mid))
self.models_table.setCellWidget(row, 10, delete_btn)
def add_company(self):
"""Добавление новой компании"""
name, ok = QInputDialog.getText(self, "Новая компания", "Введите название компании:")
if ok and name:
try:
self.db.add_company(name)
self.refresh_data()
QMessageBox.information(self, "Успех", f"Компания '{name}' добавлена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при добавлении: {str(e)}")
def add_model(self):
"""Добавление новой модели"""
dialog = ModelDialog(self)
if dialog.exec() == QDialog.DialogCode.Accepted:
try:
model_data = dialog.get_data()
self.db.add_model(model_data)
self.refresh_data()
QMessageBox.information(self, "Успех", "Модель добавлена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при добавлении: {str(e)}")
def edit_model(self, model_id):
"""Редактирование модели"""
model_data = self.db.get_model_by_id(model_id)
dialog = ModelDialog(self, model_data)
if dialog.exec() == QDialog.DialogCode.Accepted:
try:
updated_data = dialog.get_data()
self.db.update_model(model_id, updated_data)
self.refresh_data()
QMessageBox.information(self, "Успех", "Модель обновлена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при обновлении: {str(e)}")
def delete_model(self, model_id):
"""Удаление модели"""
reply = QMessageBox.question(
self, "Подтверждение",
"Вы уверены, что хотите удалить эту модель?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
try:
self.db.delete_model(model_id)
self.refresh_data()
QMessageBox.information(self, "Успех", "Модель удалена!")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Ошибка при удалении: {str(e)}")
def manage_prices(self, model_id, model_name):
"""Управление ценами модели"""
dialog = PriceDialog(self, model_id, model_name)
dialog.exec()
self.refresh_data()
def search_models(self, text):
"""Поиск моделей"""
self.load_models(text)
def update_statistics(self):
"""Обновление статистики"""
try:
stats = self.db.get_price_statistics()
stats_text = "📊 СТАТИСТИКА ЦЕН ПО РЕГИОНАМ\n" + "="*60 + "\n\n"
for stat in stats:
region_name = stat['region_name']
currency_code, currency_symbol = CURRENCY_MAP.get(region_name, ('USD', '$'))
stats_text += f"🌍 {region_name} ({currency_code}):\n"
stats_text += f" • Моделей с ценами: {stat['models_count']}\n"
stats_text += f" • Средняя цена: {currency_symbol}{stat['avg_price']:,.2f}\n"
stats_text += f" • Минимальная цена: {currency_symbol}{stat['min_price']:,.2f}\n"
stats_text += f" • Максимальная цена: {currency_symbol}{stat['max_price']:,.2f}\n\n"
self.stats_text.setText(stats_text)
except Exception as e:
self.stats_text.setText(f"Ошибка при загрузке статистики: {str(e)}")
def closeEvent(self, event):
"""Обработка закрытия окна"""
self.db.disconnect()
event.accept()
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def main():
app = QApplication(sys.argv)
# Настройки приложения
app.setApplicationName("Mobile Devices Manager")
app.setOrganizationName("Moscow Polytech")
# Устанавливаем стиль
app.setStyle('Fusion')
# Создаем и показываем главное окно
window = MainWindow()
window.show()
# Запускаем цикл обработки событий
sys.exit(app.exec())
if __name__ == "__main__":
main()