Обновили шапку и вывод всехтоваров. Добавили фильтры
This commit is contained in:
@@ -3,79 +3,164 @@ API представления для приложения products.
|
||||
"""
|
||||
from django.http import JsonResponse
|
||||
from django.db import models
|
||||
from django.core.cache import cache
|
||||
|
||||
from ..models import Product, ProductVariantGroup
|
||||
|
||||
|
||||
def search_products_and_variants(request):
|
||||
"""
|
||||
API endpoint для поиска товаров и групп вариантов.
|
||||
API endpoint для поиска товаров и групп вариантов (совместимость с Select2).
|
||||
Используется для автокомплита при добавлении компонентов в комплект.
|
||||
|
||||
Параметры GET:
|
||||
- q: строка поиска
|
||||
- type: 'product' или 'variant' (опционально, если не указано - поиск по обоим)
|
||||
- q: строка поиска (term в Select2)
|
||||
- type: 'product' или 'variant' (опционально)
|
||||
- page: номер страницы для пагинации (по умолчанию 1)
|
||||
|
||||
Возвращает JSON список:
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Роза красная Freedom 50см",
|
||||
"sku": "PROD-000001",
|
||||
"type": "product",
|
||||
"price": "150.00"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Роза красная Freedom",
|
||||
"type": "variant",
|
||||
"count": 3
|
||||
Возвращает JSON в формате Select2:
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "Роза красная Freedom 50см (PROD-000001)",
|
||||
"sku": "PROD-000001",
|
||||
"price": "150.00"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"more": true
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
query = request.GET.get('q', '').strip()
|
||||
search_type = request.GET.get('type', 'all')
|
||||
|
||||
if not query or len(query) < 2:
|
||||
return JsonResponse({'results': []})
|
||||
page = int(request.GET.get('page', 1))
|
||||
page_size = 30
|
||||
|
||||
results = []
|
||||
|
||||
# Поиск товаров
|
||||
# Если поиска нет - показываем популярные товары
|
||||
if not query or len(query) < 2:
|
||||
# Кэшируем популярные товары на 1 час
|
||||
cache_key = f'popular_products_{search_type}'
|
||||
cached_results = cache.get(cache_key)
|
||||
|
||||
if cached_results:
|
||||
return JsonResponse(cached_results)
|
||||
|
||||
if search_type in ['all', 'product']:
|
||||
# Показываем последние добавленные активные товары
|
||||
products = Product.objects.filter(is_active=True)\
|
||||
.order_by('-created_at')[:page_size]\
|
||||
.values('id', 'name', 'sku', 'sale_price')
|
||||
|
||||
for product in products:
|
||||
text = product['name']
|
||||
if product['sku']:
|
||||
text += f" ({product['sku']})"
|
||||
|
||||
results.append({
|
||||
'id': product['id'],
|
||||
'text': text,
|
||||
'sku': product['sku'],
|
||||
'price': str(product['sale_price']) if product['sale_price'] else None
|
||||
})
|
||||
|
||||
response_data = {
|
||||
'results': results,
|
||||
'pagination': {'more': False}
|
||||
}
|
||||
cache.set(cache_key, response_data, 3600)
|
||||
return JsonResponse(response_data)
|
||||
|
||||
# Поиск товаров (регистронезависимый поиск с приоритетом точных совпадений)
|
||||
if search_type in ['all', 'product']:
|
||||
products = Product.objects.filter(
|
||||
models.Q(name__icontains=query) |
|
||||
models.Q(sku__icontains=query) |
|
||||
models.Q(description__icontains=query) |
|
||||
models.Q(search_keywords__icontains=query),
|
||||
is_active=True
|
||||
).values('id', 'name', 'sku', 'sale_price')[:10]
|
||||
# Нормализуем запрос - убираем лишние пробелы
|
||||
query_normalized = ' '.join(query.split())
|
||||
|
||||
from django.db.models import Case, When, IntegerField
|
||||
from django.conf import settings
|
||||
|
||||
# ВРЕМЕННЫЙ ФИХ для SQLite: удалить когда база данных будет PostgreSQL
|
||||
# SQLite не поддерживает регистронезависимый поиск для кириллицы в LIKE
|
||||
if 'sqlite' in settings.DATABASES['default']['ENGINE']:
|
||||
from django.db.models.functions import Lower
|
||||
query_lower = query_normalized.lower()
|
||||
|
||||
products_query = Product.objects.annotate(
|
||||
name_lower=Lower('name'),
|
||||
sku_lower=Lower('sku'),
|
||||
description_lower=Lower('description')
|
||||
).filter(
|
||||
models.Q(name_lower__contains=query_lower) |
|
||||
models.Q(sku_lower__contains=query_lower) |
|
||||
models.Q(description_lower__contains=query_lower),
|
||||
is_active=True
|
||||
).annotate(
|
||||
relevance=Case(
|
||||
When(name_lower=query_lower, then=3),
|
||||
When(name_lower__startswith=query_lower, then=2),
|
||||
default=1,
|
||||
output_field=IntegerField()
|
||||
)
|
||||
).order_by('-relevance', 'name')
|
||||
else:
|
||||
# Основное решение для PostgreSQL (работает корректно с кириллицей)
|
||||
products_query = Product.objects.filter(
|
||||
models.Q(name__icontains=query_normalized) |
|
||||
models.Q(sku__icontains=query_normalized) |
|
||||
models.Q(description__icontains=query_normalized),
|
||||
is_active=True
|
||||
).annotate(
|
||||
relevance=Case(
|
||||
When(name__iexact=query_normalized, then=3),
|
||||
When(name__istartswith=query_normalized, then=2),
|
||||
default=1,
|
||||
output_field=IntegerField()
|
||||
)
|
||||
).order_by('-relevance', 'name')
|
||||
|
||||
total_products = products_query.count()
|
||||
start = (page - 1) * page_size
|
||||
end = start + page_size
|
||||
|
||||
products = products_query[start:end].values('id', 'name', 'sku', 'sale_price')
|
||||
|
||||
for product in products:
|
||||
text = product['name']
|
||||
if product['sku']:
|
||||
text += f" ({product['sku']})"
|
||||
|
||||
results.append({
|
||||
'id': product['id'],
|
||||
'name': f"{product['name']} ({product['sku']})",
|
||||
'text': text,
|
||||
'sku': product['sku'],
|
||||
'type': 'product',
|
||||
'price': str(product['sale_price']),
|
||||
'display_name': product['name'],
|
||||
'display_price': f"{product['sale_price']:.2f} ₽"
|
||||
'price': str(product['sale_price']) if product['sale_price'] else None,
|
||||
'type': 'product'
|
||||
})
|
||||
|
||||
has_more = total_products > end
|
||||
else:
|
||||
has_more = False
|
||||
|
||||
# Поиск групп вариантов
|
||||
if search_type in ['all', 'variant']:
|
||||
variants = ProductVariantGroup.objects.filter(
|
||||
models.Q(name__icontains=query) |
|
||||
models.Q(description__icontains=query)
|
||||
).prefetch_related('products')[:10]
|
||||
).prefetch_related('products')[:page_size]
|
||||
|
||||
for variant in variants:
|
||||
count = variant.products.filter(is_active=True).count()
|
||||
results.append({
|
||||
'id': variant.id,
|
||||
'name': f"{variant.name} ({count} вариантов)",
|
||||
'text': f"{variant.name} ({count} вариантов)",
|
||||
'type': 'variant',
|
||||
'count': count
|
||||
})
|
||||
|
||||
return JsonResponse({'results': results})
|
||||
return JsonResponse({
|
||||
'results': results,
|
||||
'pagination': {'more': has_more if search_type == 'product' else False}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user