Объединение списков товаров и комплектов в единый интерфейс
- Создан единый шаблон products_list.html для отображения товаров и комплектов - Удалены дублирующиеся шаблоны (product_list, productkit_list, products_unified_list, all_products_list) - Добавлены фильтры: тип (все/товары/комплекты), категория, статус, наличие, теги - Обновлен CombinedProductListView с поддержкой фильтрации по типу и тегам - Изменены URL маршруты: главная страница /products/ теперь показывает объединенный список - Обновлены success_url во всех CRUD представлениях для редиректа на объединенный список - Добавлена фильтрация по тегам с отображением количества выбранных элементов - Улучшена UX: компактный select для тегов с счетчиком выбранных - Все комментарии в коде переведены на русский язык
This commit is contained in:
@@ -115,7 +115,7 @@ class ProductCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView)
|
||||
permission_required = 'products.add_product'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('products:product-list')
|
||||
return reverse_lazy('products:products-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
from django.db import IntegrityError
|
||||
@@ -123,7 +123,7 @@ class ProductCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView)
|
||||
try:
|
||||
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:
|
||||
@@ -162,7 +162,7 @@ class ProductDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView)
|
||||
permission_required = 'products.view_product'
|
||||
|
||||
def get_queryset(self):
|
||||
# Prefetch photos to avoid N+1 queries
|
||||
# Предзагрузка фотографий для избежания N+1 запросов
|
||||
return super().get_queryset().prefetch_related('photos')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -180,7 +180,7 @@ class ProductUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView)
|
||||
permission_required = 'products.change_product'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('products:product-list')
|
||||
return reverse_lazy('products:products-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
@@ -195,7 +195,7 @@ class ProductUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView)
|
||||
try:
|
||||
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:
|
||||
@@ -235,29 +235,34 @@ class ProductDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView)
|
||||
|
||||
def get_success_url(self):
|
||||
messages.success(self.request, f'Товар "{self.object.name}" успешно удален!')
|
||||
return reverse_lazy('products:product-list')
|
||||
return reverse_lazy('products:products-list')
|
||||
|
||||
|
||||
class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
"""
|
||||
Объединенное представление для товаров и комплектов.
|
||||
Показывает оба типа продуктов в одном списке.
|
||||
Показывает оба типа продуктов в одном списке с возможностью фильтрации по типу.
|
||||
"""
|
||||
template_name = 'products/all_products_list.html'
|
||||
template_name = 'products/products_list.html'
|
||||
context_object_name = 'items'
|
||||
permission_required = 'products.view_product'
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
# Получаем фильтр по типу
|
||||
type_filter = self.request.GET.get('type', 'all')
|
||||
|
||||
# Получаем товары и комплекты (только постоянные комплекты)
|
||||
products = Product.objects.prefetch_related('categories', 'photos', 'tags')
|
||||
kits = ProductKit.objects.filter(is_temporary=False).prefetch_related('categories', 'photos')
|
||||
kits = ProductKit.objects.filter(is_temporary=False).prefetch_related('categories', 'photos', 'tags')
|
||||
|
||||
# Применяем фильтры
|
||||
search_query = self.request.GET.get('search')
|
||||
category_id = self.request.GET.get('category')
|
||||
status_filter = self.request.GET.get('status')
|
||||
is_active_filter = self.request.GET.get('is_active')
|
||||
in_stock_filter = self.request.GET.get('in_stock')
|
||||
tags = self.request.GET.getlist('tags')
|
||||
|
||||
# Фильтрация по поиску
|
||||
if search_query:
|
||||
@@ -293,15 +298,31 @@ class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListV
|
||||
elif is_active_filter == '0':
|
||||
products = products.filter(status__in=['archived', 'discontinued'])
|
||||
kits = kits.filter(status__in=['archived', 'discontinued'])
|
||||
|
||||
# Фильтрация по наличию (только для товаров)
|
||||
if in_stock_filter == '1':
|
||||
products = products.filter(in_stock=True)
|
||||
elif in_stock_filter == '0':
|
||||
products = products.filter(in_stock=False)
|
||||
|
||||
# Фильтрация по тегам
|
||||
if tags:
|
||||
products = products.filter(tags__id__in=tags).distinct()
|
||||
kits = kits.filter(tags__id__in=tags).distinct()
|
||||
|
||||
# Добавляем 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'
|
||||
# Применяем фильтр по типу
|
||||
products_list = []
|
||||
kits_list = []
|
||||
|
||||
if type_filter in ['all', 'products']:
|
||||
products_list = list(products.order_by('-created_at'))
|
||||
for p in products_list:
|
||||
p.item_type = 'product'
|
||||
|
||||
if type_filter in ['all', 'kits']:
|
||||
kits_list = list(kits.order_by('-created_at'))
|
||||
for k in kits_list:
|
||||
k.item_type = 'kit'
|
||||
|
||||
# Объединяем и сортируем по дате создания
|
||||
combined = sorted(
|
||||
@@ -315,16 +336,25 @@ class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListV
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# Получаем список статусов из модели
|
||||
from ..models.base import BaseProductEntity
|
||||
item_statuses = BaseProductEntity.STATUS_CHOICES
|
||||
|
||||
# Данные для фильтров
|
||||
context['filters'] = {
|
||||
'categories': ProductCategory.objects.filter(is_active=True),
|
||||
'tags': ProductTag.objects.all(),
|
||||
'tags': ProductTag.objects.filter(is_active=True),
|
||||
'current': {
|
||||
'search': self.request.GET.get('search', ''),
|
||||
'category': self.request.GET.get('category', ''),
|
||||
'status': self.request.GET.get('status', ''),
|
||||
'type': self.request.GET.get('type', 'all'),
|
||||
'in_stock': self.request.GET.get('in_stock', ''),
|
||||
'tags': [int(tag) for tag in self.request.GET.getlist('tags') if tag.isdigit()],
|
||||
}
|
||||
}
|
||||
|
||||
context['item_statuses'] = item_statuses
|
||||
|
||||
# Кнопки действий
|
||||
action_buttons = []
|
||||
|
||||
@@ -214,7 +214,7 @@ class ProductKitCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVi
|
||||
f'Комплект "{self.object.name}" успешно создан!'
|
||||
)
|
||||
|
||||
return redirect('products:productkit-list')
|
||||
return redirect('products:products-list')
|
||||
except IntegrityError as e:
|
||||
# Обработка нарушения уникальности в БД
|
||||
error_msg = str(e).lower()
|
||||
@@ -416,7 +416,7 @@ class ProductKitUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVi
|
||||
if self.request.POST.get('action') == 'continue':
|
||||
return redirect('products:productkit-update', pk=self.object.pk)
|
||||
else:
|
||||
return redirect('products:productkit-list')
|
||||
return redirect('products:products-list')
|
||||
except IntegrityError as e:
|
||||
# Обработка нарушения уникальности в БД
|
||||
error_msg = str(e).lower()
|
||||
@@ -450,7 +450,7 @@ class ProductKitUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVi
|
||||
return self.render_to_response(context)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('products:productkit-list')
|
||||
return reverse_lazy('products:products-list')
|
||||
|
||||
|
||||
class ProductKitDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
|
||||
Reference in New Issue
Block a user