From ea4bb5a43b5dc7cfd7bfdaff8d871f2313c167ba Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sat, 8 Nov 2025 15:00:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BA=D1=80=D1=8B=D1=82=D1=8B=20=D0=B2?= =?UTF-8?q?=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BB=D0=B5=D0=BA=D1=82=D1=8B=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=D0=B0=20=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- myproject/products/admin.py | 19 ++++++++++++++++--- myproject/products/views/api_views.py | 17 +++++++++++------ myproject/products/views/product_views.py | 4 ++-- myproject/products/views/productkit_views.py | 3 +++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/myproject/products/admin.py b/myproject/products/admin.py index 7b7f20c..480cb67 100644 --- a/myproject/products/admin.py +++ b/myproject/products/admin.py @@ -508,11 +508,11 @@ class ProductAdmin(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_filter = (DeletedFilter, 'is_active', QualityLevelFilter, 'categories', 'tags') + 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', 'is_temporary', QualityLevelFilter, 'categories', 'tags') prepopulated_fields = {'slug': ('name',)} 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 = [ restore_items, delete_selected, @@ -530,6 +530,10 @@ class ProductKitAdmin(admin.ModelAdmin): 'fields': ('base_price', 'price', 'sale_price', 'price_adjustment_type', 'price_adjustment_value'), 'description': 'base_price - сумма цен компонентов (вычисляется автоматически). price - итоговая цена с учетом корректировок. sale_price - цена со скидкой (опционально).' }), + ('Временный комплект', { + 'fields': ('is_temporary', 'order'), + 'description': 'Временные комплекты создаются для конкретных заказов и не показываются в каталоге.' + }), ('Дополнительно', { 'fields': ('tags', 'is_active') }), @@ -552,6 +556,15 @@ class ProductKitAdmin(admin.ModelAdmin): return "-" 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('{}', url, obj.order.order_number) + return '-' + get_order_link.short_description = "Заказ" + def get_queryset(self, request): """Переопределяем queryset для доступа ко всем комплектам (включая удаленные)""" qs = ProductKit.all_objects.all() diff --git a/myproject/products/views/api_views.py b/myproject/products/views/api_views.py index cd2a3d3..37d7a3b 100644 --- a/myproject/products/views/api_views.py +++ b/myproject/products/views/api_views.py @@ -83,6 +83,8 @@ def search_products_and_variants(request): 'pagination': {'more': False} }) elif item_type == 'kit': + # Для комплектов: временные комплекты можно получать по ID (для заказов) + # но не показываем их в общем поиске kit = ProductKit.objects.get(id=numeric_id, is_active=True) return JsonResponse({ 'results': [{ @@ -91,7 +93,8 @@ def search_products_and_variants(request): 'sku': kit.sku, 'price': str(kit.price) if kit.price else None, 'actual_price': str(kit.actual_price) if kit.actual_price else '0', - 'type': 'kit' + 'type': 'kit', + 'is_temporary': kit.is_temporary }], 'pagination': {'more': False} }) @@ -142,8 +145,8 @@ def search_products_and_variants(request): }) 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]\ .values('id', 'name', 'sku', 'price', 'sale_price') @@ -270,7 +273,7 @@ def search_products_and_variants(request): # Поиск комплектов if search_type in ['all', 'kit']: - # Используем аналогичную логику для комплектов + # Используем аналогичную логику для комплектов (только постоянные) if 'sqlite' in settings.DATABASES['default']['ENGINE']: from django.db.models.functions import 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(sku_lower__contains=query_lower) | models.Q(description_lower__contains=query_lower), - is_active=True + is_active=True, + is_temporary=False ).annotate( relevance=Case( 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(sku__icontains=query_normalized) | models.Q(description__icontains=query_normalized), - is_active=True + is_active=True, + is_temporary=False ).annotate( relevance=Case( When(name__iexact=query_normalized, then=3), diff --git a/myproject/products/views/product_views.py b/myproject/products/views/product_views.py index e214de1..5416224 100644 --- a/myproject/products/views/product_views.py +++ b/myproject/products/views/product_views.py @@ -194,9 +194,9 @@ class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListV paginate_by = 20 def get_queryset(self): - # Получаем товары и комплекты + # Получаем товары и комплекты (только постоянные комплекты) 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') diff --git a/myproject/products/views/productkit_views.py b/myproject/products/views/productkit_views.py index f7bc295..ae1c611 100644 --- a/myproject/products/views/productkit_views.py +++ b/myproject/products/views/productkit_views.py @@ -24,6 +24,9 @@ class ProductKitListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): queryset = super().get_queryset() queryset = queryset.prefetch_related('categories', 'photos', 'kit_items', 'tags') + # Скрываем временные комплекты из общего каталога + queryset = queryset.filter(is_temporary=False) + # Поиск по названию search_query = self.request.GET.get('search') if search_query: