diff --git a/myproject/products/templates/products/all_products_list.html b/myproject/products/templates/products/all_products_list.html deleted file mode 100644 index 4dfd55e..0000000 --- a/myproject/products/templates/products/all_products_list.html +++ /dev/null @@ -1,258 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Все товары и комплекты{% endblock %} - -{% block content %} -
-

Товары

- - - {% include 'components/category_filter_buttons.html' with categories=filters.categories current_category=filters.current.category show_type_filters=True %} - - -
-
- -
-
- Поиск и фильтры -
- - {% if action_buttons %} - - {% endif %} -
- -
- - -
-
- -
- - -
- - -
- - -
- - - {% if filters.current.category %} - - {% endif %} - - -
- -
- - - Сброс - -
-
-
-
-
-
- - {% if items %} -
- - - - - - - - - - - - - - - - {% for item in items %} - - - - - - - - - - - - {% endfor %} - -
ТипФотоНазваниеАртикулКатегорияЦена продажиВ наличииСтатусДействия
- {% if item.item_type == 'product' %} - - - - {% else %} - - - - {% endif %} - - {% if item.photos.all %} - {% with photo=item.photos.first %} - {{ item.name }} - {% endwith %} - {% else %} - Нет фото - {% endif %} - - {% if item.item_type == 'product' %} - {{ item.name }} - {% else %} - {{ item.name }} - {% endif %} - {{ item.sku }} - {% if item.categories.all %} - {% for category in item.categories.all %} - {{ category.name }}{% if not forloop.last %} {% endif %} - {% endfor %} - {% else %} - - - {% endif %} - - {% if item.sale_price %} - {{ item.price|floatformat:2 }} руб. -
- {{ item.sale_price|floatformat:2 }} руб. - Акция - {% else %} - {{ item.actual_price|floatformat:2 }} руб. - {% endif %} -
- {% if item.item_type == 'product' %} - {% if item.in_stock %} - Да - {% else %} - Нет - {% endif %} - {% else %} - - - - {% endif %} - - {% if item.is_active %} - Активен - {% else %} - Неактивен - {% endif %} - -
- {% if item.item_type == 'product' %} - - - - {% if perms.products.change_product %} - - - - {% endif %} - {% if perms.products.delete_product %} - - - - {% endif %} - {% else %} - - - - {% if perms.products.change_productkit %} - - - - {% endif %} - {% if perms.products.delete_productkit %} - - - - {% endif %} - {% endif %} -
-
-
- - - {% if is_paginated %} - - {% endif %} - {% else %} -
-

Товары не найдены

-

В данный момент нет товаров или комплектов, соответствующих выбранным фильтрам.

-
- {% if perms.products.add_product %} - - Создать товар - - {% endif %} - {% if perms.products.add_productkit %} - - Создать комплект - - {% endif %} -
-
- {% endif %} -
-{% endblock %} diff --git a/myproject/products/templates/products/product_list.html b/myproject/products/templates/products/product_list.html deleted file mode 100644 index 597b68f..0000000 --- a/myproject/products/templates/products/product_list.html +++ /dev/null @@ -1,136 +0,0 @@ -{% extends 'base.html' %} -{% load quality_tags %} - -{% block title %}Список товаров{% endblock %} - -{% block content %} -
-

Список товаров

- - - {% include 'components/filter_panel.html' with title="Товары" filters=filters action_buttons=action_buttons %} - - {% if products %} -
- - - - - - - - - - - - - - - {% for product in products %} - - - - - - - - - - - {% endfor %} - -
ФотоНазваниеАртикулКатегорияЦена продажиВ наличииСтатусДействия
- {% if product.photos.all %} - {% with photo=product.photos.first %} - -
- {{ product.name }} - {{ photo|quality_icon_only }} -
- {% endwith %} - {% else %} - Нет фото - {% endif %} -
- {{ product.name }} - {{ product.sku }} - {% if product.categories.all %} - {% for category in product.categories.all %} - {{ category.name }}{% if not forloop.last %}, {% endif %} - {% endfor %} - {% else %} - - - {% endif %} - - {% if product.sale_price %} - {{ product.price }} руб. -
- {{ product.sale_price }} руб. - Акция - {% else %} - {{ product.price }} руб. - {% endif %} -
- {% if product.in_stock %} - В наличии - {% else %} - Нет - {% endif %} - - {% if product.status == 'active' %} - Активный - {% elif product.status == 'archived' %} - Архивный - {% elif product.status == 'discontinued' %} - Снят - {% else %} - {{ product.status }} - {% endif %} - - {% if perms.products.change_product %} - Изменить - {% endif %} - {% if perms.products.delete_product %} - Удалить - {% endif %} -
-
- - - {% if is_paginated %} - - {% endif %} - {% else %} -
-

Товары не найдены.

- {% if perms.products.add_product %} - Создать первый товар - {% endif %} -
- {% endif %} -
-{% endblock %} \ No newline at end of file diff --git a/myproject/products/templates/products/productkit_list.html b/myproject/products/templates/products/productkit_list.html deleted file mode 100644 index 09a47f2..0000000 --- a/myproject/products/templates/products/productkit_list.html +++ /dev/null @@ -1,141 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Список комплектов (букетов){% endblock %} - -{% block content %} -
-

Список комплектов (букетов)

- - - {% include 'components/filter_panel.html' with title="Комплекты" filters=filters action_buttons=action_buttons %} - - {% if kits %} -
- - - - - - - - - - - - - - - {% for kit in kits %} - - - - - - - - - - - {% endfor %} - -
ФотоНазваниеАртикулКатегорияЦена продажиКомпонентовСтатусДействия
- {% if kit.photos.all %} - {% with photo=kit.photos.first %} - - {{ kit.name }} - {% endwith %} - {% else %} - Нет фото - {% endif %} - - {{ kit.name }} - {{ kit.sku }} - {% if kit.categories.all %} - {% for category in kit.categories.all %} - {{ category.name }}{% if not forloop.last %}, {% endif %} - {% endfor %} - {% else %} - - - {% endif %} - - {% if kit.sale_price %} - {{ kit.price|floatformat:2 }} руб. -
- {{ kit.sale_price|floatformat:2 }} руб. - Акция - {% else %} - {{ kit.price|floatformat:2 }} руб. - {% endif %} -
- {{ kit.get_total_components_count }} шт - {% if kit.get_components_with_variants_count > 0 %} - - {{ kit.get_components_with_variants_count }} - - {% endif %} - - {% if kit.status == 'active' %} - Активный - {% elif kit.status == 'archived' %} - Архивный - {% elif kit.status == 'discontinued' %} - Снят - {% else %} - {{ kit.status }} - {% endif %} - - -
-
- - - {% if is_paginated %} - - {% endif %} - {% else %} -
-

Комплекты не найдены

-

В данный момент нет комплектов, соответствующих выбранным фильтрам.

- {% if perms.products.add_productkit %} - - Создать первый комплект - - {% endif %} -
- {% endif %} -
-{% endblock %} diff --git a/myproject/products/templates/products/products_list.html b/myproject/products/templates/products/products_list.html new file mode 100644 index 0000000..8c580be --- /dev/null +++ b/myproject/products/templates/products/products_list.html @@ -0,0 +1,333 @@ +{% extends 'base.html' %} +{% load quality_tags %} + +{% block title %}Товары и комплекты{% endblock %} + +{% block content %} +
+

+ Товары и комплекты +

+ + +
+
+
+
+ Фильтры +
+ +
+ +
+ +
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+ +
+ +
+ + +
+ + +
+ + + {% if filters.current.tags %} + Выбрано: {{ filters.current.tags|length }} + {% endif %} +
+ +
+ +
+ + + Сбросить + +
+
+
+
+
+
+ + {% if items %} +
+ + + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + + + + {% endfor %} + +
ФотоНазваниеАртикулТипКатегорияЦенаВ наличииСтатусДействия
+ {% if item.photos.all %} + {% with photo=item.photos.first %} +
+ {{ item.name }} + {% if item.item_type == 'product' %} + + {{ photo|quality_icon_only }} + + {% endif %} +
+ {% endwith %} + {% else %} + Нет фото + {% endif %} +
+ {% if item.item_type == 'product' %} + {{ item.name }} + {% else %} + {{ item.name }} + {% endif %} + {{ item.sku }} + {% if item.item_type == 'product' %} + + {% else %} + + {% endif %} + + {% for category in item.categories.all %} + {{ category.name }} + {% empty %} + - + {% endfor %} + + {% if item.sale_price %} +
{{ item.price|floatformat:2 }} руб.
+ {{ item.sale_price|floatformat:2 }} руб. + {% else %} + {{ item.price|floatformat:2 }} руб. + {% endif %} +
+ {% if item.item_type == 'product' %} + {% if item.in_stock %} + Да + {% else %} + Нет + {% endif %} + {% else %} + - + {% endif %} + + {% if item.status == 'active' %} + Активный + {% elif item.status == 'archived' %} + Архивный + {% elif item.status == 'discontinued' %} + Снят + {% else %} + {{ item.get_status_display }} + {% endif %} + +
+ {% if item.item_type == 'product' %} + + + + {% if perms.products.change_product %} + + + + {% endif %} + {% if perms.products.delete_product %} + + + + {% endif %} + {% else %} + + + + {% if perms.products.change_productkit %} + + + + {% endif %} + {% if perms.products.delete_productkit %} + + + + {% endif %} + {% endif %} +
+
+
+ + + {% if is_paginated %} + + {% endif %} + {% else %} +
+

Товары не найдены

+

В данный момент нет товаров или комплектов, соответствующих выбранным фильтрам.

+
+ {% if perms.products.add_product %} + + Создать товар + + {% endif %} + {% if perms.products.add_productkit %} + + Создать комплект + + {% endif %} +
+
+ {% endif %} +
+ + +{% endblock %} diff --git a/myproject/products/templates/products/products_unified_list.html b/myproject/products/templates/products/products_unified_list.html deleted file mode 100644 index 96918ba..0000000 --- a/myproject/products/templates/products/products_unified_list.html +++ /dev/null @@ -1,189 +0,0 @@ -{% extends 'base.html' %} -{% load quality_tags %} - -{% block title %}Список товаров и комплектов{% endblock %} - -{% block content %} -
-

Товары и Комплекты

- - -
-
-
-
- Фильтры -
- -
- -
- -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
- - Сброс -
-
-
-
-
- - {% if items %} -
- - - - - - - - - - - - - - - - - {% for item in items %} - - - - - - - - - - - - - {% endfor %} - -
ФотоНазваниеАртикулТипКатегорияЦенаВ наличииКомпонентыСтатусДействия
- {% with photo=item.photos.first %} -
- {{ item.name }} - {% if item.item_type == 'product' and photo %} - {{ photo|quality_icon_only }} - {% endif %} -
- {% endwith %} -
- {{ item.name }} - {{ item.sku }} - {% if item.item_type == 'product' %} - Товар - {% else %} - Комплект - {% endif %} - - {% for category in item.categories.all %} - {{ category.name }} - {% empty %} - - - {% endfor %} - - {% if item.sale_price %} - {{ item.price|floatformat:2 }} руб.
- {{ item.sale_price|floatformat:2 }} руб. - {% else %} - {{ item.price|floatformat:2 }} руб. - {% endif %} -
- {% if item.item_type == 'product' %} - {% if item.in_stock %} - Да - {% else %} - Нет - {% endif %} - {% else %} - - - {% endif %} - - {% if item.item_type == 'kit' %} - {{ item.get_total_components_count }} шт - {% else %} - - - {% endif %} - - {{ item.get_status_display }} - -
- - {% if item.item_type == 'product' and perms.products.change_product %} - - {% elif item.item_type == 'kit' and perms.products.change_productkit %} - - {% endif %} - {% if item.item_type == 'product' and perms.products.delete_product %} - - {% elif item.item_type == 'kit' and perms.products.delete_productkit %} - - {% endif %} -
-
-
- - {% include 'components/pagination.html' %} - - {% else %} -
-

Товары или комплекты не найдены.

-
- {% endif %} -
-{% endblock %} diff --git a/myproject/products/urls.py b/myproject/products/urls.py index 56455e5..e10abf7 100644 --- a/myproject/products/urls.py +++ b/myproject/products/urls.py @@ -6,35 +6,37 @@ from .views import photo_status_api app_name = 'products' urlpatterns = [ - # Combined view for products and kits - path('', views.CombinedProductListView.as_view(), name='all-products'), - + # Main unified list for products and kits (default view) + path('', views.CombinedProductListView.as_view(), name='products-list'), + + # Legacy URLs for backward compatibility + path('all/', views.CombinedProductListView.as_view(), name='all-products'), + path('products/', views.ProductListView.as_view(), name='product-list-legacy'), + path('kits/', views.ProductKitListView.as_view(), name='productkit-list'), # CRUD URLs for Product - path('products/', views.ProductListView.as_view(), name='product-list'), - path('create/', views.ProductCreateView.as_view(), name='product-create'), - path('/', views.ProductDetailView.as_view(), name='product-detail'), - path('/update/', views.ProductUpdateView.as_view(), name='product-update'), - path('/delete/', views.ProductDeleteView.as_view(), name='product-delete'), + path('product/create/', views.ProductCreateView.as_view(), name='product-create'), + path('product//', views.ProductDetailView.as_view(), name='product-detail'), + path('product//update/', views.ProductUpdateView.as_view(), name='product-update'), + path('product//delete/', views.ProductDeleteView.as_view(), name='product-delete'), - # Photo management - path('photo//delete/', views.product_photo_delete, name='product-photo-delete'), - path('photo//set-main/', views.product_photo_set_main, name='product-photo-set-main'), - path('photo//move-up/', views.product_photo_move_up, name='product-photo-move-up'), - path('photo//move-down/', views.product_photo_move_down, name='product-photo-move-down'), + # Photo management for Product + path('product/photo//delete/', views.product_photo_delete, name='product-photo-delete'), + path('product/photo//set-main/', views.product_photo_set_main, name='product-photo-set-main'), + path('product/photo//move-up/', views.product_photo_move_up, name='product-photo-move-up'), + path('product/photo//move-down/', views.product_photo_move_down, name='product-photo-move-down'), # CRUD URLs for ProductKit (комплекты/букеты) - path('kits/', views.ProductKitListView.as_view(), name='productkit-list'), - path('kits/create/', views.ProductKitCreateView.as_view(), name='productkit-create'), - path('kits//', views.ProductKitDetailView.as_view(), name='productkit-detail'), - path('kits//update/', views.ProductKitUpdateView.as_view(), name='productkit-update'), - path('kits//delete/', views.ProductKitDeleteView.as_view(), name='productkit-delete'), - path('kits//make-permanent/', views.ProductKitMakePermanentView.as_view(), name='productkit-make-permanent'), + path('kit/create/', views.ProductKitCreateView.as_view(), name='productkit-create'), + path('kit//', views.ProductKitDetailView.as_view(), name='productkit-detail'), + path('kit//update/', views.ProductKitUpdateView.as_view(), name='productkit-update'), + path('kit//delete/', views.ProductKitDeleteView.as_view(), name='productkit-delete'), + path('kit//make-permanent/', views.ProductKitMakePermanentView.as_view(), name='productkit-make-permanent'), # Photo management for ProductKit - path('kits/photo//delete/', views.productkit_photo_delete, name='productkit-photo-delete'), - path('kits/photo//set-main/', views.productkit_photo_set_main, name='productkit-photo-set-main'), - path('kits/photo//move-up/', views.productkit_photo_move_up, name='productkit-photo-move-up'), - path('kits/photo//move-down/', views.productkit_photo_move_down, name='productkit-photo-move-down'), + path('kit/photo//delete/', views.productkit_photo_delete, name='productkit-photo-delete'), + path('kit/photo//set-main/', views.productkit_photo_set_main, name='productkit-photo-set-main'), + path('kit/photo//move-up/', views.productkit_photo_move_up, name='productkit-photo-move-up'), + path('kit/photo//move-down/', views.productkit_photo_move_down, name='productkit-photo-move-down'), # API endpoints path('api/search-products-variants/', views.search_products_and_variants, name='api-search-products-variants'), diff --git a/myproject/products/views/product_views.py b/myproject/products/views/product_views.py index 017743e..054f82e 100644 --- a/myproject/products/views/product_views.py +++ b/myproject/products/views/product_views.py @@ -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 = [] diff --git a/myproject/products/views/productkit_views.py b/myproject/products/views/productkit_views.py index d735cb4..13e4c5d 100644 --- a/myproject/products/views/productkit_views.py +++ b/myproject/products/views/productkit_views.py @@ -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):