""" CRUD представления для товаров (Product). """ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView from django.urls import reverse_lazy from django.db.models import Q from itertools import chain from ..models import Product, ProductCategory, ProductTag, ProductKit from ..forms import ProductForm from .utils import handle_photos from ..models import ProductPhoto class ProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): model = Product template_name = 'products/product_list.html' context_object_name = 'products' permission_required = 'products.view_product' paginate_by = 10 def get_queryset(self): queryset = super().get_queryset() # Добавляем prefetch_related для оптимизации запросов к категориям queryset = queryset.prefetch_related('categories', 'photos', 'tags') # Улучшенный поиск по нескольким полям search_query = self.request.GET.get('search') if search_query: # Ищем по названию, артикулу, описанию, категориям и ключевым словам queryset = queryset.filter( Q(name__icontains=search_query) | Q(sku__icontains=search_query) | Q(description__icontains=search_query) | Q(categories__name__icontains=search_query) | Q(search_keywords__icontains=search_query) ).distinct() # Фильтр по категории category_id = self.request.GET.get('category') if category_id: queryset = queryset.filter(categories__id=category_id) # Фильтр по статусу is_active = self.request.GET.get('is_active') if is_active == '1': queryset = queryset.filter(is_active=True) elif is_active == '0': queryset = queryset.filter(is_active=False) # Фильтр по тегам tags = self.request.GET.getlist('tags') if tags: queryset = queryset.filter(tags__id__in=tags).distinct() return queryset.order_by('-created_at') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Данные для фильтров context['filters'] = { 'categories': ProductCategory.objects.filter(is_active=True), 'tags': ProductTag.objects.all(), 'current': { 'search': self.request.GET.get('search', ''), 'category': self.request.GET.get('category', ''), 'is_active': self.request.GET.get('is_active', ''), 'tags': self.request.GET.getlist('tags'), } } # Кнопки действий action_buttons = [] if self.request.user.has_perm('products.add_product'): action_buttons.append({ 'url': reverse_lazy('products:product-create'), 'text': 'Создать товар', 'class': 'btn-primary', 'icon': 'plus-circle' }) if self.request.user.has_perm('products.add_productkit'): action_buttons.append({ 'url': reverse_lazy('products:productkit-create'), 'text': 'Создать комплект', 'class': 'btn-outline-primary', 'icon': 'box-seam' }) action_buttons.append({ 'url': reverse_lazy('products:productkit-list'), 'text': 'К списку комплектов', 'class': 'btn-outline-secondary', 'icon': 'list' }) context['action_buttons'] = action_buttons return context class ProductCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): model = Product form_class = ProductForm template_name = 'products/product_form.html' permission_required = 'products.add_product' def get_success_url(self): return reverse_lazy('products:product-list') def form_valid(self, form): response = super().form_valid(form) # Handle photo uploads photo_errors = handle_photos(self.request, self.object, ProductPhoto, 'product') if photo_errors: for error in photo_errors: messages.error(self.request, error) messages.success(self.request, f'Товар "{form.instance.name}" успешно создан!') return response class ProductDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = Product template_name = 'products/product_detail.html' context_object_name = 'product' permission_required = 'products.view_product' def get_queryset(self): # Prefetch photos to avoid N+1 queries return super().get_queryset().prefetch_related('photos') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Добавляем фотографии товара в контекст context['product_photos'] = self.object.photos.all().order_by('order', 'created_at') context['photos_count'] = self.object.photos.count() return context class ProductUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = Product form_class = ProductForm template_name = 'products/product_form.html' permission_required = 'products.change_product' def get_success_url(self): return reverse_lazy('products:product-list') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Добавляем фотографии товара в контекст context['product_photos'] = self.object.photos.all().order_by('order', 'created_at') context['photos_count'] = self.object.photos.count() return context def form_valid(self, form): response = super().form_valid(form) # Handle photo uploads photo_errors = handle_photos(self.request, self.object, ProductPhoto, 'product') if photo_errors: for error in photo_errors: messages.error(self.request, error) messages.success(self.request, f'Товар "{form.instance.name}" успешно обновлен!') return response class ProductDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): model = Product template_name = 'products/product_confirm_delete.html' context_object_name = 'product' permission_required = 'products.delete_product' def get_success_url(self): messages.success(self.request, f'Товар "{self.object.name}" успешно удален!') return reverse_lazy('products:product-list') class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): """ Объединенное представление для товаров и комплектов. Показывает оба типа продуктов в одном списке. """ template_name = 'products/all_products_list.html' context_object_name = 'items' permission_required = 'products.view_product' paginate_by = 20 def get_queryset(self): # Получаем товары и комплекты (только постоянные комплекты) products = Product.objects.prefetch_related('categories', 'photos', 'tags') kits = ProductKit.objects.filter(is_temporary=False).prefetch_related('categories', 'photos') # Применяем фильтры search_query = self.request.GET.get('search') category_id = self.request.GET.get('category') is_active = self.request.GET.get('is_active') # Фильтрация по поиску if search_query: products = products.filter( Q(name__icontains=search_query) | Q(sku__icontains=search_query) | Q(description__icontains=search_query) | Q(categories__name__icontains=search_query) | Q(search_keywords__icontains=search_query) ).distinct() kits = kits.filter( Q(name__icontains=search_query) | Q(sku__icontains=search_query) | Q(description__icontains=search_query) | Q(categories__name__icontains=search_query) ).distinct() # Фильтрация по категории if category_id: products = products.filter(categories__id=category_id) kits = kits.filter(categories__id=category_id) # Фильтрация по статусу if is_active == '1': products = products.filter(is_active=True) kits = kits.filter(is_active=True) elif is_active == '0': products = products.filter(is_active=False) kits = kits.filter(is_active=False) # Добавляем type для различения в шаблоне products_list = list(products.order_by('-created_at')) for p in products_list: p.item_type = 'product' kits_list = list(kits.order_by('-created_at')) for k in kits_list: k.item_type = 'kit' # Объединяем и сортируем по дате создания combined = sorted( chain(products_list, kits_list), key=lambda x: x.created_at, reverse=True ) return combined def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Данные для фильтров context['filters'] = { 'categories': ProductCategory.objects.filter(is_active=True), 'tags': ProductTag.objects.all(), 'current': { 'search': self.request.GET.get('search', ''), 'category': self.request.GET.get('category', ''), 'is_active': self.request.GET.get('is_active', ''), } } # Кнопки действий action_buttons = [] if self.request.user.has_perm('products.add_product'): action_buttons.append({ 'url': reverse_lazy('products:product-create'), 'text': 'Создать товар', 'class': 'btn-primary', 'icon': 'plus-circle' }) if self.request.user.has_perm('products.add_productkit'): action_buttons.append({ 'url': reverse_lazy('products:productkit-create'), 'text': 'Создать комплект', 'class': 'btn-outline-primary', 'icon': 'box-seam' }) context['action_buttons'] = action_buttons return context