From 4549b2c2c2e21902bc0eaf451da6750daa312314 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Mon, 24 Nov 2025 00:31:37 +0300 Subject: [PATCH] Feat: Add catalog page with category tree and product grid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create catalog view with recursive category tree building - Add left-side category tree with expand/collapse functionality - Add right-side product/kit grid with filtering and search - Include category navigation with product/kit counts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../products/templates/products/catalog.html | 217 ++++++++++++++++++ .../templates/products/catalog_tree_node.html | 45 ++++ .../partials/catalog_product_row.html | 22 ++ .../products/partials/catalog_tree_node.html | 25 ++ myproject/products/urls.py | 5 +- myproject/products/views/__init__.py | 6 + myproject/products/views/catalog_views.py | 52 +++++ myproject/templates/navbar.html | 19 +- 8 files changed, 382 insertions(+), 9 deletions(-) create mode 100644 myproject/products/templates/products/catalog.html create mode 100644 myproject/products/templates/products/catalog_tree_node.html create mode 100644 myproject/products/templates/products/partials/catalog_product_row.html create mode 100644 myproject/products/templates/products/partials/catalog_tree_node.html create mode 100644 myproject/products/views/catalog_views.py diff --git a/myproject/products/templates/products/catalog.html b/myproject/products/templates/products/catalog.html new file mode 100644 index 0000000..f5a8b73 --- /dev/null +++ b/myproject/products/templates/products/catalog.html @@ -0,0 +1,217 @@ +{% extends 'base.html' %} + +{% block title %}Каталог{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+ +
+
+
+ Категории +
+ + +
+
+
+ {% if category_tree %} + {% for node in category_tree %} + {% include 'products/catalog_tree_node.html' with node=node %} + {% endfor %} + {% else %} +
+ Категорий нет +
+ {% endif %} +
+
+
+ + +
+
+
+ Товары и комплекты +
+ +
+ + + +
+
+
+
+
+ {% for item in items %} +
+
+
+ {% if item.main_photo %} + {{ item.name }} + {% else %} +
+ +
+ {% endif %} + + {% if item.item_type == 'kit' %}К{% else %}Т{% endif %} + +
+
+
{{ item.name }}
+
+ {{ item.actual_price|floatformat:0 }} ₽ + {{ item.sku }} +
+
+
+
+ {% empty %} +
+ +

Каталог пуст

+
+ {% endfor %} +
+
+
+
+
+
+ + +{% endblock %} diff --git a/myproject/products/templates/products/catalog_tree_node.html b/myproject/products/templates/products/catalog_tree_node.html new file mode 100644 index 0000000..353c9df --- /dev/null +++ b/myproject/products/templates/products/catalog_tree_node.html @@ -0,0 +1,45 @@ +{% comment %} +Рекурсивный узел дерева категорий. +При клике раскрывается и показывает товары/комплекты текстом. +{% endcomment %} +
+
+ {% if node.children or node.category.products.exists or node.category.kits.exists %} + + {% else %} + + {% endif %} + + {{ node.category.name }} + {% with products_count=node.category.products.count kits_count=node.category.kits.count %} + {% if products_count or kits_count %} + {{ products_count|add:kits_count }} + {% endif %} + {% endwith %} +
+ + {% if node.children %} +
+ {% for child in node.children %} + {% include 'products/catalog_tree_node.html' with node=child %} + {% endfor %} +
+ {% endif %} + + {% if node.category.products.exists or node.category.kits.exists %} +
+ {% for product in node.category.products.all %} + + {% endfor %} + {% for kit in node.category.kits.all %} +
+ К + {{ kit.name }} +
+ {% endfor %} +
+ {% endif %} +
diff --git a/myproject/products/templates/products/partials/catalog_product_row.html b/myproject/products/templates/products/partials/catalog_product_row.html new file mode 100644 index 0000000..5357098 --- /dev/null +++ b/myproject/products/templates/products/partials/catalog_product_row.html @@ -0,0 +1,22 @@ +{% comment %} +Строка товара/комплекта в дереве каталога. +{% endcomment %} +
+ + {% if item.main_photo %} + + {% else %} +
+ +
+ {% endif %} +
+ + {{ item.name }} + +
{{ item.sku }}
+
+ + {% if item.item_type == 'kit' %}К{% else %}Т{% endif %} + +
diff --git a/myproject/products/templates/products/partials/catalog_tree_node.html b/myproject/products/templates/products/partials/catalog_tree_node.html new file mode 100644 index 0000000..450e8c8 --- /dev/null +++ b/myproject/products/templates/products/partials/catalog_tree_node.html @@ -0,0 +1,25 @@ +{% comment %} +Рекурсивный шаблон для отображения узла дерева категорий. +Параметры: nodes - список узлов, level - уровень вложенности +{% endcomment %} +{% for node in nodes %} +
+
+ {% if node.children %} + + {% else %} + + {% endif %} + + {{ node.category.name }} + + {{ node.category.products.count|default:0 }} + +
+ {% if node.children %} +
+ {% include 'products/partials/catalog_tree_node.html' with nodes=node.children level=level|add:1 %} +
+ {% endif %} +
+{% endfor %} diff --git a/myproject/products/urls.py b/myproject/products/urls.py index e7f2d91..d3c05f4 100644 --- a/myproject/products/urls.py +++ b/myproject/products/urls.py @@ -8,7 +8,10 @@ app_name = 'products' urlpatterns = [ # Main unified list for products and kits (default view) path('', views.CombinedProductListView.as_view(), name='products-list'), - + + # Каталог с drag-n-drop + path('catalog/', views.CatalogView.as_view(), name='catalog'), + # Legacy URLs for backward compatibility path('all/', views.CombinedProductListView.as_view(), name='all-products'), path('products/', views.ProductListView.as_view(), name='product-list-legacy'), diff --git a/myproject/products/views/__init__.py b/myproject/products/views/__init__.py index a49dcbe..99ac042 100644 --- a/myproject/products/views/__init__.py +++ b/myproject/products/views/__init__.py @@ -95,6 +95,9 @@ from .configurablekit_views import ( # API представления from .api_views import search_products_and_variants, validate_kit_cost, create_temporary_kit_api, create_tag_api +# Каталог +from .catalog_views import CatalogView + __all__ = [ # Утилиты @@ -174,4 +177,7 @@ __all__ = [ 'validate_kit_cost', 'create_temporary_kit_api', 'create_tag_api', + + # Каталог + 'CatalogView', ] diff --git a/myproject/products/views/catalog_views.py b/myproject/products/views/catalog_views.py new file mode 100644 index 0000000..ded76c9 --- /dev/null +++ b/myproject/products/views/catalog_views.py @@ -0,0 +1,52 @@ +""" +Представление для каталога товаров и комплектов. +""" +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic import TemplateView + +from ..models import Product, ProductKit, ProductCategory + + +class CatalogView(LoginRequiredMixin, TemplateView): + """Каталог с деревом категорий слева и сеткой товаров справа.""" + template_name = 'products/catalog.html' + + def build_category_tree(self, categories, parent=None): + """Рекурсивно строит дерево категорий с товарами.""" + tree = [] + for cat in categories: + if cat.parent_id == (parent.pk if parent else None): + children = self.build_category_tree(categories, cat) + tree.append({ + 'category': cat, + 'children': children, + }) + return tree + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Все активные категории + categories = list(ProductCategory.objects.filter( + is_active=True, is_deleted=False + ).prefetch_related('products', 'kits').order_by('name')) + + # Строим дерево + category_tree = self.build_category_tree(categories, parent=None) + + # Товары и комплекты для правой панели + products = Product.objects.filter(status='active').prefetch_related('photos').order_by('name') + for p in products: + p.item_type = 'product' + p.main_photo = p.photos.order_by('order').first() + + kits = ProductKit.objects.filter(status='active', is_temporary=False).prefetch_related('photos').order_by('name') + for k in kits: + k.item_type = 'kit' + k.main_photo = k.photos.order_by('order').first() + + items = sorted(list(products) + list(kits), key=lambda x: x.name) + + context['category_tree'] = category_tree + context['items'] = items + return context diff --git a/myproject/templates/navbar.html b/myproject/templates/navbar.html index 35eb179..cbef61b 100644 --- a/myproject/templates/navbar.html +++ b/myproject/templates/navbar.html @@ -1,4 +1,4 @@ - + \ No newline at end of file +