Скрыты временные комплекты из каталога и поиска
Views: - ProductKitListView: фильтр is_temporary=False - CombinedProductListView: фильтр is_temporary=False для комплектов - API search: фильтр is_temporary=False в поиске и популярных Admin: - Добавлен фильтр по is_temporary - Добавлено отображение статуса временного комплекта в списке - Добавлена ссылка на заказ для временных комплектов - Добавлен раздел "Временный комплект" в fieldsets Теперь временные комплекты не показываются в общем каталоге, но доступны в админке и по прямой ссылке (для заказов). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -508,11 +508,11 @@ class ProductAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class ProductKitAdmin(admin.ModelAdmin):
|
class ProductKitAdmin(admin.ModelAdmin):
|
||||||
list_display = ('photo_with_quality', 'name', 'slug', 'get_categories_display', 'get_price_display', 'is_active', 'get_deleted_status')
|
list_display = ('photo_with_quality', 'name', 'slug', 'get_categories_display', 'get_price_display', 'is_temporary', 'get_order_link', 'is_active', 'get_deleted_status')
|
||||||
list_filter = (DeletedFilter, 'is_active', QualityLevelFilter, 'categories', 'tags')
|
list_filter = (DeletedFilter, 'is_active', 'is_temporary', QualityLevelFilter, 'categories', 'tags')
|
||||||
prepopulated_fields = {'slug': ('name',)}
|
prepopulated_fields = {'slug': ('name',)}
|
||||||
filter_horizontal = ('categories', 'tags')
|
filter_horizontal = ('categories', 'tags')
|
||||||
readonly_fields = ('photo_preview_large', 'base_price', 'deleted_at', 'deleted_by')
|
readonly_fields = ('photo_preview_large', 'base_price', 'deleted_at', 'deleted_by', 'order')
|
||||||
actions = [
|
actions = [
|
||||||
restore_items,
|
restore_items,
|
||||||
delete_selected,
|
delete_selected,
|
||||||
@@ -530,6 +530,10 @@ class ProductKitAdmin(admin.ModelAdmin):
|
|||||||
'fields': ('base_price', 'price', 'sale_price', 'price_adjustment_type', 'price_adjustment_value'),
|
'fields': ('base_price', 'price', 'sale_price', 'price_adjustment_type', 'price_adjustment_value'),
|
||||||
'description': 'base_price - сумма цен компонентов (вычисляется автоматически). price - итоговая цена с учетом корректировок. sale_price - цена со скидкой (опционально).'
|
'description': 'base_price - сумма цен компонентов (вычисляется автоматически). price - итоговая цена с учетом корректировок. sale_price - цена со скидкой (опционально).'
|
||||||
}),
|
}),
|
||||||
|
('Временный комплект', {
|
||||||
|
'fields': ('is_temporary', 'order'),
|
||||||
|
'description': 'Временные комплекты создаются для конкретных заказов и не показываются в каталоге.'
|
||||||
|
}),
|
||||||
('Дополнительно', {
|
('Дополнительно', {
|
||||||
'fields': ('tags', 'is_active')
|
'fields': ('tags', 'is_active')
|
||||||
}),
|
}),
|
||||||
@@ -552,6 +556,15 @@ class ProductKitAdmin(admin.ModelAdmin):
|
|||||||
return "-"
|
return "-"
|
||||||
get_price_display.short_description = "Цена"
|
get_price_display.short_description = "Цена"
|
||||||
|
|
||||||
|
def get_order_link(self, obj):
|
||||||
|
"""Отображение ссылки на заказ для временных комплектов"""
|
||||||
|
if obj.order:
|
||||||
|
from django.urls import reverse
|
||||||
|
url = reverse('admin:orders_order_change', args=[obj.order.pk])
|
||||||
|
return format_html('<a href="{}">{}</a>', url, obj.order.order_number)
|
||||||
|
return '-'
|
||||||
|
get_order_link.short_description = "Заказ"
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
"""Переопределяем queryset для доступа ко всем комплектам (включая удаленные)"""
|
"""Переопределяем queryset для доступа ко всем комплектам (включая удаленные)"""
|
||||||
qs = ProductKit.all_objects.all()
|
qs = ProductKit.all_objects.all()
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ def search_products_and_variants(request):
|
|||||||
'pagination': {'more': False}
|
'pagination': {'more': False}
|
||||||
})
|
})
|
||||||
elif item_type == 'kit':
|
elif item_type == 'kit':
|
||||||
|
# Для комплектов: временные комплекты можно получать по ID (для заказов)
|
||||||
|
# но не показываем их в общем поиске
|
||||||
kit = ProductKit.objects.get(id=numeric_id, is_active=True)
|
kit = ProductKit.objects.get(id=numeric_id, is_active=True)
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'results': [{
|
'results': [{
|
||||||
@@ -91,7 +93,8 @@ def search_products_and_variants(request):
|
|||||||
'sku': kit.sku,
|
'sku': kit.sku,
|
||||||
'price': str(kit.price) if kit.price else None,
|
'price': str(kit.price) if kit.price else None,
|
||||||
'actual_price': str(kit.actual_price) if kit.actual_price else '0',
|
'actual_price': str(kit.actual_price) if kit.actual_price else '0',
|
||||||
'type': 'kit'
|
'type': 'kit',
|
||||||
|
'is_temporary': kit.is_temporary
|
||||||
}],
|
}],
|
||||||
'pagination': {'more': False}
|
'pagination': {'more': False}
|
||||||
})
|
})
|
||||||
@@ -142,8 +145,8 @@ def search_products_and_variants(request):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if search_type in ['all', 'kit']:
|
if search_type in ['all', 'kit']:
|
||||||
# Показываем последние добавленные активные комплекты
|
# Показываем последние добавленные активные комплекты (только постоянные)
|
||||||
kits = ProductKit.objects.filter(is_active=True)\
|
kits = ProductKit.objects.filter(is_active=True, is_temporary=False)\
|
||||||
.order_by('-created_at')[:page_size]\
|
.order_by('-created_at')[:page_size]\
|
||||||
.values('id', 'name', 'sku', 'price', 'sale_price')
|
.values('id', 'name', 'sku', 'price', 'sale_price')
|
||||||
|
|
||||||
@@ -270,7 +273,7 @@ def search_products_and_variants(request):
|
|||||||
|
|
||||||
# Поиск комплектов
|
# Поиск комплектов
|
||||||
if search_type in ['all', 'kit']:
|
if search_type in ['all', 'kit']:
|
||||||
# Используем аналогичную логику для комплектов
|
# Используем аналогичную логику для комплектов (только постоянные)
|
||||||
if 'sqlite' in settings.DATABASES['default']['ENGINE']:
|
if 'sqlite' in settings.DATABASES['default']['ENGINE']:
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
query_lower = query_normalized.lower()
|
query_lower = query_normalized.lower()
|
||||||
@@ -283,7 +286,8 @@ def search_products_and_variants(request):
|
|||||||
models.Q(name_lower__contains=query_lower) |
|
models.Q(name_lower__contains=query_lower) |
|
||||||
models.Q(sku_lower__contains=query_lower) |
|
models.Q(sku_lower__contains=query_lower) |
|
||||||
models.Q(description_lower__contains=query_lower),
|
models.Q(description_lower__contains=query_lower),
|
||||||
is_active=True
|
is_active=True,
|
||||||
|
is_temporary=False
|
||||||
).annotate(
|
).annotate(
|
||||||
relevance=Case(
|
relevance=Case(
|
||||||
When(name_lower=query_lower, then=3),
|
When(name_lower=query_lower, then=3),
|
||||||
@@ -297,7 +301,8 @@ def search_products_and_variants(request):
|
|||||||
models.Q(name__icontains=query_normalized) |
|
models.Q(name__icontains=query_normalized) |
|
||||||
models.Q(sku__icontains=query_normalized) |
|
models.Q(sku__icontains=query_normalized) |
|
||||||
models.Q(description__icontains=query_normalized),
|
models.Q(description__icontains=query_normalized),
|
||||||
is_active=True
|
is_active=True,
|
||||||
|
is_temporary=False
|
||||||
).annotate(
|
).annotate(
|
||||||
relevance=Case(
|
relevance=Case(
|
||||||
When(name__iexact=query_normalized, then=3),
|
When(name__iexact=query_normalized, then=3),
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListV
|
|||||||
paginate_by = 20
|
paginate_by = 20
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
# Получаем товары и комплекты
|
# Получаем товары и комплекты (только постоянные комплекты)
|
||||||
products = Product.objects.prefetch_related('categories', 'photos', 'tags')
|
products = Product.objects.prefetch_related('categories', 'photos', 'tags')
|
||||||
kits = ProductKit.objects.prefetch_related('categories', 'photos')
|
kits = ProductKit.objects.filter(is_temporary=False).prefetch_related('categories', 'photos')
|
||||||
|
|
||||||
# Применяем фильтры
|
# Применяем фильтры
|
||||||
search_query = self.request.GET.get('search')
|
search_query = self.request.GET.get('search')
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ class ProductKitListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset()
|
||||||
queryset = queryset.prefetch_related('categories', 'photos', 'kit_items', 'tags')
|
queryset = queryset.prefetch_related('categories', 'photos', 'kit_items', 'tags')
|
||||||
|
|
||||||
|
# Скрываем временные комплекты из общего каталога
|
||||||
|
queryset = queryset.filter(is_temporary=False)
|
||||||
|
|
||||||
# Поиск по названию
|
# Поиск по названию
|
||||||
search_query = self.request.GET.get('search')
|
search_query = self.request.GET.get('search')
|
||||||
if search_query:
|
if search_query:
|
||||||
|
|||||||
Reference in New Issue
Block a user