fix(api): fix err with filters
This commit is contained in:
1 parent
ba81b2cf32
commit
77ebb0e3a8
2 files changed
+67
-80
No files matched your search
+31
-18
@@ -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
|
||||||
|
|||||||
@@ -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 = ?
|
||||||
@@ -27,7 +32,7 @@ def get_products_with_filters(
|
|||||||
"""
|
"""
|
||||||
cursor.execute(hierarchy_query, (category_id,))
|
cursor.execute(hierarchy_query, (category_id,))
|
||||||
category_ids = [row['id'] for row in cursor.fetchall()]
|
category_ids = [row['id'] for row in cursor.fetchall()]
|
||||||
|
|
||||||
if not category_ids:
|
if not category_ids:
|
||||||
return {"items": [], "total": 0, "page": page, "limit": limit, "total_pages": 0}
|
return {"items": [], "total": 0, "page": page, "limit": limit, "total_pages": 0}
|
||||||
|
|
||||||
@@ -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 = []
|
||||||
|
|||||||
Reference in new issue
Block a user