1
0
Fork 0
mirror of https://github.com/EDeev/mobiles_dataset.git synced 2026-10-08 04:59:41 +03:00

Compare commits

..

No commits in common. "7277093a13dc024695811d5cfcb21cd18f8c0b99" and "fe5e71a2486dfe579a932bfb2d1d51961b1b4c9d" have entirely different histories.

20 changed files with 6917 additions and 4551 deletions

5
.gitattributes vendored
View file

@ -1,5 +0,0 @@
* text=auto eol=lf
*.png binary
*.pdf binary
*.docx binary
*.pgerd text

View file

@ -1,36 +0,0 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U postgres" --health-interval 5s --health-retries 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install ruff==0.14.0 psycopg2-binary==2.9.9 pandas==2.1.4 numpy==1.26.2
- name: Ruff (синтаксис и ошибки)
run: ruff check --select E9,F .
- name: Схема и импорт датасета
env:
PGPASSWORD: postgres
DB_USER: postgres
DB_PASSWORD: postgres
run: |
psql -h localhost -U postgres -f sql/create_schema.sql
python scripts/import_data.py
test "$(psql -h localhost -U postgres -d mobile_devices_db -tAc 'select count(*) from models')" -gt 900

View file

@ -1,23 +0,0 @@
name: Release
on:
push:
tags: ["v*"]
jobs:
exe:
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install pyinstaller psycopg2-binary==2.9.9 PyQt6==6.6.1
- name: Сборка .exe из однофайловой версии
run: pyinstaller --onefile --windowed --name MobileDevices scripts/exe/mobile_devices.py
- name: Файл в релиз
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${{ github.ref_name }}" dist/MobileDevices.exe --clobber

View file

@ -1,76 +0,0 @@
# Mobile Devices Database
[Русский](README.md) · **English**
[![CI](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml/badge.svg)](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/EDeev/mobiles_dataset)](https://github.com/EDeev/mobiles_dataset/releases)
Database design coursework: the 2025 mobile devices dataset split into a normalized PostgreSQL database,
a PyQt6 desktop app to work with it, and a measurement of index gains with `EXPLAIN ANALYZE`.
**Status:** coursework ("Database Design and Administration", Moscow Polytechnic University, 2025), completed
![Models tab](docs/screenshots/models.png)
**Stack:** Python 3.11 · PostgreSQL 15 · psycopg2 · PyQt6 · pandas · Docker
## Features
- 3NF schema of five tables (companies, processors, models, regions, prices) with foreign keys and
cascading actions
- Import of the [Kaggle dataset](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
(930 rows; 914 models and 4569 prices after cleaning) split into reference tables
- App: adding companies, models with create, edit and delete, prices in five regions (Pakistan, India,
China, UAE, USA) with currency symbols, search by name, company and RAM, a price statistics tab
- Index experiment: `EXPLAIN ANALYZE` queries before and after, plans saved in `sql/explain_results/`
| Metric | No indexes | With indexes | Difference |
|---|---|---|---|
| Search | 0.234 ms | 0.089 ms | 62% faster |
| Four-table JOIN | 18.6 ms | 0.95 ms | 95% faster |
| Planner cost | 44.76 | 12.45 | 72% lower |
| Rows scanned | 914 | 18 | 98% fewer |
Measured on this dataset (hundreds of rows), so absolute times are fractions of a millisecond.
## Quick start
```bash
git clone https://github.com/EDeev/mobiles_dataset.git && cd mobiles_dataset
docker compose up -d # PostgreSQL 15 with the schema from sql/create_schema.sql
pip install -r requirements.txt
python scripts/import_data.py # load the dataset
python main.py # the app
```
A ready-made Windows build (`.exe`) is in the [releases](https://github.com/EDeev/mobiles_dataset/releases).
## Without Docker
1. Run `sql/create_schema.sql` in psql or pgAdmin as `postgres` — the script creates the
`mobile_devices_db` database itself.
2. Set the connection with `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
(defaults: `localhost:5432`, `mobile_devices_db`, `admin` / `password`).
3. `python scripts/import_data.py`, then `python main.py`.
## Screenshots
| Companies | Analytics |
|---|---|
| ![Companies](docs/screenshots/companies.png) | ![Analytics](docs/screenshots/analytics.png) |
## License
Coursework (Database Design and Administration, Moscow Polytechnic University, 2025). The code is open
for study; there is no separate license. The dataset comes from Kaggle under its author's terms.
## Author
**Egor Deev** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
---
<div align="center">
<sub>⭐ If you find this project useful, give it a star on GitHub!</sub>
<p><sub>Made with ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
</div>

331
README.md
View file

@ -1,89 +1,290 @@
# Mobile Devices Database
# 📱 Mobile Devices Database Management System
**Русский** · [English](README.en.md)
**Комплексная система управления каталогом мобильных устройств с GUI-интерфейсом**
[![CI](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml/badge.svg)](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/EDeev/mobiles_dataset)](https://github.com/EDeev/mobiles_dataset/releases)
[![Python](https://img.shields.io/badge/Python-3.11+-blue.svg)](https://python.org)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15.x-blue.svg)](https://postgresql.org)
[![PyQt6](https://img.shields.io/badge/PyQt6-6.6+-green.svg)](https://pypi.org/project/PyQt6/)
[![License](https://img.shields.io/badge/License-Academic-orange.svg)](#)
Курсовая по проектированию баз данных: датасет мобильных устройств 2025 года, разложенный в
нормализованную базу PostgreSQL, десктопное приложение на PyQt6 для работы с ним и измерение
выигрыша от индексов через `EXPLAIN ANALYZE`.
## 🎯 Назначение проекта
**Статус:** учебный проект (курсовая «Проектирование и администрирование баз данных», Московский
Политех, 2025), завершён
Курсовая работа по дисциплине "Проектирование и администрирование баз данных", представляющая собой полнофункциональную информационную систему для управления данными о мобильных устройствах. Проект демонстрирует практическое применение принципов реляционного моделирования, оптимизации производительности СУБД и создания современных desktop-приложений.
![Вкладка «Модели»](docs/screenshots/models.png)
## 🏗️ Архитектура системы
**Стек:** Python 3.11 · PostgreSQL 15 · psycopg2 · PyQt6 · pandas · Docker
## Возможности
- Схема в 3НФ из пяти таблиц (компании, процессоры, модели, регионы, цены) с внешними ключами и
каскадными операциями
- Импорт [датасета с Kaggle](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
(930 строк, после очистки 914 моделей и 4569 цен) с разнесением по справочникам
- Приложение: добавление компаний, модели с добавлением, правкой и удалением, цены по пяти регионам (Пакистан,
Индия, Китай, ОАЭ, США) с символами валют, поиск по названию, компании и RAM, вкладка статистики цен
- Эксперимент с индексами: запросы `EXPLAIN ANALYZE` до и после, планы сохранены в `sql/explain_results/`
| Метрика | Без индексов | С индексами | Разница |
|---|---|---|---|
| Поиск | 0,234 мс | 0,089 мс | быстрее на 62 % |
| JOIN четырёх таблиц | 18,6 мс | 0,95 мс | быстрее на 95 % |
| Стоимость по планировщику | 44,76 | 12,45 | ниже на 72 % |
| Просмотренные строки | 914 | 18 | меньше на 98 % |
Измерено на этом датасете (сотни строк), поэтому абсолютные времена — доли миллисекунды.
## Быстрый старт
```bash
git clone https://github.com/EDeev/mobiles_dataset.git && cd mobiles_dataset
docker compose up -d # PostgreSQL 15 со схемой из sql/create_schema.sql
pip install -r requirements.txt
python scripts/import_data.py # загрузка датасета
python main.py # приложение
### Компонентная структура
```
📦 mobile-devices-db/
├── database.py # Слой доступа к данным (DAO)
├── main_window.py # GUI-интерфейс на PyQt6
├── main.py # Запускаемый файл
├── Mobiles Dataset 2025.csv # Дата-сет данных
├── 🗃️ sql/ # SQL-скрипты и схема БД
│ ├── create_schema.sql # Создание нормализованной структуры
│ └── performance_analysis.sql # Тестовые запросы для анализа
├── 🐍 scripts/ # Python-модули системы
│ └── import_data.py # Система импорта и нормализации
├── 📊 docs/ # Техническая документация
└── 📋 requirements.txt # Зависимости проекта
```
Готовая сборка для Windows (`.exe`) — в [релизах](https://github.com/EDeev/mobiles_dataset/releases).
### Технологический стек
- **СУБД**: PostgreSQL 15.x с расширенными возможностями индексирования
- **Backend**: Python 3.11+ с паттерном Singleton для управления соединениями
- **Frontend**: PyQt6 с архитектурой Model-View-Controller
- **Интеграция**: psycopg2 для типобезопасного взаимодействия с БД
## Без Docker
## ⚡ Ключевые особенности
1. Выполните `sql/create_schema.sql` в psql или pgAdmin под пользователем `postgres` — скрипт сам
создаёт базу `mobile_devices_db`.
2. Задайте подключение переменными `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
(по умолчанию `localhost:5432`, `mobile_devices_db`, `admin` / `password`).
3. `python scripts/import_data.py`, затем `python main.py`.
### 🔧 Техническая реализация
- **Нормализация до 3НФ**: Декомпозиция исходного CSV в 5 взаимосвязанных таблиц
- **Референциальная целостность**: Система FK с каскадными операциями
- **Оптимизация производительности**: Стратегическое индексирование с 62-95% улучшением
- **CRUD-функционал**: Полный спектр операций через интуитивный GUI
## Как выглядит
### 📈 Результаты оптимизации
| Метрика | До оптимизации | После оптимизации | Улучшение |
|---------|----------------|-------------------|-----------|
| Время выполнения поиска | 0.234 мс | 0.089 мс | **62% быстрее** |
| JOIN-запросы (4 таблицы) | 18.6 мс | 0.95 мс | **95% быстрее** |
| Стоимость по планировщику | 44.76 ед. | 12.45 ед. | **72% снижение** |
| Обработанные строки | 914 (фильтрация) | 18 (прямой доступ) | **98% сокращение** |
| Компании | Аналитика |
|---|---|
| ![Компании](docs/screenshots/companies.png) | ![Аналитика](docs/screenshots/analytics.png) |
### 🌍 Функциональные возможности
- **Интеллектуальный поиск** по множественным атрибутам устройств
- **Валютная локализация** ценовой информации (5 регионов)
- **Автоматический импорт** данных с нормализацией на лету
- **Аналитические отчеты** с региональной статистикой
## Структура
## 📊 Источники данных
```
database.py доступ к БД (синглтон, параметризованные запросы)
main_window.py, main.py приложение на PyQt6
scripts/import_data.py импорт и нормализация датасета
scripts/exe/ однофайловая версия приложения для сборки .exe
sql/ схема, запросы для анализа производительности, ERD, результаты EXPLAIN
docs/report.* отчёт по курсовой (md, docx, pdf)
Проект использует актуальный датасет мобильных устройств 2025 года:
- **Источник**: [Kaggle - Mobiles Dataset 2025](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
- **Размер**: 1000+ записей устройств
- **Охват**: Основные производители (Apple, Samsung, Xiaomi, OnePlus, etc.)
- **Регионы**: Пакистан, Индия, Китай, США, ОАЭ
## 🚀 Быстрый старт
### Системные требования
- **Python 3.11+** с поддержкой venv
- **PostgreSQL 15.x** с административными правами
- **4GB RAM** (рекомендуется 8GB для больших наборов данных)
### Установка и настройка
1. **Клонирование репозитория**
```bash
git clone https://github.com/username/mobile-devices-db.git
cd mobile-devices-db
```
2. **Создание виртуального окружения**
```bash
python -m venv venv
source venv/bin/activate # Linux/macOS
# или
venv\Scripts\activate # Windows
```
3. **Установка зависимостей**
```bash
pip install -r requirements.txt
```
4. **Настройка PostgreSQL**
```sql
-- Создание пользователя и БД
CREATE USER admin WITH PASSWORD 'your_password';
CREATE DATABASE mobile_devices_db OWNER admin;
GRANT ALL PRIVILEGES ON DATABASE mobile_devices_db TO admin;
```
5. **Развертывание схемы данных**
```bash
psql -U admin -d mobile_devices_db -f sql/create_schema.sql
```
6. **Импорт тестовых данных**
```bash
python scripts/import_data.py
```
7. **Запуск приложения**
```bash
python scripts/main_window.py
```
## 📊 Структура базы данных
### Реляционная модель (3НФ)
```mermaid
erDiagram
COMPANIES ||--o{ MODELS : manufactures
PROCESSORS ||--o{ MODELS : uses
MODELS ||--o{ PRICES : "has prices in"
REGIONS ||--o{ PRICES : "contains prices"
COMPANIES {
int company_id PK
varchar company_name UK
}
MODELS {
int model_id PK
varchar model_name
int company_id FK
int processor_id FK
varchar ram
varchar battery_capacity
int launched_year
}
PRICES {
int price_id PK
int model_id FK
int region_id FK
decimal price
varchar currency
}
```
## Лицензия
### Ключевые таблицы
- **Companies**: Справочник производителей (19 уникальных компаний)
- **Models**: Каталог устройств (914 моделей с техническими характеристиками)
- **Processors**: Справочник процессоров (217 уникальных чипсетов)
- **Regions**: Географические регионы ценообразования (5 регионов)
- **Prices**: Региональная ценовая информация (4,569 записей)
Учебный проект (Проектирование и администрирование баз данных, Московский Политех, 2025). Код открыт
для изучения, отдельной лицензии нет. Датасет — с Kaggle, на условиях его автора.
## 🎨 Пользовательский интерфейс
## Автор
### Основные компоненты
- **📋 Каталог компаний**: Обзор производителей с количеством моделей
- **📱 Управление моделями**: CRUD-операции с техническими характеристиками
- **💰 Ценовое управление**: Редактирование цен с валютной локализацией
- **📊 Аналитическая панель**: Региональная статистика и отчеты
**Деев Егор Викторович** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
### Функциональные возможности
```python
# Пример поискового запроса
def search_models(self, search_text: str):
"""Интеллектуальный поиск по множественным атрибутам"""
return self.db.search_models(search_text)
# Валютная локализация
CURRENCY_MAP = {
'Pakistan': ('PKR', '₨'),
'India': ('INR', '₹'),
'China': ('CNY', '¥'),
'USA': ('USD', '$'),
'Dubai': ('AED', 'د.إ')
}
```
## 🔍 Анализ производительности
### Методология тестирования
Комплексный анализ производительности выполнен с использованием PostgreSQL EXPLAIN ANALYZE:
#### Тестовые сценарии
1. **Простые селективные запросы** - поиск по названию компании
2. **Многотабличные JOIN** - получение полной информации об устройствах
3. **Агрегирующие операции** - статистические запросы с группировкой
4. **Поиск по характеристикам** - фильтрация по техническим параметрам
#### Стратегия оптимизации
```sql
-- Базовые B-tree индексы
CREATE INDEX idx_companies_name ON companies(company_name);
CREATE INDEX idx_models_company_id ON models(company_id);
-- Составные индексы для комплексных запросов
CREATE INDEX idx_models_ram_battery ON models(ram, battery_capacity);
-- Функциональные индексы для LIKE-операций
CREATE INDEX idx_companies_name_pattern ON companies(company_name varchar_pattern_ops);
```
## 📈 Практические результаты
### Количественные показатели
- **Импорт данных**: 930 записей устройств с 100% целостностью
- **Нормализация**: 60% сокращение избыточности данных
- **Производительность**: 95% улучшение для сложных JOIN-запросов
- **Индексирование**: 72% снижение стоимости по планировщику
### Качественные достижения
- Полная автоматизация процесса нормализации данных
- Интуитивный GUI с поддержкой валютной локализации
- Масштабируемая архитектура для enterprise-развертывания
- Комплексная система мониторинга производительности
## 🔧 Документация
- **[Техническая документация](docs/README.md)** - Подробное описание архитектуры и API
- **[Отчет по курсовой работе](docs/report.md)** - Полный академический отчет
- **[SQL-скрипты](sql/)** - Схемы БД и запросы для анализа производительности
### Конфигурация базы данных
```python
# Параметры подключения
DATABASE_CONFIG = {
'host': 'localhost',
'port': 5432,
'database': 'mobile_devices_db',
'user': 'admin',
'password': 'your_password'
}
```
### Основные зависимости
```
psycopg2-binary==2.9.9 # PostgreSQL adapter
PyQt6==6.6.1 # GUI framework
pandas==2.1.4 # Data processing
numpy==1.26.2 # Numerical computing
```
## 🚀 Перспективы развития
### Краткосрочные цели
- **Веб-интерфейс**: Реализация REST API на FastAPI
- **Автоматизация**: Интеграция с внешними API для обновления каталога
- **Мониторинг**: Real-time дашборды производительности
### Долгосрочная стратегия
- **Machine Learning**: Прогнозирование ценовых тенденций
- **Микросервисы**: Архитектурная модернизация для cloud-deployment
- **Аналитика**: Интеграция с Apache Kafka для stream-processing
## 📚 Академическая ценность
Проект демонстрирует практическое применение ключевых концепций:
### Теоретические основы
- **Нормализация реляционных данных** до третьей нормальной формы
- **Оптимизация производительности СУБД** через стратегическое индексирование
- **Архитектурные паттерны** для desktop-приложений с базами данных
### Практические навыки
- Проектирование и реализация нормализованных схем данных
- Анализ и оптимизация производительности SQL-запросов
- Создание многокомпонентных приложений с GUI-интерфейсом
## 📄 Лицензия
Этот проект создан в учебных целях как курсовая работа по дисциплине "Проектирование и администрирование баз данных" в Московском Политехническом Университете, 2024-2025 учебный год.
## 👨‍💻 Автор
**Деев Егор Викторович** - Backend Developer
- GitHub: [@EDeev](https://github.com/EDeev)
- Email: egor@deev.space
- Telegram: [@Egor_Deev](https://t.me/Egor_Deev)
---
<div align="center">
<sub>⭐ Если проект оказался полезным, поставьте звёздочку на GitHub!</sub>
<p><sub>Сделано с ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
<p><sub>Создано с ❤️ от вашего дорогого - deev.space ©</sub></p>
</div>

View file

@ -1,20 +0,0 @@
# Локальный PostgreSQL 15 со схемой курсовой: docker compose up -d
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- ./docker/00-roles.sql:/docker-entrypoint-initdb.d/00-roles.sql:ro
- ./sql/create_schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d mobile_devices_db"]
interval: 5s
retries: 20
volumes:
pgdata:

View file

@ -1,5 +1,4 @@
# db/database.py
import os
import psycopg2
from psycopg2.extras import RealDictCursor
from typing import List, Dict, Any, Optional
@ -21,13 +20,8 @@ class Database:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host=None, port=None, database=None, user=None, password=None):
# Параметры подключения: аргументы, затем переменные окружения DB_*, затем значения для локальной разработки
host = host or os.environ.get('DB_HOST', 'localhost')
port = port or int(os.environ.get('DB_PORT', '5432'))
database = database or os.environ.get('DB_NAME', 'mobile_devices_db')
user = user or os.environ.get('DB_USER', 'admin')
password = password or os.environ.get('DB_PASSWORD', 'password')
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,

View file

@ -1,2 +0,0 @@
-- Роль приложения для локального стенда (значения по умолчанию из database.py)
CREATE ROLE admin LOGIN SUPERUSER PASSWORD 'password';

2374
docs/README.md Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

View file

@ -1,6 +1,7 @@
import sys
import logging
from PyQt6.QtWidgets import QApplication
from PyQt6.QtCore import Qt
from main_window import MainWindow

View file

@ -1,14 +1,15 @@
# ui/main_window.py
import sys
from PyQt6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
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
from PyQt6.QtGui import QAction, QFont
from typing import Dict, Any
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
from PyQt6.QtGui import QAction, QIcon, QFont
from typing import Optional, Dict, Any
import logging
# Импортируем наш модуль БД
@ -189,9 +190,6 @@ class PriceDialog(QDialog):
def load_prices(self):
"""Загрузка цен из БД"""
prices = self.db.get_model_prices(self.model_id)
# При включённой сортировке строки переставляются прямо во время заполнения,
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
self.prices_table.setSortingEnabled(False)
self.prices_table.setRowCount(len(prices))
for row, price_data in enumerate(prices):
@ -216,9 +214,6 @@ class PriceDialog(QDialog):
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)
# сортировку возвращаем только после заполнения (см. начало функции)
self.prices_table.setSortingEnabled(True)
def add_update_price(self):
"""Добавление или обновление цены"""
@ -430,9 +425,6 @@ class MainWindow(QMainWindow):
def load_companies(self):
"""Загрузка списка компаний"""
companies = self.db.get_all_companies()
# При включённой сортировке строки переставляются прямо во время заполнения,
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
self.companies_table.setSortingEnabled(False)
self.companies_table.setRowCount(len(companies))
for row, company in enumerate(companies):
@ -450,9 +442,6 @@ class MainWindow(QMainWindow):
count_item = QTableWidgetItem(str(company['models_count']))
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.companies_table.setItem(row, 2, count_item)
# сортировку возвращаем только после заполнения (см. начало функции)
self.companies_table.setSortingEnabled(True)
def load_models(self, search_text=""):
"""Загрузка списка моделей"""
@ -461,9 +450,6 @@ class MainWindow(QMainWindow):
else:
models = self.db.get_all_models()
# При включённой сортировке строки переставляются прямо во время заполнения,
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
self.models_table.setSortingEnabled(False)
self.models_table.setRowCount(len(models))
for row, model in enumerate(models):
@ -522,9 +508,6 @@ class MainWindow(QMainWindow):
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)
# сортировку возвращаем только после заполнения (см. начало функции)
self.models_table.setSortingEnabled(True)
def add_company(self):
"""Добавление новой компании"""

View file

@ -5,21 +5,24 @@ from PyQt6.QtWidgets import QApplication
from PyQt6.QtCore import Qt
# ui/main_window.py
import sys
from PyQt6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
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.QtGui import QAction, QFont
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 os
import psycopg2
from psycopg2.extras import RealDictCursor
from typing import List
from typing import List, Dict, Any, Optional
import logging
from contextlib import contextmanager
logger = logging.getLogger(__name__)
@ -45,13 +48,8 @@ class Database:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host=None, port=None, database=None, user=None, password=None):
# Параметры подключения: аргументы, затем переменные окружения DB_*, затем значения для локальной разработки
host = host or os.environ.get('DB_HOST', 'localhost')
port = port or int(os.environ.get('DB_PORT', '5432'))
database = database or os.environ.get('DB_NAME', 'mobile_devices_db')
user = user or os.environ.get('DB_USER', 'admin')
password = password or os.environ.get('DB_PASSWORD', 'password')
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,
@ -505,9 +503,6 @@ class PriceDialog(QDialog):
def load_prices(self):
"""Загрузка цен из БД"""
prices = self.db.get_model_prices(self.model_id)
# При включённой сортировке строки переставляются прямо во время заполнения,
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
self.prices_table.setSortingEnabled(False)
self.prices_table.setRowCount(len(prices))
for row, price_data in enumerate(prices):
@ -532,9 +527,6 @@ class PriceDialog(QDialog):
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)
# сортировку возвращаем только после заполнения (см. начало функции)
self.prices_table.setSortingEnabled(True)
def add_update_price(self):
"""Добавление или обновление цены"""
@ -745,9 +737,6 @@ class MainWindow(QMainWindow):
def load_companies(self):
"""Загрузка списка компаний"""
companies = self.db.get_all_companies()
# При включённой сортировке строки переставляются прямо во время заполнения,
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
self.companies_table.setSortingEnabled(False)
self.companies_table.setRowCount(len(companies))
for row, company in enumerate(companies):
@ -765,9 +754,6 @@ class MainWindow(QMainWindow):
count_item = QTableWidgetItem(str(company['models_count']))
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.companies_table.setItem(row, 2, count_item)
# сортировку возвращаем только после заполнения (см. начало функции)
self.companies_table.setSortingEnabled(True)
def load_models(self, search_text=""):
"""Загрузка списка моделей"""
@ -776,9 +762,6 @@ class MainWindow(QMainWindow):
else:
models = self.db.get_all_models()
# При включённой сортировке строки переставляются прямо во время заполнения,
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
self.models_table.setSortingEnabled(False)
self.models_table.setRowCount(len(models))
for row, model in enumerate(models):
@ -837,9 +820,6 @@ class MainWindow(QMainWindow):
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)
# сортировку возвращаем только после заполнения (см. начало функции)
self.models_table.setSortingEnabled(True)
def add_company(self):
"""Добавление новой компании"""

View file

@ -1,9 +1,8 @@
import os
import sys
import pandas as pd
import psycopg2
from psycopg2.extras import execute_values
import re
from typing import Optional, Dict
from typing import Optional, Dict, Tuple
import logging
# Настройка логирования
@ -246,13 +245,12 @@ class MobileDataImporter:
# Использование скрипта
if __name__ == "__main__":
# Конфигурация подключения к БД
# Параметры подключения — из переменных окружения DB_* (по умолчанию — локальная разработка)
db_config = {
'host': os.environ.get('DB_HOST', 'localhost'),
'port': int(os.environ.get('DB_PORT', '5432')),
'database': os.environ.get('DB_NAME', 'mobile_devices_db'),
'user': os.environ.get('DB_USER', 'admin'),
'password': os.environ.get('DB_PASSWORD', 'password'),
'host': 'localhost',
'port': 5432,
'database': 'mobile_devices_db',
'user': 'admin',
'password': 'password' # Замените на ваш пароль
}
# Создаем импортер и выполняем импорт
@ -260,9 +258,6 @@ if __name__ == "__main__":
try:
importer.connect()
# Датасет лежит в корне репозитория; другой файл можно передать первым аргументом
csv_path = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'Mobiles Dataset 2025.csv')
importer.import_data(csv_path)
importer.import_data('Mobiles Dataset 2025.csv') # Укажите путь к вашему файлу
finally:
importer.disconnect()