fix(api): fix err with filters

This commit is contained in:
Black_Raven committed 2026-04-23 17:27:41 +03:00
1 parent ba81b2cf32
commit 77ebb0e3a8
2 files changed
+65 -78

No files matched your search

+31 -18
View File
@@ -2,9 +2,19 @@ from flask import jsonify, request
from db.handlers.product_handler import get_products_with_filters, get_product_by_id from db.handlers.product_handler import get_products_with_filters, get_product_by_id
from flask import Blueprint from flask import Blueprint
# Создаем blueprint для API
api_product = Blueprint('api_product', __name__, url_prefix='/products') api_product = Blueprint('api_product', __name__, url_prefix='/products')
def clean_filter_value(value):
"""Очищает значение фильтра от мусора. Возвращает очищенное значение или None, если оно пустое."""
if value is None:
return None
if isinstance(value, str):
cleaned = value.strip().rstrip('/')
if not cleaned:
return None
return cleaned
return value
@api_product.route('/', methods=['GET'], strict_slashes=False) @api_product.route('/', methods=['GET'], strict_slashes=False)
def get_products(): def get_products():
try: try:
@@ -20,14 +30,14 @@ def get_products():
if limit < 1 or limit > 100: if limit < 1 or limit > 100:
limit = 9 limit = 9
# Словарь для маппинга внешних ключей → внутренние имена в БД # ✅ ИСПРАВЛЕННЫЙ МАППИНГ
filter_mapping = { filter_mapping = {
'attr_1': 'diameter', 'attr_1': 'diameter',
'attr_2': 'length', 'attr_2': 'length',
'attr_3': 'wall_thickness', 'attr_3': 'wall_thickness', # (опционально) тоже толщина
'attr_4': 'color', 'attr_4': 'color',
'attr_5': 'application_type', 'attr_5': 'stiffness_class', # SN2, SN4
'attr_6': 'stiffness_class', 'attr_6': 'wall_thickness', # <- теперь attr_6 ищет толщину стенки
'attr_7': 'angle', 'attr_7': 'angle',
'attr_8': 'fitting_type', 'attr_8': 'fitting_type',
} }
@@ -39,14 +49,24 @@ def get_products():
if key in excluded_params: if key in excluded_params:
continue continue
# Преобразуем ключ, если он есть в маппинге, иначе оставляем как есть
real_key = filter_mapping.get(key, key) real_key = filter_mapping.get(key, key)
# Разделяем значения по запятой
if ',' in value: if ',' in value:
filters[real_key] = value.split(',') parts = value.split(',')
cleaned_parts = []
for part in parts:
cleaned = clean_filter_value(part)
if cleaned is not None:
cleaned_parts.append(cleaned)
if cleaned_parts:
filters[real_key] = cleaned_parts
else: else:
filters[real_key] = value cleaned = clean_filter_value(value)
if cleaned is not None:
filters[real_key] = cleaned
# Отладочный вывод (убедитесь, что здесь видно правильный ключ)
print("=== FINAL FILTERS ===", filters)
result = get_products_with_filters( result = get_products_with_filters(
category_id=category_id, category_id=category_id,
@@ -60,12 +80,9 @@ def get_products():
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@api_product.route('/<int:product_id>', methods=['GET'], strict_slashes=False) @api_product.route('/<int:product_id>', methods=['GET'], strict_slashes=False)
def get_product(product_id): def get_product(product_id):
"""
Получение товара по ID
GET /api/products/{id}
"""
try: try:
product = get_product_by_id(product_id) product = get_product_by_id(product_id)
if product: if product:
@@ -74,17 +91,13 @@ def get_product(product_id):
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@api_product.route('/fetchById', methods=['GET'], strict_slashes=False) @api_product.route('/fetchById', methods=['GET'], strict_slashes=False)
def get_product_by_id_param(): def get_product_by_id_param():
"""
Альтернативный маршрут для получения товара через query параметр
GET /api/products/fetchById?id=101
"""
try: try:
product_id = request.args.get('id', type=int) product_id = request.args.get('id', type=int)
if not product_id: if not product_id:
return jsonify({"error": "id parameter is required"}), 400 return jsonify({"error": "id parameter is required"}), 400
product = get_product_by_id(product_id) product = get_product_by_id(product_id)
if product: if product:
return jsonify(product), 200 return jsonify(product), 200
+34 -60
View File
@@ -10,12 +10,17 @@ def get_products_with_filters(
filters: Dict[str, Any] = None, filters: Dict[str, Any] = None,
db_name: str = 'pipe_store.db' db_name: str = 'pipe_store.db'
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""
Получение товаров с фильтрацией и пагинацией.
Все фильтры (включая диаметр, длину, цвет и т.д.) ищутся в таблице product_filters.
"""
print(f"Received filters: {filters}")
conn = sqlite3.connect(db_name) conn = sqlite3.connect(db_name)
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
cursor = conn.cursor() cursor = conn.cursor()
try: try:
# Рекурсивный CTE для получения всех подкатегорий # 1. Получаем все ID категорий (включая подкатегории)
hierarchy_query = """ hierarchy_query = """
WITH RECURSIVE category_hierarchy AS ( WITH RECURSIVE category_hierarchy AS (
SELECT id FROM categories WHERE id = ? SELECT id FROM categories WHERE id = ?
@@ -35,71 +40,43 @@ def get_products_with_filters(
base_where = f"WHERE p.category_id IN ({placeholders_cats})" base_where = f"WHERE p.category_id IN ({placeholders_cats})"
params = list(category_ids) params = list(category_ids)
# Обработка фильтров # 2. Обработка всех фильтров через product_filters
if filters: if filters:
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(): for key, value in filters.items():
if not value: if not value:
continue continue
if key in column_map: # Подзапрос для проверки наличия фильтра у товара
col = column_map[key] if isinstance(value, list):
if isinstance(value, list): # Множественный фильтр (например, несколько диаметров)
casted = [cast_value(key, v) for v in value] placeholders = ','.join(['?' for _ in value])
v_ph = ','.join(['?' for _ in casted]) base_where += f"""
base_where += f" AND {col} IN ({v_ph})" AND p.id IN (
params.extend(casted) SELECT product_id FROM product_filters
else: WHERE filter_key = ? AND value IN ({placeholders})
casted = cast_value(key, value) )
base_where += f" AND {col} = ?" """
params.append(casted) params.append(key)
params.extend([str(v) for v in value])
else: else:
# Фильтры из product_filters # Одиночный фильтр
if isinstance(value, list): base_where += f"""
v_ph = ','.join(['?' for _ in value]) AND p.id IN (
base_where += f""" SELECT product_id FROM product_filters
AND p.id IN ( WHERE filter_key = ? AND value = ?
SELECT product_id FROM product_filters )
WHERE filter_key = ? AND value IN ({v_ph}) """
)""" params.append(key)
params.append(key) params.append(str(value))
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))
# Подсчёт общего количества # 3. Подсчёт общего количества
count_query = f"SELECT COUNT(*) as total FROM products p {base_where}" count_query = f"SELECT COUNT(*) as total FROM products p {base_where}"
cursor.execute(count_query, params) cursor.execute(count_query, params)
print("SQL QUERY:", count_query)
print("PARAMS:", params)
total = cursor.fetchone()['total'] total = cursor.fetchone()['total']
# Пагинация # 4. Пагинация
offset = (page - 1) * limit offset = (page - 1) * limit
query = f""" query = f"""
SELECT p.* FROM products p SELECT p.* FROM products p
@@ -110,6 +87,7 @@ def get_products_with_filters(
cursor.execute(query, params + [limit, offset]) cursor.execute(query, params + [limit, offset])
rows = cursor.fetchall() rows = cursor.fetchall()
# 5. Формируем ответ
items = [] items = []
for row in rows: for row in rows:
items.append({ items.append({
@@ -228,15 +206,12 @@ def get_product_images(product_id: int, main_image: Optional[str], conn: sqlite3
def check_stock(product_id: int, conn: sqlite3.Connection) -> bool: def check_stock(product_id: int, conn: sqlite3.Connection) -> bool:
"""Проверить наличие товара на складе""" """Проверить наличие товара на складе"""
# Временная логика - все товары в наличии # Временная логика - все товары в наличии
# В будущем можно добавить таблицу stock
return True return True
def get_pack_amount(product_id: int, diameter: int = None, conn: sqlite3.Connection = None) -> str: def get_pack_amount(product_id: int, diameter: int = None, conn: sqlite3.Connection = None) -> str:
""" """
Получить количество в упаковке Получить количество в упаковке
В реальном проекте должно быть в базе данных
""" """
# Временная логика на основе диаметра
if diameter: if diameter:
if diameter == 110: if diameter == 110:
return "6-10" return "6-10"
@@ -272,7 +247,7 @@ def generate_full_description(row: sqlite3.Row) -> str:
def get_product_list(page=1, limit=100, category_id=None): def get_product_list(page=1, limit=100, category_id=None):
""" """
Получить список товаров с пагинацией. Получить список товаров с пагинацией (без фильтров).
Возвращает список словарей с полями id, full_name, description. Возвращает список словарей с полями id, full_name, description.
""" """
conn = None conn = None
@@ -281,7 +256,6 @@ def get_product_list(page=1, limit=100, category_id=None):
conn = sqlite3.connect('pipe_store.db') conn = sqlite3.connect('pipe_store.db')
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
offset = (page - 1) * limit
rows = get_products_by_page(conn, page, limit, category_id) rows = get_products_by_page(conn, page, limit, category_id)
products = [] products = []