fix(db): Added auto generating slug for products
This commit is contained in:
1 parent
0c15ff3059
commit
098a6aa38a
4 files changed
+176
-37
No files matched your search
+23
-4
@@ -1,6 +1,7 @@
|
||||
import re
|
||||
import sqlite3
|
||||
from loguru import logger as log
|
||||
from utils.slugify import slugify
|
||||
|
||||
def add_category(conn, name, description=None, parent_id=None, image_path=None):
|
||||
cursor = conn.cursor()
|
||||
@@ -76,7 +77,7 @@ def parse_product_data(product_text):
|
||||
def add_product(conn, product_text, category_id, image_path=None,
|
||||
diameter=None, wall_thickness=None, length=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
|
||||
@@ -113,13 +114,30 @@ def add_product(conn, product_text, category_id, image_path=None,
|
||||
|
||||
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:
|
||||
cursor.execute('''
|
||||
INSERT INTO products (
|
||||
name, description, category_id,
|
||||
diameter, wall_thickness, length, color,
|
||||
application_type, stiffness_class, image_path
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
application_type, stiffness_class, image_path, slug
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (
|
||||
product_text,
|
||||
description,
|
||||
@@ -130,7 +148,8 @@ def add_product(conn, product_text, category_id, image_path=None,
|
||||
color,
|
||||
application_type,
|
||||
stiffness_class,
|
||||
image_path
|
||||
image_path,
|
||||
slug_value
|
||||
))
|
||||
|
||||
product_id = cursor.lastrowid
|
||||
|
||||
+41
-2
@@ -1,5 +1,6 @@
|
||||
import sqlite3
|
||||
from loguru import logger as log
|
||||
from utils.slugify import slugify
|
||||
|
||||
|
||||
def init_database(db_name='pipe_store.db'):
|
||||
@@ -31,6 +32,7 @@ def init_database(db_name='pipe_store.db'):
|
||||
application_type TEXT, -- внутренняя/наружная
|
||||
stiffness_class TEXT, -- SN4, SN2 и т.д.
|
||||
image_path TEXT,
|
||||
slug TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
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_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
|
||||
cursor.execute("PRAGMA table_info(products)")
|
||||
columns = {row[1] for row in cursor.fetchall()}
|
||||
@@ -80,11 +89,12 @@ def init_database(db_name='pipe_store.db'):
|
||||
application_type TEXT,
|
||||
stiffness_class TEXT,
|
||||
image_path TEXT,
|
||||
slug TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
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)
|
||||
SELECT id, full_name, description, category_id, diameter, wall_thickness, length, color, application_type, stiffness_class, image_path, created_at FROM products;
|
||||
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, slug, created_at FROM products;
|
||||
DROP TABLE products;
|
||||
ALTER TABLE products_new RENAME TO products;
|
||||
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'")
|
||||
|
||||
# Миграция: заполняем 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.close()
|
||||
log.success("База данных успешно инициализирована!")
|
||||
+32
-30
@@ -108,21 +108,22 @@ def init_tables(db_name='pipe_store.db'):
|
||||
|
||||
# 1. Внутренние трубы
|
||||
internal_pipes = [
|
||||
("110х2,2х500", 110, 2.2, 500, "серый", "внутренняя", None),
|
||||
("110х2,2х1000", 110, 2.2, 1000, "серый", "внутренняя", None),
|
||||
("110х2,2х1500", 110, 2.2, 1500, "серый", "внутренняя", None),
|
||||
("110х2,2х2000", 110, 2.2, 2000, "серый", "внутренняя", None),
|
||||
("110х2,2х3000", 110, 2.2, 3000, "серый", "внутренняя", None),
|
||||
("110х2,2х500", 110, 2.2, 500, "серый", "внутренняя", None, "RK011022500"),
|
||||
("110х2,2х1000", 110, 2.2, 1000, "серый", "внутренняя", None, "RK0110221000"),
|
||||
("110х2,2х1500", 110, 2.2, 1500, "серый", "внутренняя", None, "RK0110221500"),
|
||||
("110х2,2х2000", 110, 2.2, 2000, "серый", "внутренняя", None, "RK0110222000"),
|
||||
("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} мм внутренняя"
|
||||
# Замена на внешнюю ссылку
|
||||
image_url = f"{base_url}/products/internal_{diameter}_{length}.png"
|
||||
|
||||
product_id = add_product(
|
||||
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:
|
||||
added_products += 1
|
||||
@@ -130,30 +131,30 @@ def init_tables(db_name='pipe_store.db'):
|
||||
|
||||
# 2. Наружные трубы
|
||||
external_pipes = [
|
||||
("110х2,3х500", 110, 2.3, 500, "оранжево-коричневый", "наружная", None),
|
||||
("110х2,3х1000", 110, 2.3, 1000, "оранжево-коричневый", "наружная", None),
|
||||
("110х2,3х2000", 110, 2.3, 2000, "оранжево-коричневый", "наружная", None),
|
||||
("110х2,3х3000", 110, 2.3, 3000, "оранжево-коричневый", "наружная", None),
|
||||
("110х2,3х4000", 110, 2.3, 4000, "оранжево-коричневый", "наружная", None),
|
||||
("110х2,7х500 SN2", 110, 2.7, 500, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("110х2,7х1000 SN2", 110, 2.7, 1000, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("110х2,7х2000 SN2", 110, 2.7, 2000, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("110х2,7х3000 SN2", 110, 2.7, 3000, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("110х2,7х4000 SN2", 110, 2.7, 4000, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("110х3,2х500 SN4", 110, 3.2, 500, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("110х3,2х1000 SN4", 110, 3.2, 1000, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("110х3,2х2000 SN4", 110, 3.2, 2000, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("110х3,2х3000 SN4", 110, 3.2, 3000, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("110х3,2х4000 SN4", 110, 3.2, 4000, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("160х3,6х1000 SN2", 160, 3.6, 1000, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("160х3,6х2000 SN2", 160, 3.6, 2000, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("160х3,6х3000 SN2", 160, 3.6, 3000, "оранжево-коричневый", "наружная", "SN2"),
|
||||
("160х4,0х1000 SN4", 160, 4.0, 1000, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("160х4,0х2000 SN4", 160, 4.0, 2000, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("160х4,0х3000 SN4", 160, 4.0, 3000, "оранжево-коричневый", "наружная", "SN4"),
|
||||
("110х2,3х500", 110, 2.3, 500, "оранжево-коричневый", "наружная", None, "RK111023500"),
|
||||
("110х2,3х1000", 110, 2.3, 1000, "оранжево-коричневый", "наружная", None, "RK1110231000"),
|
||||
("110х2,3х2000", 110, 2.3, 2000, "оранжево-коричневый", "наружная", None, "RK1110232000"),
|
||||
("110х2,3х3000", 110, 2.3, 3000, "оранжево-коричневый", "наружная", None, "RK1110233000"),
|
||||
("110х2,3х4000", 110, 2.3, 4000, "оранжево-коричневый", "наружная", None, "RK1110234000"),
|
||||
("110х2,7х500 SN2", 110, 2.7, 500, "оранжево-коричневый", "наружная", "SN2", "RK111027500"),
|
||||
("110х2,7х1000 SN2", 110, 2.7, 1000, "оранжево-коричневый", "наружная", "SN2", "RK1110271000"),
|
||||
("110х2,7х2000 SN2", 110, 2.7, 2000, "оранжево-коричневый", "наружная", "SN2", "RK1110272000"),
|
||||
("110х2,7х3000 SN2", 110, 2.7, 3000, "оранжево-коричневый", "наружная", "SN2", "RK1110273000"),
|
||||
("110х2,7х4000 SN2", 110, 2.7, 4000, "оранжево-коричневый", "наружная", "SN2", "RK1110274000"),
|
||||
("110х3,2х500 SN4", 110, 3.2, 500, "оранжево-коричневый", "наружная", "SN4", "RK111032500"),
|
||||
("110х3,2х1000 SN4", 110, 3.2, 1000, "оранжево-коричневый", "наружная", "SN4", "RK1110321000"),
|
||||
("110х3,2х2000 SN4", 110, 3.2, 2000, "оранжево-коричневый", "наружная", "SN4", "RK1110322000"),
|
||||
("110х3,2х3000 SN4", 110, 3.2, 3000, "оранжево-коричневый", "наружная", "SN4", "RK1110323000"),
|
||||
("110х3,2х4000 SN4", 110, 3.2, 4000, "оранжево-коричневый", "наружная", "SN4", "RK1110324000"),
|
||||
("160х3,6х1000 SN2", 160, 3.6, 1000, "оранжево-коричневый", "наружная", "SN2", "RK1160361000"),
|
||||
("160х3,6х2000 SN2", 160, 3.6, 2000, "оранжево-коричневый", "наружная", "SN2", "RK1160362000"),
|
||||
("160х3,6х3000 SN2", 160, 3.6, 3000, "оранжево-коричневый", "наружная", "SN2", "RK1160363000"),
|
||||
("160х4,0х1000 SN4", 160, 4.0, 1000, "оранжево-коричневый", "наружная", "SN4", "RK1160401000"),
|
||||
("160х4,0х2000 SN4", 160, 4.0, 2000, "оранжево-коричневый", "наружная", "SN4", "RK1160402000"),
|
||||
("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 ""
|
||||
full_name = f"Труба НПВХ {diameter}x{thickness}x{length} мм наружная"
|
||||
# Замена на внешнюю ссылку
|
||||
@@ -162,7 +163,8 @@ def init_tables(db_name='pipe_store.db'):
|
||||
|
||||
product_id = add_product(
|
||||
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:
|
||||
added_products += 1
|
||||
|
||||
@@ -101,6 +101,7 @@ def get_products_with_filters(
|
||||
items.append({
|
||||
"id": row['id'],
|
||||
"name": row['name'],
|
||||
"slug": row['slug'],
|
||||
"description": row['description'],
|
||||
"categoryId": row['category_id'],
|
||||
"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,
|
||||
p.diameter, p.wall_thickness, p.length, p.color,
|
||||
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
|
||||
WHERE p.id = ?
|
||||
""", (product_id,))
|
||||
@@ -192,6 +193,7 @@ def get_product_by_id(product_id: int, db_name: str = 'pipe_store.db') -> Option
|
||||
return {
|
||||
"id": row['id'],
|
||||
"name": row['name'],
|
||||
"slug": row['slug'],
|
||||
"description": full_description,
|
||||
"price": None,
|
||||
"categoryId": row['category_id'],
|
||||
@@ -207,6 +209,82 @@ def get_product_by_id(product_id: int, db_name: str = 'pipe_store.db') -> Option
|
||||
finally:
|
||||
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]:
|
||||
"""Получить список изображений товара"""
|
||||
images = []
|
||||
@@ -278,6 +356,7 @@ def get_product_list(page=1, limit=100, category_id=None):
|
||||
products.append({
|
||||
'id': row['id'],
|
||||
'name': row['name'],
|
||||
'slug': row['slug'],
|
||||
'description': row['description']
|
||||
})
|
||||
return products
|
||||
|
||||
Reference in new issue
Block a user