Расширен API поиска товаров фильтрацией и фото
- Добавлены параметры фильтрации: category, tag, in_stock - Функция _apply_product_filters() для применения фильтров к queryset - Функция _get_product_photo_url() для получения главного фото товара - В ответ API добавлено поле photo_url с URL фото товара - Отключено кэширование при использовании фильтров - Улучшена производительность запросов с использованием order_by и values
This commit is contained in:
@@ -7,11 +7,30 @@ from django.core.cache import cache
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..models import Product, ProductVariantGroup, ProductKit, ProductCategory
|
from ..models import Product, ProductVariantGroup, ProductKit, ProductCategory, ProductPhoto
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_product_photo_url(product_id):
|
||||||
|
"""Получает URL главного фото товара (первого по порядку)."""
|
||||||
|
photo = ProductPhoto.objects.filter(product_id=product_id).order_by('order').first()
|
||||||
|
if photo and photo.image:
|
||||||
|
return photo.image.url
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_product_filters(queryset, category_id=None, tag_id=None, in_stock_only=False):
|
||||||
|
"""Применяет фильтры к queryset товаров."""
|
||||||
|
if category_id:
|
||||||
|
queryset = queryset.filter(categories__id=category_id)
|
||||||
|
if tag_id:
|
||||||
|
queryset = queryset.filter(tags__id=tag_id)
|
||||||
|
if in_stock_only:
|
||||||
|
queryset = queryset.filter(in_stock=True)
|
||||||
|
return queryset.distinct()
|
||||||
|
|
||||||
|
|
||||||
def search_products_and_variants(request):
|
def search_products_and_variants(request):
|
||||||
"""
|
"""
|
||||||
API endpoint для поиска товаров, групп вариантов и комплектов (совместимость с Select2).
|
API endpoint для поиска товаров, групп вариантов и комплектов (совместимость с Select2).
|
||||||
@@ -22,6 +41,9 @@ def search_products_and_variants(request):
|
|||||||
- id: ID товара/комплекта для получения его данных (формат: "product_123" или "kit_456")
|
- id: ID товара/комплекта для получения его данных (формат: "product_123" или "kit_456")
|
||||||
- type: 'product', 'variant', 'kit' или 'all' (опционально, по умолчанию 'all')
|
- type: 'product', 'variant', 'kit' или 'all' (опционально, по умолчанию 'all')
|
||||||
- page: номер страницы для пагинации (по умолчанию 1)
|
- page: номер страницы для пагинации (по умолчанию 1)
|
||||||
|
- category: ID категории для фильтрации (опционально)
|
||||||
|
- tag: ID тега для фильтрации (опционально)
|
||||||
|
- in_stock: 'true' для фильтрации только товаров в наличии (опционально)
|
||||||
|
|
||||||
Возвращает JSON в формате Select2 с группировкой:
|
Возвращает JSON в формате Select2 с группировкой:
|
||||||
{
|
{
|
||||||
@@ -130,24 +152,34 @@ def search_products_and_variants(request):
|
|||||||
page = int(request.GET.get('page', 1))
|
page = int(request.GET.get('page', 1))
|
||||||
page_size = 30
|
page_size = 30
|
||||||
|
|
||||||
|
# Дополнительные фильтры
|
||||||
|
category_id = request.GET.get('category', '').strip()
|
||||||
|
tag_id = request.GET.get('tag', '').strip()
|
||||||
|
in_stock_only = request.GET.get('in_stock', '').lower() == 'true'
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
|
# Проверяем, есть ли дополнительные фильтры
|
||||||
|
has_filters = category_id or tag_id or in_stock_only
|
||||||
|
|
||||||
# Если поиска нет - показываем популярные товары и комплекты
|
# Если поиска нет - показываем популярные товары и комплекты
|
||||||
if not query or len(query) < 2:
|
if not query or len(query) < 2:
|
||||||
# Кэшируем популярные товары на 1 час
|
# Кэшируем только если нет фильтров
|
||||||
cache_key = f'popular_items_{search_type}'
|
if not has_filters:
|
||||||
cached_results = cache.get(cache_key)
|
cache_key = f'popular_items_{search_type}'
|
||||||
|
cached_results = cache.get(cache_key)
|
||||||
if cached_results:
|
if cached_results:
|
||||||
return JsonResponse(cached_results)
|
return JsonResponse(cached_results)
|
||||||
|
|
||||||
product_results = []
|
product_results = []
|
||||||
kit_results = []
|
kit_results = []
|
||||||
|
|
||||||
if search_type in ['all', 'product']:
|
if search_type in ['all', 'product']:
|
||||||
# Показываем последние добавленные активные товары
|
# Показываем последние добавленные активные товары
|
||||||
products = Product.objects.filter(status='active')\
|
products_qs = Product.objects.filter(status='active')
|
||||||
.order_by('-created_at')[:page_size]\
|
# Применяем фильтры
|
||||||
|
products_qs = _apply_product_filters(products_qs, category_id, tag_id, in_stock_only)
|
||||||
|
products = products_qs.order_by('-created_at')[:page_size]\
|
||||||
.values('id', 'name', 'sku', 'price', 'sale_price', 'in_stock')
|
.values('id', 'name', 'sku', 'price', 'sale_price', 'in_stock')
|
||||||
|
|
||||||
for product in products:
|
for product in products:
|
||||||
@@ -165,7 +197,8 @@ def search_products_and_variants(request):
|
|||||||
'price': str(product['price']) if product['price'] else None,
|
'price': str(product['price']) if product['price'] else None,
|
||||||
'actual_price': str(actual_price) if actual_price else '0',
|
'actual_price': str(actual_price) if actual_price else '0',
|
||||||
'in_stock': product['in_stock'],
|
'in_stock': product['in_stock'],
|
||||||
'type': 'product'
|
'type': 'product',
|
||||||
|
'photo_url': _get_product_photo_url(product['id'])
|
||||||
})
|
})
|
||||||
|
|
||||||
if search_type in ['all', 'kit']:
|
if search_type in ['all', 'kit']:
|
||||||
@@ -214,7 +247,9 @@ def search_products_and_variants(request):
|
|||||||
'results': results,
|
'results': results,
|
||||||
'pagination': {'more': False}
|
'pagination': {'more': False}
|
||||||
}
|
}
|
||||||
cache.set(cache_key, response_data, 3600)
|
# Кэшируем только если нет фильтров
|
||||||
|
if not has_filters:
|
||||||
|
cache.set(cache_key, response_data, 3600)
|
||||||
return JsonResponse(response_data)
|
return JsonResponse(response_data)
|
||||||
|
|
||||||
# Поиск товаров и комплектов (регистронезависимый поиск с приоритетом точных совпадений)
|
# Поиск товаров и комплектов (регистронезависимый поиск с приоритетом точных совпадений)
|
||||||
@@ -269,6 +304,9 @@ def search_products_and_variants(request):
|
|||||||
)
|
)
|
||||||
).order_by('-relevance', 'name')
|
).order_by('-relevance', 'name')
|
||||||
|
|
||||||
|
# Применяем дополнительные фильтры
|
||||||
|
products_query = _apply_product_filters(products_query, category_id, tag_id, in_stock_only)
|
||||||
|
|
||||||
total_products = products_query.count()
|
total_products = products_query.count()
|
||||||
start = (page - 1) * page_size
|
start = (page - 1) * page_size
|
||||||
end = start + page_size
|
end = start + page_size
|
||||||
@@ -290,7 +328,8 @@ def search_products_and_variants(request):
|
|||||||
'price': str(product['price']) if product['price'] else None,
|
'price': str(product['price']) if product['price'] else None,
|
||||||
'actual_price': str(actual_price) if actual_price else '0',
|
'actual_price': str(actual_price) if actual_price else '0',
|
||||||
'in_stock': product['in_stock'],
|
'in_stock': product['in_stock'],
|
||||||
'type': 'product'
|
'type': 'product',
|
||||||
|
'photo_url': _get_product_photo_url(product['id'])
|
||||||
})
|
})
|
||||||
|
|
||||||
has_more = total_products > end
|
has_more = total_products > end
|
||||||
|
|||||||
Reference in New Issue
Block a user