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

Compare commits

..

8 commits

Author SHA1 Message Date
7277093a13 README: компании в приложении можно только добавлять 2026-10-05 17:37:51 +00:00
2daa52a238 README на русском и английском, скриншоты, CI и сборка .exe в релиз
- README: факты курсовой сверены с кодом (пять таблиц, 930 строк датасета → 914 моделей),
  ссылка клонирования — на настоящий репозиторий вместо шаблонной, инструкция без Docker
  исправлена (скрипт схемы сам создаёт базу и выполняется под postgres);
- CI: ruff, создание схемы и импорт датасета в PostgreSQL;
- по тегу v* GitHub Actions собирает MobileDevices.exe (PyInstaller) и кладёт в релиз.
2026-10-05 17:36:36 +00:00
3be7052b0a Таблицы: сортировка выключается на время заполнения
QTableWidget с включённой сортировкой переставлял строки прямо во время заполнения:
после сортировки по столбцу и обновления данные и кнопки «Редактировать/Цены/Удалить»
могли оказаться в разных строках — действие применялось к чужой записи. Исправлено в
таблицах компаний, моделей и цен (и в копии для exe).
2026-10-05 17:35:37 +00:00
2c26b2fe65 docker-compose: PostgreSQL 15 со схемой курсовой и ролью по умолчанию для локального запуска 2026-10-05 17:23:12 +00:00
891a0e9a3c Переводы строк в CSV с результатами EXPLAIN приведены к LF 2026-10-05 17:21:14 +00:00
20a36a7d1c Подключение к БД — из переменных окружения DB_*, лишние импорты убраны
- database.py, однофайловая копия для exe и импорт датасета читают DB_HOST, DB_PORT,
  DB_NAME, DB_USER, DB_PASSWORD; прежние значения остались значениями по умолчанию;
- импорт находит датасет в корне репозитория независимо от текущей папки (или берёт путь
  первым аргументом);
- 20 неиспользуемых импортов и переопределений после склейки файлов (ruff).
2026-10-05 17:21:14 +00:00
acc9e6b207 Удалён docs/README.md — точная копия docs/report.md 2026-10-05 17:19:44 +00:00
227b491d96 Переводы строк приведены к LF 2026-10-05 17:19:44 +00:00
20 changed files with 4551 additions and 6917 deletions

5
.gitattributes vendored Normal file
View file

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

36
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,36 @@
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

23
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,23 @@
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

76
README.en.md Normal file
View file

@ -0,0 +1,76 @@
# 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>

327
README.md
View file

@ -1,290 +1,89 @@
# 📱 Mobile Devices Database Management System
# Mobile Devices Database
**Комплексная система управления каталогом мобильных устройств с GUI-интерфейсом**
**Русский** · [English](README.en.md)
[![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)](#)
[![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)
## 🎯 Назначение проекта
Курсовая по проектированию баз данных: датасет мобильных устройств 2025 года, разложенный в
нормализованную базу PostgreSQL, десктопное приложение на PyQt6 для работы с ним и измерение
выигрыша от индексов через `EXPLAIN ANALYZE`.
Курсовая работа по дисциплине "Проектирование и администрирование баз данных", представляющая собой полнофункциональную информационную систему для управления данными о мобильных устройствах. Проект демонстрирует практическое применение принципов реляционного моделирования, оптимизации производительности СУБД и создания современных desktop-приложений.
**Статус:** учебный проект (курсовая «Проектирование и администрирование баз данных», Московский
Политех, 2025), завершён
## 🏗️ Архитектура системы
![Вкладка «Модели»](docs/screenshots/models.png)
### Компонентная структура
```
📦 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 # Зависимости проекта
```
**Стек:** Python 3.11 · PostgreSQL 15 · psycopg2 · PyQt6 · pandas · Docker
### Технологический стек
- **СУБД**: PostgreSQL 15.x с расширенными возможностями индексирования
- **Backend**: Python 3.11+ с паттерном Singleton для управления соединениями
- **Frontend**: PyQt6 с архитектурой Model-View-Controller
- **Интеграция**: psycopg2 для типобезопасного взаимодействия с БД
## Возможности
## ⚡ Ключевые особенности
- Схема в 3НФ из пяти таблиц (компании, процессоры, модели, регионы, цены) с внешними ключами и
каскадными операциями
- Импорт [датасета с Kaggle](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
(930 строк, после очистки 914 моделей и 4569 цен) с разнесением по справочникам
- Приложение: добавление компаний, модели с добавлением, правкой и удалением, цены по пяти регионам (Пакистан,
Индия, Китай, ОАЭ, США) с символами валют, поиск по названию, компании и RAM, вкладка статистики цен
- Эксперимент с индексами: запросы `EXPLAIN ANALYZE` до и после, планы сохранены в `sql/explain_results/`
### 🔧 Техническая реализация
- **Нормализация до 3НФ**: Декомпозиция исходного CSV в 5 взаимосвязанных таблиц
- **Референциальная целостность**: Система FK с каскадными операциями
- **Оптимизация производительности**: Стратегическое индексирование с 62-95% улучшением
- **CRUD-функционал**: Полный спектр операций через интуитивный GUI
| Метрика | Без индексов | С индексами | Разница |
|---|---|---|---|
| Поиск | 0,234 мс | 0,089 мс | быстрее на 62 % |
| JOIN четырёх таблиц | 18,6 мс | 0,95 мс | быстрее на 95 % |
| Стоимость по планировщику | 44,76 | 12,45 | ниже на 72 % |
| Просмотренные строки | 914 | 18 | меньше на 98 % |
### 📈 Результаты оптимизации
| Метрика | До оптимизации | После оптимизации | Улучшение |
|---------|----------------|-------------------|-----------|
| Время выполнения поиска | 0.234 мс | 0.089 мс | **62% быстрее** |
| JOIN-запросы (4 таблицы) | 18.6 мс | 0.95 мс | **95% быстрее** |
| Стоимость по планировщику | 44.76 ед. | 12.45 ед. | **72% снижение** |
| Обработанные строки | 914 (фильтрация) | 18 (прямой доступ) | **98% сокращение** |
Измерено на этом датасете (сотни строк), поэтому абсолютные времена — доли миллисекунды.
### 🌍 Функциональные возможности
- **Интеллектуальный поиск** по множественным атрибутам устройств
- **Валютная локализация** ценовой информации (5 регионов)
- **Автоматический импорт** данных с нормализацией на лету
- **Аналитические отчеты** с региональной статистикой
## Быстрый старт
## 📊 Источники данных
Проект использует актуальный датасет мобильных устройств 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
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 # приложение
```
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;
Готовая сборка для Windows (`.exe`) — в [релизах](https://github.com/EDeev/mobiles_dataset/releases).
## Без 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`.
## Как выглядит
| Компании | Аналитика |
|---|---|
| ![Компании](docs/screenshots/companies.png) | ![Аналитика](docs/screenshots/analytics.png) |
## Структура
```
database.py доступ к БД (синглтон, параметризованные запросы)
main_window.py, main.py приложение на PyQt6
scripts/import_data.py импорт и нормализация датасета
scripts/exe/ однофайловая версия приложения для сборки .exe
sql/ схема, запросы для анализа производительности, ERD, результаты EXPLAIN
docs/report.* отчёт по курсовой (md, docx, pdf)
```
5. **Развертывание схемы данных**
```bash
psql -U admin -d mobile_devices_db -f sql/create_schema.sql
```
## Лицензия
6. **Импорт тестовых данных**
```bash
python scripts/import_data.py
```
Учебный проект (Проектирование и администрирование баз данных, Московский Политех, 2025). Код открыт
для изучения, отдельной лицензии нет. Датасет — с Kaggle, на условиях его автора.
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 записей)
## 🎨 Пользовательский интерфейс
### Основные компоненты
- **📋 Каталог компаний**: Обзор производителей с количеством моделей
- **📱 Управление моделями**: CRUD-операции с техническими характеристиками
- **💰 Ценовое управление**: Редактирование цен с валютной локализацией
- **📊 Аналитическая панель**: Региональная статистика и отчеты
### Функциональные возможности
```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)
**Деев Егор Викторович** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
---
<div align="center">
<p><sub>Создано с ❤️ от вашего дорогого - deev.space ©</sub></p>
<sub>⭐ Если проект оказался полезным, поставьте звёздочку на GitHub!</sub>
<p><sub>Сделано с ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
</div>

20
compose.yaml Normal file
View file

@ -0,0 +1,20 @@
# Локальный 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,4 +1,5 @@
# db/database.py
import os
import psycopg2
from psycopg2.extras import RealDictCursor
from typing import List, Dict, Any, Optional
@ -20,8 +21,13 @@ class Database:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host='localhost', port=5432, database='mobile_devices_db',
user='admin', password='password'):
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')
if not hasattr(self, 'initialized'):
self.connection_params = {
'host': host,

2
docker/00-roles.sql Normal file
View file

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

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
docs/screenshots/models.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View file

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

View file

@ -1,15 +1,14 @@
# ui/main_window.py
import sys
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
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
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction, QFont
from typing import Dict, Any
import logging
# Импортируем наш модуль БД
@ -190,6 +189,9 @@ 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):
@ -214,6 +216,9 @@ 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):
"""Добавление или обновление цены"""
@ -425,6 +430,9 @@ 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):
@ -442,6 +450,9 @@ 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=""):
"""Загрузка списка моделей"""
@ -450,6 +461,9 @@ 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):
@ -508,6 +522,9 @@ 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,24 +5,21 @@ 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,
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 PyQt6.QtGui import QAction, 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, Dict, Any, Optional
import logging
from typing import List
from contextlib import contextmanager
logger = logging.getLogger(__name__)
@ -48,8 +45,13 @@ class Database:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host='localhost', port=5432, database='mobile_devices_db',
user='admin', password='password'):
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')
if not hasattr(self, 'initialized'):
self.connection_params = {
'host': host,
@ -503,6 +505,9 @@ 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):
@ -527,6 +532,9 @@ 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):
"""Добавление или обновление цены"""
@ -737,6 +745,9 @@ 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):
@ -754,6 +765,9 @@ 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=""):
"""Загрузка списка моделей"""
@ -762,6 +776,9 @@ 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):
@ -820,6 +837,9 @@ 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,8 +1,9 @@
import os
import sys
import pandas as pd
import psycopg2
from psycopg2.extras import execute_values
import re
from typing import Optional, Dict, Tuple
from typing import Optional, Dict
import logging
# Настройка логирования
@ -245,12 +246,13 @@ class MobileDataImporter:
# Использование скрипта
if __name__ == "__main__":
# Конфигурация подключения к БД
# Параметры подключения — из переменных окружения DB_* (по умолчанию — локальная разработка)
db_config = {
'host': 'localhost',
'port': 5432,
'database': 'mobile_devices_db',
'user': 'admin',
'password': 'password' # Замените на ваш пароль
'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'),
}
# Создаем импортер и выполняем импорт
@ -258,6 +260,9 @@ if __name__ == "__main__":
try:
importer.connect()
importer.import_data('Mobiles Dataset 2025.csv') # Укажите путь к вашему файлу
# Датасет лежит в корне репозитория; другой файл можно передать первым аргументом
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)
finally:
importer.disconnect()