From 1daee88cbd503fdf0d0a807f19b5b4330443a1f2 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Tue, 11 Nov 2025 23:26:40 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B1=D1=8B=D1=81=D1=82=D1=80=D0=BE=D0=B5=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=D0=B2=20=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=86=D0=B5=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API функционал: - Создан endpoint /api/tags/create/ для AJAX создания тегов - Валидация: пустое имя, длина, уникальность (регистронезависимо) - Автоматическая генерация slug через модель - Возврат JSON с данными созданного тега UI функционал на странице списка тегов: - Панель быстрого создания с крупным полем ввода - Автофокус на поле при загрузке страницы - Создание тега по нажатию Enter или клику на кнопку - Индикатор загрузки (спиннер) во время создания - Блокировка поля/кнопки во время запроса - Автоматическая перезагрузка страницы после создания - Возврат фокуса в поле при ошибке - Красивые alert-сообщения об успехе/ошибке - CSRF защита Workflow: ввёл название → Enter → тег создан → фокус снова в поле → можно вводить следующий 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../products/templates/products/tag_list.html | 99 +++++++++++++++++++ myproject/products/urls.py | 1 + myproject/products/views/__init__.py | 3 +- myproject/products/views/api_views.py | 91 +++++++++++++++++ 4 files changed, 193 insertions(+), 1 deletion(-) diff --git a/myproject/products/templates/products/tag_list.html b/myproject/products/templates/products/tag_list.html index c69f259..5de779a 100644 --- a/myproject/products/templates/products/tag_list.html +++ b/myproject/products/templates/products/tag_list.html @@ -11,6 +11,24 @@ + +
+
+
+ Быстрое создание тега +
+
+ + +
+
+
+
+
@@ -144,4 +162,85 @@
+ + + {% endblock %} diff --git a/myproject/products/urls.py b/myproject/products/urls.py index cace000..daa3b19 100644 --- a/myproject/products/urls.py +++ b/myproject/products/urls.py @@ -37,6 +37,7 @@ urlpatterns = [ # API endpoints path('api/search-products-variants/', views.search_products_and_variants, name='api-search-products-variants'), path('api/kits/temporary/create/', views.create_temporary_kit_api, name='api-temporary-kit-create'), + path('api/tags/create/', views.create_tag_api, name='api-tag-create'), # CRUD URLs for ProductVariantGroup (Варианты товаров) path('variant-groups/', views.ProductVariantGroupListView.as_view(), name='variantgroup-list'), diff --git a/myproject/products/views/__init__.py b/myproject/products/views/__init__.py index 5499667..cecae97 100644 --- a/myproject/products/views/__init__.py +++ b/myproject/products/views/__init__.py @@ -80,7 +80,7 @@ from .tag_views import ( ) # API представления -from .api_views import search_products_and_variants, validate_kit_cost, create_temporary_kit_api +from .api_views import search_products_and_variants, validate_kit_cost, create_temporary_kit_api, create_tag_api __all__ = [ @@ -149,4 +149,5 @@ __all__ = [ 'search_products_and_variants', 'validate_kit_cost', 'create_temporary_kit_api', + 'create_tag_api', ] diff --git a/myproject/products/views/api_views.py b/myproject/products/views/api_views.py index dc41b25..cb0bf68 100644 --- a/myproject/products/views/api_views.py +++ b/myproject/products/views/api_views.py @@ -620,3 +620,94 @@ def create_temporary_kit_api(request): 'success': False, 'error': f'Ошибка при создании комплекта: {str(e)}' }, status=500) + + +def create_tag_api(request): + """ + AJAX endpoint для быстрого создания тега из списка тегов. + + Принимает JSON: + { + "name": "Новый тег" + } + + Возвращает JSON: + { + "success": true, + "tag": { + "id": 1, + "name": "Новый тег", + "slug": "novyj-teg", + "is_active": true, + "products_count": 0, + "kits_count": 0 + } + } + + Или при ошибке: + { + "success": false, + "error": "Описание ошибки" + } + """ + if request.method != 'POST': + return JsonResponse({ + 'success': False, + 'error': 'Метод не поддерживается' + }, status=405) + + try: + import json + from ..models import ProductTag + + data = json.loads(request.body) + name = data.get('name', '').strip() + + # Валидация + if not name: + return JsonResponse({ + 'success': False, + 'error': 'Название тега не может быть пустым' + }, status=400) + + if len(name) > 100: + return JsonResponse({ + 'success': False, + 'error': 'Название тега слишком длинное (максимум 100 символов)' + }, status=400) + + # Проверка уникальности (регистронезависимо) + if ProductTag.objects.filter(name__iexact=name).exists(): + return JsonResponse({ + 'success': False, + 'error': f'Тег "{name}" уже существует' + }, status=400) + + # Создание тега (slug будет сгенерирован автоматически в модели) + tag = ProductTag.objects.create( + name=name, + is_active=True + ) + + return JsonResponse({ + 'success': True, + 'tag': { + 'id': tag.id, + 'name': tag.name, + 'slug': tag.slug, + 'is_active': tag.is_active, + 'products_count': 0, + 'kits_count': 0 + } + }) + + except json.JSONDecodeError: + return JsonResponse({ + 'success': False, + 'error': 'Некорректный JSON' + }, status=400) + except Exception as e: + return JsonResponse({ + 'success': False, + 'error': f'Ошибка при создании тега: {str(e)}' + }, status=500)