fix(db): Fix handler for get product with categories

This commit is contained in:
Black_Raven committed 2026-04-06 22:50:56 +03:00
1 parent cfd5228f67
commit b7c831f0c3
2 files changed
+123 -106

No files matched your search

+24 -15
View File
@@ -7,38 +7,47 @@ api_product = Blueprint('api_product', __name__, url_prefix='/products')
@api_product.route('/', methods=['GET'], strict_slashes=False) @api_product.route('/', methods=['GET'], strict_slashes=False)
def get_products(): def get_products():
"""
Получение товаров с фильтрацией и пагинацией
GET /api/products?categoryId=1&page=1&limit=9&color=серый&size=110х2,2х500
"""
try: try:
# Получаем обязательный параметр categoryId
category_id = request.args.get('categoryId', type=int) category_id = request.args.get('categoryId', type=int)
if not category_id: if not category_id:
return jsonify({"error": "categoryId is required"}), 400 return jsonify({"error": "categoryId is required"}), 400
# Получаем параметры пагинации
page = request.args.get('page', 1, type=int) page = request.args.get('page', 1, type=int)
limit = request.args.get('limit', 9, type=int) limit = request.args.get('limit', 9, type=int)
# Валидация параметров
if page < 1: if page < 1:
page = 1 page = 1
if limit < 1 or limit > 100: if limit < 1 or limit > 100:
limit = 9 limit = 9
# Собираем фильтры из query параметров # Словарь для маппинга внешних ключей → внутренние имена в БД
filter_mapping = {
'attr_1': 'diameter',
'attr_2': 'length',
'attr_3': 'wall_thickness',
'attr_4': 'color',
'attr_5': 'application_type',
'attr_6': 'stiffness_class',
'attr_7': 'angle',
'attr_8': 'fitting_type',
}
filters = {} filters = {}
excluded_params = ['categoryId', 'page', 'limit'] excluded_params = {'categoryId', 'page', 'limit'}
for key, value in request.args.items(): for key, value in request.args.items():
if key not in excluded_params: if key in excluded_params:
if ',' in value: continue
filters[key] = value.split(',')
else: # Преобразуем ключ, если он есть в маппинге, иначе оставляем как есть
filters[key] = value real_key = filter_mapping.get(key, key)
# Разделяем значения по запятой
if ',' in value:
filters[real_key] = value.split(',')
else:
filters[real_key] = value
# Получаем товары
result = get_products_with_filters( result = get_products_with_filters(
category_id=category_id, category_id=category_id,
page=page, page=page,
+99 -91
View File
@@ -14,109 +14,117 @@ def get_products_with_filters(
cursor = conn.cursor() cursor = conn.cursor()
try: try:
# Базовый запрос для получения товаров # Рекурсивный CTE для получения всех подкатегорий
base_query = """ hierarchy_query = """
SELECT DISTINCT p.id, p.name, p.full_name, p.description, WITH RECURSIVE category_hierarchy AS (
p.diameter, p.wall_thickness, p.length, p.color, SELECT id FROM categories WHERE id = ?
p.application_type, p.stiffness_class, p.image_path, UNION ALL
p.created_at SELECT c.id FROM categories c
FROM products p JOIN category_hierarchy ch ON c.parent_id = ch.id
WHERE p.category_id = ? )
SELECT id FROM category_hierarchy
""" """
cursor.execute(hierarchy_query, (category_id,))
category_ids = [row['id'] for row in cursor.fetchall()]
params = [category_id] if not category_ids:
return {"items": [], "total": 0, "page": page, "limit": limit, "total_pages": 0}
# Применяем фильтры placeholders_cats = ','.join(['?' for _ in category_ids])
base_where = f"WHERE p.category_id IN ({placeholders_cats})"
params = list(category_ids)
# Обработка фильтров
if filters: if filters:
for filter_key, filter_value in filters.items(): column_map = {
if filter_value: 'diameter': 'p.diameter',
if isinstance(filter_value, list): 'length': 'p.length',
# Для множественных значений 'wall_thickness': 'p.wall_thickness',
placeholders = ','.join(['?' for _ in filter_value]) 'color': 'p.color',
base_query += f""" 'application_type': 'p.application_type',
AND p.id IN ( 'stiffness_class': 'p.stiffness_class'
SELECT DISTINCT pf.product_id }
FROM product_filters pf
WHERE pf.filter_key = ?
AND pf.value IN ({placeholders})
)
"""
params.append(filter_key)
params.extend(filter_value)
else:
# Для одиночных значений
base_query += """
AND p.id IN (
SELECT DISTINCT pf.product_id
FROM product_filters pf
WHERE pf.filter_key = ? AND pf.value = ?
)
"""
params.append(filter_key)
params.append(filter_value)
# Получаем общее количество товаров def cast_value(col_name, val):
count_query = f"SELECT COUNT(*) as total FROM ({base_query})" if col_name in ('diameter', 'length'):
try:
return int(val)
except:
return val
if col_name == 'wall_thickness':
try:
return float(val)
except:
return val
return val
for key, value in filters.items():
if not value:
continue
if key in column_map:
col = column_map[key]
if isinstance(value, list):
casted = [cast_value(key, v) for v in value]
v_ph = ','.join(['?' for _ in casted])
base_where += f" AND {col} IN ({v_ph})"
params.extend(casted)
else:
casted = cast_value(key, value)
base_where += f" AND {col} = ?"
params.append(casted)
else:
# Фильтры из product_filters
if isinstance(value, list):
v_ph = ','.join(['?' for _ in value])
base_where += f"""
AND p.id IN (
SELECT product_id FROM product_filters
WHERE filter_key = ? AND value IN ({v_ph})
)"""
params.append(key)
params.extend([str(v) for v in value])
else:
base_where += f"""
AND p.id IN (
SELECT product_id FROM product_filters
WHERE filter_key = ? AND value = ?
)"""
params.append(key)
params.append(str(value))
# Подсчёт общего количества
count_query = f"SELECT COUNT(*) as total FROM products p {base_where}"
cursor.execute(count_query, params) cursor.execute(count_query, params)
total = cursor.fetchone()['total'] total = cursor.fetchone()['total']
# Добавляем пагинацию # Пагинация
offset = (page - 1) * limit offset = (page - 1) * limit
paginated_query = f""" query = f"""
{base_query} SELECT p.* FROM products p
ORDER BY p.id {base_where}
ORDER BY p.id ASC
LIMIT ? OFFSET ? LIMIT ? OFFSET ?
""" """
params.extend([limit, offset]) cursor.execute(query, params + [limit, offset])
rows = cursor.fetchall()
cursor.execute(paginated_query, params)
# Формируем результат
items = [] items = []
for row in cursor.fetchall(): for row in rows:
# Формируем атрибуты товара
attributes = {}
if row['color']:
attributes['color'] = row['color']
if row['length'] and row['length'] > 0:
if row['diameter'] and row['wall_thickness']:
attributes['size'] = f"{row['diameter']}x{row['wall_thickness']}x{row['length']}"
else:
attributes['size'] = str(row['length'])
if row['diameter']:
attributes['diameter'] = str(row['diameter'])
if row['wall_thickness']:
attributes['wall_thickness'] = str(row['wall_thickness'])
if row['stiffness_class']:
attributes['stiffness_class'] = row['stiffness_class']
if row['application_type']:
attributes['application_type'] = row['application_type']
# Получаем дополнительные атрибуты из product_filters
cursor.execute("""
SELECT filter_key, value
FROM product_filters
WHERE product_id = ?
""", (row['id'],))
for filter_row in cursor.fetchall():
if filter_row['filter_key'] not in attributes:
attributes[filter_row['filter_key']] = filter_row['value']
# Определяем количество в упаковке (можно вынести в отдельную таблицу)
attributes['amount'] = get_pack_amount(row['id'], row['diameter'], conn)
items.append({ items.append({
"id": row['id'], "id": row['id'],
"name": row['name'], "name": row['name'],
"description": row['description'] or f"Труба ПВХ {row['diameter']}мм" if row['diameter'] else "Товар", "full_name": row['full_name'],
# Цена не хранится в БД - возвращаем 0 или None "description": row['description'],
"price": None, "categoryId": row['category_id'],
"categoryId": category_id, "images": [row['image_path']] if row['image_path'] else ["/images/products/default.jpg"],
"images": get_product_images(row['id'], row['image_path'], conn), "attributes": {
"attributes": attributes, "diameter": row['diameter'],
"inStock": check_stock(row['id'], conn) "length": row['length'],
"wall_thickness": row['wall_thickness'],
"color": row['color'],
"application_type": row['application_type']
}
}) })
return { return {
@@ -124,12 +132,12 @@ def get_products_with_filters(
"total": total, "total": total,
"page": page, "page": page,
"limit": limit, "limit": limit,
"total_pages": (total + limit - 1) // limit "total_pages": (total + limit - 1) // limit if total > 0 else 0
} }
except sqlite3.Error as e: except Exception as e:
log.error(f"Ошибка при получении товаров: {e}") log.error(f"SQL Error in get_products_with_filters: {e}")
return {"items": [], "total": 0, "page": page, "limit": limit} return {"items": [], "total": 0, "page": page, "limit": limit, "total_pages": 0}
finally: finally:
conn.close() conn.close()