fix(db): Added auto generating slug for products

This commit is contained in:
Black_Raven committed 2026-07-26 20:16:57 +03:00
1 parent 0c15ff3059
commit 098a6aa38a
4 files changed
+176 -37

No files matched your search

+23 -4
View File
@@ -1,6 +1,7 @@
import re import re
import sqlite3 import sqlite3
from loguru import logger as log from loguru import logger as log
from utils.slugify import slugify
def add_category(conn, name, description=None, parent_id=None, image_path=None): def add_category(conn, name, description=None, parent_id=None, image_path=None):
cursor = conn.cursor() cursor = conn.cursor()
@@ -76,7 +77,7 @@ def parse_product_data(product_text):
def add_product(conn, product_text, category_id, image_path=None, def add_product(conn, product_text, category_id, image_path=None,
diameter=None, wall_thickness=None, length=None, diameter=None, wall_thickness=None, length=None,
color=None, application_type=None, stiffness_class=None, color=None, application_type=None, stiffness_class=None,
fitting_type=None, angle=None): fitting_type=None, angle=None, slug=None, artikul=None):
""" """
Добавление товара с возможностью явного указания параметров Добавление товара с возможностью явного указания параметров
Если параметры не указаны, они парсятся из product_text Если параметры не указаны, они парсятся из product_text
@@ -113,13 +114,30 @@ def add_product(conn, product_text, category_id, image_path=None,
description = ", ".join(description_parts) description = ", ".join(description_parts)
if fitting_type:
slug_source = product_text
else:
slug_parts = [product_text]
if stiffness_class:
slug_parts.append(stiffness_class)
if diameter:
slug_parts.append(f"d{diameter}")
if wall_thickness:
slug_parts.append(f"stenka{int(wall_thickness)}")
if length and length > 0:
slug_parts.append(f"l{length}")
slug_source = " ".join(slug_parts)
slug_value = slug or slugify(slug_source)
if artikul:
slug_value = f"{slug_value}-{artikul.lower()}"
try: try:
cursor.execute(''' cursor.execute('''
INSERT INTO products ( INSERT INTO products (
name, description, category_id, name, description, category_id,
diameter, wall_thickness, length, color, diameter, wall_thickness, length, color,
application_type, stiffness_class, image_path application_type, stiffness_class, image_path, slug
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', ( ''', (
product_text, product_text,
description, description,
@@ -130,7 +148,8 @@ def add_product(conn, product_text, category_id, image_path=None,
color, color,
application_type, application_type,
stiffness_class, stiffness_class,
image_path image_path,
slug_value
)) ))
product_id = cursor.lastrowid product_id = cursor.lastrowid
+41 -2
View File
@@ -1,5 +1,6 @@
import sqlite3 import sqlite3
from loguru import logger as log from loguru import logger as log
from utils.slugify import slugify
def init_database(db_name='pipe_store.db'): def init_database(db_name='pipe_store.db'):
@@ -31,6 +32,7 @@ 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,
slug TEXT,
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)
) )
@@ -62,6 +64,13 @@ def init_database(db_name='pipe_store.db'):
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_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)')
# Миграция: добавляем колонку slug, если её нет
cursor.execute("PRAGMA table_info(products)")
columns = {row[1] for row in cursor.fetchall()}
if 'slug' not in columns:
cursor.execute("ALTER TABLE products ADD COLUMN slug TEXT")
log.info("Migration: added 'slug' column to products")
# Миграция: переименовываем full_name → name # Миграция: переименовываем full_name → name
cursor.execute("PRAGMA table_info(products)") cursor.execute("PRAGMA table_info(products)")
columns = {row[1] for row in cursor.fetchall()} columns = {row[1] for row in cursor.fetchall()}
@@ -80,11 +89,12 @@ def init_database(db_name='pipe_store.db'):
application_type TEXT, application_type TEXT,
stiffness_class TEXT, stiffness_class TEXT,
image_path TEXT, image_path TEXT,
slug TEXT,
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)
); );
INSERT INTO products_new (id, name, description, category_id, diameter, wall_thickness, length, color, application_type, stiffness_class, image_path, created_at) INSERT INTO products_new (id, name, description, category_id, diameter, wall_thickness, length, color, application_type, stiffness_class, image_path, slug, created_at)
SELECT id, full_name, description, category_id, diameter, wall_thickness, length, color, application_type, stiffness_class, image_path, created_at FROM products; SELECT id, full_name, description, category_id, diameter, wall_thickness, length, color, application_type, stiffness_class, image_path, slug, created_at FROM products;
DROP TABLE products; DROP TABLE products;
ALTER TABLE products_new RENAME TO products; ALTER TABLE products_new RENAME TO products;
CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id); CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id);
@@ -93,6 +103,35 @@ def init_database(db_name='pipe_store.db'):
''') ''')
log.info("Migrated products table: renamed 'full_name' → 'name'") log.info("Migrated products table: renamed 'full_name' → 'name'")
# Миграция: заполняем slug для старых товаров
cursor.execute("""
SELECT p.id, p.name, p.diameter, p.wall_thickness, p.length,
p.stiffness_class, p.application_type,
pf.value as fitting_type
FROM products p
LEFT JOIN product_filters pf ON pf.product_id = p.id AND pf.filter_key = 'fitting_type'
WHERE p.slug IS NULL OR p.slug = ''
""")
rows = cursor.fetchall()
for row in rows:
id_, name, diameter, wall_thickness, length, stiffness_class, application_type, fitting_type = row
if fitting_type:
slug_source = name
else:
slug_parts = [name]
if stiffness_class:
slug_parts.append(stiffness_class)
if diameter:
slug_parts.append(f"d{diameter}")
if wall_thickness:
slug_parts.append(f"stenka{int(wall_thickness)}")
if length and length > 0:
slug_parts.append(f"l{length}")
slug_source = " ".join(slug_parts)
slug = slugify(slug_source)
cursor.execute("UPDATE products SET slug = ? WHERE id = ?", (slug, id_))
log.info(f"Backfill slug for product {id_}: {slug}")
conn.commit() conn.commit()
conn.close() conn.close()
log.success("База данных успешно инициализирована!") log.success("База данных успешно инициализирована!")
+32 -30
View File
@@ -108,21 +108,22 @@ def init_tables(db_name='pipe_store.db'):
# 1. Внутренние трубы # 1. Внутренние трубы
internal_pipes = [ internal_pipes = [
("110х2,2х500", 110, 2.2, 500, "серый", "внутренняя", None), ("110х2,2х500", 110, 2.2, 500, "серый", "внутренняя", None, "RK011022500"),
("110х2,2х1000", 110, 2.2, 1000, "серый", "внутренняя", None), ("110х2,2х1000", 110, 2.2, 1000, "серый", "внутренняя", None, "RK0110221000"),
("110х2,2х1500", 110, 2.2, 1500, "серый", "внутренняя", None), ("110х2,2х1500", 110, 2.2, 1500, "серый", "внутренняя", None, "RK0110221500"),
("110х2,2х2000", 110, 2.2, 2000, "серый", "внутренняя", None), ("110х2,2х2000", 110, 2.2, 2000, "серый", "внутренняя", None, "RK0110222000"),
("110х2,2х3000", 110, 2.2, 3000, "серый", "внутренняя", None), ("110х2,2х3000", 110, 2.2, 3000, "серый", "внутренняя", None, "RK0110223000"),
] ]
for size, diameter, thickness, length, color, app_type, stiffness in internal_pipes: for size, diameter, thickness, length, color, app_type, stiffness, artikul in internal_pipes:
full_name = f"Труба НПВХ {diameter}x{thickness}x{length} мм внутренняя" full_name = f"Труба НПВХ {diameter}x{thickness}x{length} мм внутренняя"
# Замена на внешнюю ссылку # Замена на внешнюю ссылку
image_url = f"{base_url}/products/internal_{diameter}_{length}.png" image_url = f"{base_url}/products/internal_{diameter}_{length}.png"
product_id = add_product( product_id = add_product(
conn, full_name, internal_category_id, image_url, conn, full_name, internal_category_id, image_url,
diameter, thickness, length, color, app_type, stiffness diameter, thickness, length, color, app_type, stiffness,
artikul=artikul
) )
if product_id: if product_id:
added_products += 1 added_products += 1
@@ -130,30 +131,30 @@ def init_tables(db_name='pipe_store.db'):
# 2. Наружные трубы # 2. Наружные трубы
external_pipes = [ external_pipes = [
("110х2,3х500", 110, 2.3, 500, "оранжево-коричневый", "наружная", None), ("110х2,3х500", 110, 2.3, 500, "оранжево-коричневый", "наружная", None, "RK111023500"),
("110х2,3х1000", 110, 2.3, 1000, "оранжево-коричневый", "наружная", None), ("110х2,3х1000", 110, 2.3, 1000, "оранжево-коричневый", "наружная", None, "RK1110231000"),
("110х2,3х2000", 110, 2.3, 2000, "оранжево-коричневый", "наружная", None), ("110х2,3х2000", 110, 2.3, 2000, "оранжево-коричневый", "наружная", None, "RK1110232000"),
("110х2,3х3000", 110, 2.3, 3000, "оранжево-коричневый", "наружная", None), ("110х2,3х3000", 110, 2.3, 3000, "оранжево-коричневый", "наружная", None, "RK1110233000"),
("110х2,3х4000", 110, 2.3, 4000, "оранжево-коричневый", "наружная", None), ("110х2,3х4000", 110, 2.3, 4000, "оранжево-коричневый", "наружная", None, "RK1110234000"),
("110х2,7х500 SN2", 110, 2.7, 500, "оранжево-коричневый", "наружная", "SN2"), ("110х2,7х500 SN2", 110, 2.7, 500, "оранжево-коричневый", "наружная", "SN2", "RK111027500"),
("110х2,7х1000 SN2", 110, 2.7, 1000, "оранжево-коричневый", "наружная", "SN2"), ("110х2,7х1000 SN2", 110, 2.7, 1000, "оранжево-коричневый", "наружная", "SN2", "RK1110271000"),
("110х2,7х2000 SN2", 110, 2.7, 2000, "оранжево-коричневый", "наружная", "SN2"), ("110х2,7х2000 SN2", 110, 2.7, 2000, "оранжево-коричневый", "наружная", "SN2", "RK1110272000"),
("110х2,7х3000 SN2", 110, 2.7, 3000, "оранжево-коричневый", "наружная", "SN2"), ("110х2,7х3000 SN2", 110, 2.7, 3000, "оранжево-коричневый", "наружная", "SN2", "RK1110273000"),
("110х2,7х4000 SN2", 110, 2.7, 4000, "оранжево-коричневый", "наружная", "SN2"), ("110х2,7х4000 SN2", 110, 2.7, 4000, "оранжево-коричневый", "наружная", "SN2", "RK1110274000"),
("110х3,2х500 SN4", 110, 3.2, 500, "оранжево-коричневый", "наружная", "SN4"), ("110х3,2х500 SN4", 110, 3.2, 500, "оранжево-коричневый", "наружная", "SN4", "RK111032500"),
("110х3,2х1000 SN4", 110, 3.2, 1000, "оранжево-коричневый", "наружная", "SN4"), ("110х3,2х1000 SN4", 110, 3.2, 1000, "оранжево-коричневый", "наружная", "SN4", "RK1110321000"),
("110х3,2х2000 SN4", 110, 3.2, 2000, "оранжево-коричневый", "наружная", "SN4"), ("110х3,2х2000 SN4", 110, 3.2, 2000, "оранжево-коричневый", "наружная", "SN4", "RK1110322000"),
("110х3,2х3000 SN4", 110, 3.2, 3000, "оранжево-коричневый", "наружная", "SN4"), ("110х3,2х3000 SN4", 110, 3.2, 3000, "оранжево-коричневый", "наружная", "SN4", "RK1110323000"),
("110х3,2х4000 SN4", 110, 3.2, 4000, "оранжево-коричневый", "наружная", "SN4"), ("110х3,2х4000 SN4", 110, 3.2, 4000, "оранжево-коричневый", "наружная", "SN4", "RK1110324000"),
("160х3,6х1000 SN2", 160, 3.6, 1000, "оранжево-коричневый", "наружная", "SN2"), ("160х3,6х1000 SN2", 160, 3.6, 1000, "оранжево-коричневый", "наружная", "SN2", "RK1160361000"),
("160х3,6х2000 SN2", 160, 3.6, 2000, "оранжево-коричневый", "наружная", "SN2"), ("160х3,6х2000 SN2", 160, 3.6, 2000, "оранжево-коричневый", "наружная", "SN2", "RK1160362000"),
("160х3,6х3000 SN2", 160, 3.6, 3000, "оранжево-коричневый", "наружная", "SN2"), ("160х3,6х3000 SN2", 160, 3.6, 3000, "оранжево-коричневый", "наружная", "SN2", "RK1160363000"),
("160х4,0х1000 SN4", 160, 4.0, 1000, "оранжево-коричневый", "наружная", "SN4"), ("160х4,0х1000 SN4", 160, 4.0, 1000, "оранжево-коричневый", "наружная", "SN4", "RK1160401000"),
("160х4,0х2000 SN4", 160, 4.0, 2000, "оранжево-коричневый", "наружная", "SN4"), ("160х4,0х2000 SN4", 160, 4.0, 2000, "оранжево-коричневый", "наружная", "SN4", "RK1160402000"),
("160х4,0х3000 SN4", 160, 4.0, 3000, "оранжево-коричневый", "наружная", "SN4"), ("160х4,0х3000 SN4", 160, 4.0, 3000, "оранжево-коричневый", "наружная", "SN4", "RK1160403000"),
] ]
for size, diameter, thickness, length, color, app_type, stiffness in external_pipes: for size, diameter, thickness, length, color, app_type, stiffness, artikul in external_pipes:
stiffness_text = f", класс жесткости {stiffness}" if stiffness else "" stiffness_text = f", класс жесткости {stiffness}" if stiffness else ""
full_name = f"Труба НПВХ {diameter}x{thickness}x{length} мм наружная" full_name = f"Труба НПВХ {diameter}x{thickness}x{length} мм наружная"
# Замена на внешнюю ссылку # Замена на внешнюю ссылку
@@ -162,7 +163,8 @@ def init_tables(db_name='pipe_store.db'):
product_id = add_product( product_id = add_product(
conn, full_name, external_category_id, image_url, conn, full_name, external_category_id, image_url,
diameter, thickness, length, color, app_type, stiffness diameter, thickness, length, color, app_type, stiffness,
artikul=artikul
) )
if product_id: if product_id:
added_products += 1 added_products += 1
+80 -1
View File
@@ -101,6 +101,7 @@ def get_products_with_filters(
items.append({ items.append({
"id": row['id'], "id": row['id'],
"name": row['name'], "name": row['name'],
"slug": row['slug'],
"description": row['description'], "description": row['description'],
"categoryId": row['category_id'], "categoryId": row['category_id'],
"images": [row['image_path']] if row['image_path'] else ["/images/products/default.jpg"], "images": [row['image_path']] if row['image_path'] else ["/images/products/default.jpg"],
@@ -144,7 +145,7 @@ def get_product_by_id(product_id: int, db_name: str = 'pipe_store.db') -> Option
SELECT p.id, p.name, p.description, SELECT p.id, p.name, p.description,
p.diameter, p.wall_thickness, p.length, p.color, p.diameter, p.wall_thickness, p.length, p.color,
p.application_type, p.stiffness_class, p.image_path, p.application_type, p.stiffness_class, p.image_path,
p.created_at, p.category_id p.slug, p.created_at, p.category_id
FROM products p FROM products p
WHERE p.id = ? WHERE p.id = ?
""", (product_id,)) """, (product_id,))
@@ -192,6 +193,7 @@ def get_product_by_id(product_id: int, db_name: str = 'pipe_store.db') -> Option
return { return {
"id": row['id'], "id": row['id'],
"name": row['name'], "name": row['name'],
"slug": row['slug'],
"description": full_description, "description": full_description,
"price": None, "price": None,
"categoryId": row['category_id'], "categoryId": row['category_id'],
@@ -207,6 +209,82 @@ def get_product_by_id(product_id: int, db_name: str = 'pipe_store.db') -> Option
finally: finally:
conn.close() conn.close()
def get_product_by_slug(slug: str, db_name: str = 'pipe_store.db') -> Optional[Dict[str, Any]]:
"""
Получить товар по slug с полной информацией
"""
conn = sqlite3.connect(db_name)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
try:
cursor.execute("""
SELECT p.id, p.name, p.description,
p.diameter, p.wall_thickness, p.length, p.color,
p.application_type, p.stiffness_class, p.image_path,
p.slug, p.created_at, p.category_id
FROM products p
WHERE p.slug = ?
""", (slug,))
row = cursor.fetchone()
if not row:
return None
product_id = row['id']
images = get_product_images(product_id, row['image_path'], conn)
cursor.execute("""
SELECT filter_key, value
FROM product_filters
WHERE product_id = ?
""", (product_id,))
extra_attributes = {}
for filter_row in cursor.fetchall():
extra_attributes[filter_row['filter_key']] = filter_row['value']
full_description = generate_full_description(row)
size = None
if row['diameter'] and row['wall_thickness'] and row['length'] and row['length'] > 0:
size = f"{row['diameter']}x{row['wall_thickness']}x{row['length']}"
amount = get_pack_amount(product_id, row['diameter'])
attributes = {
"color": row['color'],
"amount": amount,
"size": size,
"diameter": str(row['diameter']) if row['diameter'] else None,
"wall_thickness": str(row['wall_thickness']) if row['wall_thickness'] else None,
"length": str(row['length']) if row['length'] and row['length'] > 0 else None,
"stiffness_class": row['stiffness_class'],
"application_type": row['application_type'],
}
attributes.update(extra_attributes)
return {
"id": row['id'],
"name": row['name'],
"slug": row['slug'],
"description": full_description,
"price": None,
"categoryId": row['category_id'],
"images": images,
"attributes": attributes,
"inStock": check_stock(product_id, conn),
"created_at": row['created_at']
}
except sqlite3.Error as e:
log.error(f"Ошибка при получении товара по slug {slug}: {e}")
return None
finally:
conn.close()
def get_product_images(product_id: int, main_image: Optional[str], conn: sqlite3.Connection) -> List[str]: def get_product_images(product_id: int, main_image: Optional[str], conn: sqlite3.Connection) -> List[str]:
"""Получить список изображений товара""" """Получить список изображений товара"""
images = [] images = []
@@ -278,6 +356,7 @@ def get_product_list(page=1, limit=100, category_id=None):
products.append({ products.append({
'id': row['id'], 'id': row['id'],
'name': row['name'], 'name': row['name'],
'slug': row['slug'],
'description': row['description'] 'description': row['description']
}) })
return products return products