feat(modefied)

This commit is contained in:
Black_Raven committed 2026-03-17 12:08:19 +03:00
1 parent d705f2f9d3
commit e3110c3897
12 files changed
+237 -63

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
from flask import Blueprint, request
type = Blueprint("type", __name__)
@product.route('/types', method = ['GET'])
def GetTypes():
return
+5
View File
@@ -0,0 +1,5 @@
from flask import Blueprint, request
product = Blueprint("product", __name__)
@product.route('')
+7 -3
View File
@@ -1,10 +1,14 @@
from sys import exit from sys import exit
from flask import Flask from flask import Flask
from db.InitializeDB import init_database from db.InitializeDB import init_database
from db.InitializeDBData import add_products from json import dumps
from db.InitializeDBData import init_tables
from db.handlers.catigory_handler import GetAllCategories
init_database() init_tables()
add_products() categories = GetAllCategories()
print(dumps(categories))
app = Flask(__name__) app = Flask(__name__)
+13 -16
View File
@@ -2,14 +2,14 @@ import re
import sqlite3 import sqlite3
from loguru import logger as log from loguru import logger as log
def add_category(conn, name, description=None, parent_id=None, image_path=None, page_number=1): def add_category(conn, name, description=None, parent_id=None, image_path=None):
cursor = conn.cursor() cursor = conn.cursor()
try: try:
cursor.execute(''' cursor.execute('''
INSERT OR IGNORE INTO categories (name, description, parent_id, image_path, page_number) INSERT OR IGNORE INTO categories (name, description, parent_id, image_path)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?)
''', (name, description, parent_id, image_path, page_number)) ''', (name, description, parent_id, image_path))
cursor.execute('SELECT id FROM categories WHERE name = ?', (name,)) cursor.execute('SELECT id FROM categories WHERE name = ?', (name,))
result = cursor.fetchone() result = cursor.fetchone()
@@ -59,8 +59,8 @@ def parse_product_data(product_text):
'stiffness_class': stiffness_class 'stiffness_class': stiffness_class
} }
def add_product(conn, product_text, category_id, image_path=None, page_number=1): def add_product(conn, product_text, category_id, image_path=None):
"""Добавление товара с парсингом параметров, изображением и номером страницы""" """Добавление товара с парсингом параметров и изображением"""
cursor = conn.cursor() cursor = conn.cursor()
product_data = parse_product_data(product_text) product_data = parse_product_data(product_text)
@@ -70,8 +70,8 @@ def add_product(conn, product_text, category_id, image_path=None, page_number=1)
INSERT INTO products ( INSERT INTO products (
name, full_name, description, category_id, name, full_name, description, category_id,
diameter, wall_thickness, length, color, diameter, wall_thickness, length, color,
application_type, stiffness_class, image_path, page_number application_type, stiffness_class, image_path
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', ( ''', (
product_data['short_name'], product_data['short_name'],
product_data['full_name'], product_data['full_name'],
@@ -83,8 +83,7 @@ def add_product(conn, product_text, category_id, image_path=None, page_number=1)
product_data['color'], product_data['color'],
product_data['application_type'], product_data['application_type'],
product_data['stiffness_class'], product_data['stiffness_class'],
image_path, image_path
page_number
)) ))
product_id = cursor.lastrowid product_id = cursor.lastrowid
@@ -122,10 +121,9 @@ def get_categories_by_page(conn, page_number=1, items_per_page=9):
cursor.execute(''' cursor.execute('''
SELECT * FROM categories SELECT * FROM categories
WHERE page_number = ?
ORDER BY id ORDER BY id
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
''', (page_number, items_per_page, offset)) ''', (items_per_page, offset))
return cursor.fetchall() return cursor.fetchall()
@@ -137,17 +135,16 @@ def get_products_by_page(conn, page_number=1, items_per_page=9, category_id=None
if category_id: if category_id:
cursor.execute(''' cursor.execute('''
SELECT * FROM products SELECT * FROM products
WHERE page_number = ? AND category_id = ? WHERE category_id = ?
ORDER BY id ORDER BY id
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
''', (page_number, category_id, items_per_page, offset)) ''', (category_id, items_per_page, offset))
else: else:
cursor.execute(''' cursor.execute('''
SELECT * FROM products SELECT * FROM products
WHERE page_number = ?
ORDER BY id ORDER BY id
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
''', (page_number, items_per_page, offset)) ''', (items_per_page, offset))
return cursor.fetchall() return cursor.fetchall()
-4
View File
@@ -13,7 +13,6 @@ def init_database(db_name='pipe_store.db'):
description TEXT, description TEXT,
parent_id INTEGER, parent_id INTEGER,
image_path TEXT, image_path TEXT,
page_number INTEGER DEFAULT 1, -- Новое поле: номер страницы для пагинации
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (parent_id) REFERENCES categories (id) FOREIGN KEY (parent_id) REFERENCES categories (id)
) )
@@ -33,7 +32,6 @@ def init_database(db_name='pipe_store.db'):
application_type TEXT, -- внутренняя/наружная application_type TEXT, -- внутренняя/наружная
stiffness_class TEXT, -- SN4, SN2 и т.д. stiffness_class TEXT, -- SN4, SN2 и т.д.
image_path TEXT, image_path TEXT,
page_number INTEGER DEFAULT 1, -- Новое поле: номер страницы для пагинации
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories (id) FOREIGN KEY (category_id) REFERENCES categories (id)
) )
@@ -63,8 +61,6 @@ def init_database(db_name='pipe_store.db'):
cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_diameter ON products(diameter)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_diameter ON products(diameter)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_length ON products(length)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_length ON products(length)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_page ON products(page_number)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_categories_page ON categories(page_number)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_product_filters_key_value ON product_filters(filter_key, value)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_product_filters_key_value ON product_filters(filter_key, value)')
conn.commit() conn.commit()
+23 -36
View File
@@ -19,44 +19,35 @@ def init_tables(db_name='pipe_store.db'):
log.info("Начинаем инициализацию базы категорий...") log.info("Начинаем инициализацию базы категорий...")
# Добавляем категории с распределением по страницам
# Страница 1
main_category_id = add_category( main_category_id = add_category(
conn, conn,
"Трубы ПВХ", "Трубы ПВХ",
"Трубы ПВХ для канализационных систем", "Трубы ПВХ для канализационных систем",
image_path="/images/categories/pvc_pipes_main.jpg", image_path="/images/categories/pvc_pipes_main.jpg"
page_number=1
) )
# Страница 1
internal_category_id = add_category( internal_category_id = add_category(
conn, conn,
"Для внутренней канализации", "Для внутренней канализации",
"Трубы для внутренней канализационной системы", "Трубы для внутренней канализационной системы",
main_category_id, main_category_id,
image_path="/images/categories/internal_pipes.jpg", image_path="/images/categories/internal_pipes.jpg"
page_number=1
) )
# Страница 1
external_category_id = add_category( external_category_id = add_category(
conn, conn,
"Для наружной канализации", "Для наружной канализации",
"Трубы для наружной канализационной системы", "Трубы для наружной канализационной системы",
main_category_id, main_category_id,
image_path="/images/categories/external_pipes.jpg", image_path="/images/categories/external_pipes.jpg"
page_number=1
) )
# Дополнительные категории для демонстрации пагинации (страница 2)
add_category( add_category(
conn, conn,
"Фитинги для ПВХ труб", "Фитинги для ПВХ труб",
"Соединительные элементы для ПВХ труб", "Соединительные элементы для ПВХ труб",
main_category_id, main_category_id,
image_path="/images/categories/fittings.jpg", image_path="/images/categories/fittings.jpg"
page_number=2
) )
add_category( add_category(
@@ -64,8 +55,7 @@ def init_tables(db_name='pipe_store.db'):
"Колена и отводы", "Колена и отводы",
"Угловые соединения для ПВХ труб", "Угловые соединения для ПВХ труб",
main_category_id, main_category_id,
image_path="/images/categories/elbows.jpg", image_path="/images/categories/elbows.jpg"
page_number=2
) )
log.info("Категории успешно созданы") log.info("Категории успешно созданы")
@@ -79,43 +69,40 @@ def init_tables(db_name='pipe_store.db'):
log.info("Фильтры успешно добавлены") log.info("Фильтры успешно добавлены")
# Данные продуктов с распределением по страницам # Данные продуктов
products_data = [ products_data = [
# Страница 1 - внутренние трубы # Внутренние трубы
{"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х500", "image": "/images/products/internal_110_500.jpg", "page": 1}, {"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х500", "image": "/images/products/internal_110_500.jpg"},
{"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х1000", "image": "/images/products/internal_110_1000.jpg", "page": 1}, {"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х1000", "image": "/images/products/internal_110_1000.jpg"},
{"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х1500", "image": "/images/products/internal_110_1500.jpg", "page": 1}, {"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х1500", "image": "/images/products/internal_110_1500.jpg"},
{"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х2000", "image": "/images/products/internal_110_2000.jpg", "page": 1}, {"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х2000", "image": "/images/products/internal_110_2000.jpg"},
{"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х3000", "image": "/images/products/internal_110_3000.jpg", "page": 1}, {"text": "Труба ПВХ для ВНУТРЕННЕЙ канализации (цвет серый), с внутренней структурной стенкой толщиной 2,2 мм 110х2,2х3000", "image": "/images/products/internal_110_3000.jpg"},
# Страница 1 - наружные трубы # Наружные трубы
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х500", "image": "/images/products/external_110_sn4_500.jpg", "page": 1}, {"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х500", "image": "/images/products/external_110_sn4_500.jpg"},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х1000", "image": "/images/products/external_110_sn4_1000.jpg", "page": 1}, {"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х1000", "image": "/images/products/external_110_sn4_1000.jpg"},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х2000", "image": "/images/products/external_110_sn4_2000.jpg", "page": 1}, {"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х2000", "image": "/images/products/external_110_sn4_2000.jpg"},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х3000", "image": "/images/products/external_110_sn4_3000.jpg"},
# Страница 2 - оставшиеся наружные трубы {"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х4000", "image": "/images/products/external_110_sn4_4000.jpg"},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х3000", "image": "/images/products/external_110_sn4_3000.jpg", "page": 2}, {"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN2 160х3,6х1000", "image": "/images/products/external_160_sn2_1000.jpg"},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN4 110х3,2х4000", "image": "/images/products/external_110_sn4_4000.jpg", "page": 2}, {"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN2 160х3,6х2000", "image": "/images/products/external_160_sn2_2000.jpg"},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN2 160х3,6х1000", "image": "/images/products/external_160_sn2_1000.jpg", "page": 2}, {"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN2 160х3,6х3000", "image": "/images/products/external_160_sn2_3000.jpg"},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN2 160х3,6х2000", "image": "/images/products/external_160_sn2_2000.jpg", "page": 2},
{"text": "Труба ПВХ для НАРУЖНОЙ канализации (цвет оранжево-коричневый), с внутренней структурной стенкой, класс жесткости SN2 160х3,6х3000", "image": "/images/products/external_160_sn2_3000.jpg", "page": 2},
] ]
added_products = 0 added_products = 0
for product_info in products_data: for product_info in products_data:
product_text = product_info["text"] product_text = product_info["text"]
image_path = product_info["image"] image_path = product_info["image"]
page_number = product_info["page"]
if "ВНУТРЕННЕЙ" in product_text: if "ВНУТРЕННЕЙ" in product_text:
category_id = internal_category_id category_id = internal_category_id
else: else:
category_id = external_category_id category_id = external_category_id
product_id = add_product(conn, product_text, category_id, image_path, page_number) product_id = add_product(conn, product_text, category_id, image_path)
if product_id: if product_id:
added_products += 1 added_products += 1
log.info(f"Добавлен товар: {product_text[:50]}... (страница {page_number})") log.info(f"Добавлен товар: {product_text[:50]}...")
else: else:
log.error(f"Ошибка при добавлении товара: {product_text}") log.error(f"Ошибка при добавлении товара: {product_text}")
+23
View File
@@ -0,0 +1,23 @@
import sqlite3
from module.category import Category
def GetAllCategories(db_name: str = 'pipe_store.db', parent_id: int = 1) -> list[Category]:
conn = sqlite3.connect(db_name)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
categories = []
cursor.execute("SELECT id, name, description, image_path FROM categories WHERE parent_id = ?", [parent_id])
for row in cursor.fetchall():
category = Category(
id = row["id"],
name = row["name"],
description= row["description"],
image_path= row["image_path"],
)
categories.append(category.model_dump())
conn.close()
return categories
+11
View File
@@ -0,0 +1,11 @@
import sqlite3
from module.product import Product
def get_products(db_name: str = 'pipe_store.db', parent_id: int = 1) -> List[Product]:
conn = sqlite3.connect(db_name)
conn.row_factory = sqlite3.Row
transaction = conn.cursor()
products = []
transaction.execute("SELECT ... FROM products")
+2 -4
View File
@@ -2,15 +2,11 @@ from pydantic import BaseModel
from typing import Optional from typing import Optional
from datetime import datetime from datetime import datetime
class Category(BaseModel): class Category(BaseModel):
id: int id: int
name: str name: str
description: Optional[str] = None description: Optional[str] = None
parent_id: Optional[int] = None
image_path: Optional[str] = None image_path: Optional[str] = None
page_number: int = 1
created_at: datetime
class Config: class Config:
from_attributes = True from_attributes = True
@@ -22,6 +18,7 @@ class CategoryCreate(BaseModel):
parent_id: Optional[int] = None parent_id: Optional[int] = None
image_path: Optional[str] = None image_path: Optional[str] = None
page_number: int = 1 page_number: int = 1
created_at: datetime = None
class CategoryUpdate(BaseModel): class CategoryUpdate(BaseModel):
@@ -30,3 +27,4 @@ class CategoryUpdate(BaseModel):
parent_id: Optional[int] = None parent_id: Optional[int] = None
image_path: Optional[str] = None image_path: Optional[str] = None
page_number: Optional[int] = None page_number: Optional[int] = None
created_at: datetime = None
+5
View File
@@ -0,0 +1,5 @@
python-lsp-server[all]
pyright
ruff-lsp
ruff
+14
View File
@@ -0,0 +1,14 @@
import os
from dotenv import load_dotenv
from loguru import logger as log
DOTENV_PATH = '.env'
def initializeENV() -> bool:
if os.path.exists(DOTENV_PATH):
load_dotenv(DOTENV_PATH)
log.info('.env is loaded')
return True
else:
log.error('.env isn`t loaded')
return False
+127
View File
@@ -0,0 +1,127 @@
import os
import smtplib
import logging
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from pathlib import Path
# Настройка логирования
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def send_pdf_to_email(recipient_email, pdf_filename=None, subject="PDF документ", body_text=None):
# Настройки Яндекс.Почты
SMTP_SERVER = "smtp.yandex.ru"
SMTP_PORT = 587
SENDER_EMAIL = "w0ron.black@yandex.ru"
# Получаем пароль из переменной окружения
SENDER_PASSWORD = os.environ.get('EMAIL_PASS')
if not SENDER_PASSWORD:
logger.error("Не найдена переменная окружения EMAIL_PASS")
return False
# Путь к папке с PDF файлами
data_folder = Path("./data")
if not data_folder.exists():
logger.error(f"Папка {data_folder.absolute()} не существует")
return False
# Определяем файл для отправки
if pdf_filename:
pdf_path = data_folder / pdf_filename
if not pdf_path.exists():
logger.error(f"Файл {pdf_filename} не найден в папке {data_folder}")
return False
else:
# Ищем первый PDF файл в папке
pdf_files = list(data_folder.glob("*.pdf"))
if not pdf_files:
logger.error("В папке нет PDF файлов")
return False
pdf_path = pdf_files[0]
logger.info(f"Найден файл: {pdf_path.name}")
# Проверяем размер файла (Яндекс ограничивает вложения до 30 МБ)
file_size_mb = pdf_path.stat().st_size / (1024 * 1024)
if file_size_mb > 30:
logger.error(f"Файл слишком большой: {file_size_mb:.2f} МБ (максимум 30 МБ)")
return False
# Текст письма по умолчанию
if body_text is None:
body_text = f"""
Здравствуйте!
Во вложении находится PDF документ: {pdf_path.name}
С уважением,
Отправитель
"""
# Создаем сообщение
msg = MIMEMultipart()
msg['From'] = SENDER_EMAIL
msg['To'] = recipient_email
msg['Subject'] = subject
msg['Reply-To'] = SENDER_EMAIL
# Добавляем текст письма с правильной кодировкой
msg.attach(MIMEText(body_text, 'plain', 'utf-8'))
try:
# Добавляем PDF файл
with open(pdf_path, 'rb') as file:
pdf_attachment = MIMEApplication(file.read(), _subtype='pdf')
pdf_attachment.add_header(
'Content-Disposition',
'attachment',
filename=('utf-8', '', pdf_path.name) # Поддержка русских имен файлов
)
msg.attach(pdf_attachment)
# Подключаемся к серверу Яндекс.Почты
logger.info("Подключение к серверу Яндекс.Почты...")
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
server.starttls()
server.login(SENDER_EMAIL, SENDER_PASSWORD)
# Отправляем письмо
server.send_message(msg)
server.quit()
logger.info(f"✅ Письмо успешно отправлено на {recipient_email}")
logger.info(f"📎 Файл: {pdf_path.name} ({file_size_mb:.2f} МБ)")
return True
except smtplib.SMTPAuthenticationError:
logger.error("❌ Ошибка аутентификации. Проверьте пароль в переменной EMAIL_PASS")
return False
except smtplib.SMTPException as e:
logger.error(f"❌ Ошибка SMTP: {e}")
return False
except Exception as e:
logger.error(f"❌ Неожиданная ошибка: {e}")
return False
def send_pdf_to_multiple(recipient_emails, pdf_filename=None, subject="PDF документ", body_text=None):
"""
Отправляет PDF файл нескольким получателям
Args:
recipient_emails (list): Список email получателей
pdf_filename (str, optional): Имя PDF файла
subject (str): Тема письма
body_text (str): Текст письма
Returns:
dict: Словарь с результатами для каждого получателя
"""
results = {}
for email in recipient_emails:
results[email] = send_pdf_to_email(email, pdf_filename, subject, body_text)
return results