fix(db): Fix handler for get product with categories
This commit is contained in:
1 parent
cfd5228f67
commit
b7c831f0c3
2 files changed
+134
-117
No files matched your search
+31
-22
@@ -7,47 +7,56 @@ api_product = Blueprint('api_product', __name__, url_prefix='/products')
|
||||
|
||||
@api_product.route('/', methods=['GET'], strict_slashes=False)
|
||||
def get_products():
|
||||
"""
|
||||
Получение товаров с фильтрацией и пагинацией
|
||||
GET /api/products?categoryId=1&page=1&limit=9&color=серый&size=110х2,2х500
|
||||
"""
|
||||
try:
|
||||
# Получаем обязательный параметр categoryId
|
||||
category_id = request.args.get('categoryId', type=int)
|
||||
if not category_id:
|
||||
return jsonify({"error": "categoryId is required"}), 400
|
||||
|
||||
# Получаем параметры пагинации
|
||||
|
||||
page = request.args.get('page', 1, type=int)
|
||||
limit = request.args.get('limit', 9, type=int)
|
||||
|
||||
# Валидация параметров
|
||||
|
||||
if page < 1:
|
||||
page = 1
|
||||
if limit < 1 or limit > 100:
|
||||
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 = {}
|
||||
excluded_params = ['categoryId', 'page', 'limit']
|
||||
|
||||
excluded_params = {'categoryId', 'page', 'limit'}
|
||||
|
||||
for key, value in request.args.items():
|
||||
if key not in excluded_params:
|
||||
if ',' in value:
|
||||
filters[key] = value.split(',')
|
||||
else:
|
||||
filters[key] = value
|
||||
|
||||
# Получаем товары
|
||||
if key in excluded_params:
|
||||
continue
|
||||
|
||||
# Преобразуем ключ, если он есть в маппинге, иначе оставляем как есть
|
||||
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(
|
||||
category_id=category_id,
|
||||
page=page,
|
||||
limit=limit,
|
||||
filters=filters if filters else None
|
||||
)
|
||||
|
||||
|
||||
return jsonify(result), 200
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
|
||||
+103
-95
@@ -14,122 +14,130 @@ def get_products_with_filters(
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
# Базовый запрос для получения товаров
|
||||
base_query = """
|
||||
SELECT DISTINCT p.id, p.name, p.full_name, p.description,
|
||||
p.diameter, p.wall_thickness, p.length, p.color,
|
||||
p.application_type, p.stiffness_class, p.image_path,
|
||||
p.created_at
|
||||
FROM products p
|
||||
WHERE p.category_id = ?
|
||||
# Рекурсивный CTE для получения всех подкатегорий
|
||||
hierarchy_query = """
|
||||
WITH RECURSIVE category_hierarchy AS (
|
||||
SELECT id FROM categories WHERE id = ?
|
||||
UNION ALL
|
||||
SELECT c.id FROM categories c
|
||||
JOIN category_hierarchy ch ON c.parent_id = ch.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:
|
||||
for filter_key, filter_value in filters.items():
|
||||
if filter_value:
|
||||
if isinstance(filter_value, list):
|
||||
# Для множественных значений
|
||||
placeholders = ','.join(['?' for _ in filter_value])
|
||||
base_query += f"""
|
||||
AND p.id IN (
|
||||
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)
|
||||
column_map = {
|
||||
'diameter': 'p.diameter',
|
||||
'length': 'p.length',
|
||||
'wall_thickness': 'p.wall_thickness',
|
||||
'color': 'p.color',
|
||||
'application_type': 'p.application_type',
|
||||
'stiffness_class': 'p.stiffness_class'
|
||||
}
|
||||
|
||||
def cast_value(col_name, val):
|
||||
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:
|
||||
# Для одиночных значений
|
||||
base_query += """
|
||||
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 DISTINCT pf.product_id
|
||||
FROM product_filters pf
|
||||
WHERE pf.filter_key = ? AND pf.value = ?
|
||||
)
|
||||
"""
|
||||
params.append(filter_key)
|
||||
params.append(filter_value)
|
||||
|
||||
# Получаем общее количество товаров
|
||||
count_query = f"SELECT COUNT(*) as total FROM ({base_query})"
|
||||
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)
|
||||
total = cursor.fetchone()['total']
|
||||
|
||||
# Добавляем пагинацию
|
||||
|
||||
# Пагинация
|
||||
offset = (page - 1) * limit
|
||||
paginated_query = f"""
|
||||
{base_query}
|
||||
ORDER BY p.id
|
||||
query = f"""
|
||||
SELECT p.* FROM products p
|
||||
{base_where}
|
||||
ORDER BY p.id ASC
|
||||
LIMIT ? OFFSET ?
|
||||
"""
|
||||
params.extend([limit, offset])
|
||||
|
||||
cursor.execute(paginated_query, params)
|
||||
|
||||
# Формируем результат
|
||||
cursor.execute(query, params + [limit, offset])
|
||||
rows = cursor.fetchall()
|
||||
|
||||
items = []
|
||||
for row in cursor.fetchall():
|
||||
# Формируем атрибуты товара
|
||||
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)
|
||||
|
||||
for row in rows:
|
||||
items.append({
|
||||
"id": row['id'],
|
||||
"name": row['name'],
|
||||
"description": row['description'] or f"Труба ПВХ {row['diameter']}мм" if row['diameter'] else "Товар",
|
||||
# Цена не хранится в БД - возвращаем 0 или None
|
||||
"price": None,
|
||||
"categoryId": category_id,
|
||||
"images": get_product_images(row['id'], row['image_path'], conn),
|
||||
"attributes": attributes,
|
||||
"inStock": check_stock(row['id'], conn)
|
||||
"full_name": row['full_name'],
|
||||
"description": row['description'],
|
||||
"categoryId": row['category_id'],
|
||||
"images": [row['image_path']] if row['image_path'] else ["/images/products/default.jpg"],
|
||||
"attributes": {
|
||||
"diameter": row['diameter'],
|
||||
"length": row['length'],
|
||||
"wall_thickness": row['wall_thickness'],
|
||||
"color": row['color'],
|
||||
"application_type": row['application_type']
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
"items": items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total_pages": (total + limit - 1) // limit
|
||||
"total_pages": (total + limit - 1) // limit if total > 0 else 0
|
||||
}
|
||||
|
||||
except sqlite3.Error as e:
|
||||
log.error(f"Ошибка при получении товаров: {e}")
|
||||
return {"items": [], "total": 0, "page": page, "limit": limit}
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"SQL Error in get_products_with_filters: {e}")
|
||||
return {"items": [], "total": 0, "page": page, "limit": limit, "total_pages": 0}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
Reference in new issue
Block a user