Files
octopus/myproject/products/views/api_views.py
Andrey Smakotin dbb94e9d2a refactor: Добавить в_stock в API и улучшить загрузку данных в форме вариантов
Изменения в API:
- Добавить поле in_stock в возвращаемые данные товаров
- Добавить поддержку запроса товара по ID (параметр ?id=) для получения актуальных данных
- Исправить дублирование информации одного товара на остальные в таблице вариантов

Изменения в форме:
- Добавить автоматическую загрузку данных товара при загрузке страницы для существующих строк
- Правильно отображать артикул, цену и наличие для каждого товара в группе

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 00:03:40 +03:00

190 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 для поиска товаров и групп вариантов (совместимость с Select2).
Используется для автокомплита при добавлении компонентов в комплект.
Параметры GET:
- q: строка поиска (term в Select2)
- id: ID товара для получения его данных
- type: 'product' или 'variant' (опционально)
- page: номер страницы для пагинации (по умолчанию 1)
Возвращает JSON в формате Select2:
{
"results": [
{
"id": 1,
"text": "Роза красная Freedom 50см (PROD-000001)",
"sku": "PROD-000001",
"price": "150.00",
"in_stock": true
}
],
"pagination": {
"more": true
}
}
"""
# Если передан ID товара - получаем его данные напрямую
product_id = request.GET.get('id', '').strip()
if product_id:
try:
product = Product.objects.get(id=int(product_id), is_active=True)
return JsonResponse({
'results': [{
'id': product.id,
'text': f"{product.name} ({product.sku})" if product.sku else product.name,
'sku': product.sku,
'price': str(product.sale_price) if product.sale_price else None,
'in_stock': product.in_stock,
'type': 'product'
}],
'pagination': {'more': False}
})
except (Product.DoesNotExist, ValueError):
return JsonResponse({'results': [], 'pagination': {'more': False}})
query = request.GET.get('q', '').strip()
search_type = request.GET.get('type', 'all')
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', 'in_stock')
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,
'in_stock': product['in_stock']
})
response_data = {
'results': results,
'pagination': {'more': False}
}
cache.set(cache_key, response_data, 3600)
return JsonResponse(response_data)
# Поиск товаров (регистронезависимый поиск с приоритетом точных совпадений)
if search_type in ['all', 'product']:
# Нормализуем запрос - убираем лишние пробелы
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', 'in_stock')
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,
'in_stock': product['in_stock'],
'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')[:page_size]
for variant in variants:
count = variant.products.filter(is_active=True).count()
results.append({
'id': variant.id,
'text': f"{variant.name} ({count} вариантов)",
'type': 'variant',
'count': count
})
return JsonResponse({
'results': results,
'pagination': {'more': has_more if search_type == 'product' else False}
})