diff --git a/myproject/products/forms.py b/myproject/products/forms.py
index a24dada..787c9bf 100644
--- a/myproject/products/forms.py
+++ b/myproject/products/forms.py
@@ -16,7 +16,7 @@ class ProductForm(forms.ModelForm):
)
tags = forms.ModelMultipleChoiceField(
- queryset=ProductTag.objects.all(),
+ queryset=ProductTag.objects.filter(is_active=True),
widget=forms.CheckboxSelectMultiple,
required=False,
label="Теги"
@@ -82,7 +82,7 @@ class ProductKitForm(forms.ModelForm):
)
tags = forms.ModelMultipleChoiceField(
- queryset=ProductTag.objects.all(),
+ queryset=ProductTag.objects.filter(is_active=True),
widget=forms.CheckboxSelectMultiple,
required=False,
label="Теги"
diff --git a/myproject/products/templates/products/product_detail.html b/myproject/products/templates/products/product_detail.html
index df6947d..0581c07 100644
--- a/myproject/products/templates/products/product_detail.html
+++ b/myproject/products/templates/products/product_detail.html
@@ -154,7 +154,9 @@
{% if product.tags.all %}
{% for tag in product.tags.all %}
- {{ tag.name }}
+ {% if tag.is_active %}
+ {{ tag.name }}
+ {% endif %}
{% endfor %}
{% else %}
-
diff --git a/myproject/products/templates/products/productkit_detail.html b/myproject/products/templates/products/productkit_detail.html
index 1fa135b..f20a86c 100644
--- a/myproject/products/templates/products/productkit_detail.html
+++ b/myproject/products/templates/products/productkit_detail.html
@@ -104,7 +104,9 @@
Теги:
{% for tag in kit.tags.all %}
+ {% if tag.is_active %}
{{ tag.name }}
+ {% endif %}
{% endfor %}
diff --git a/myproject/products/templates/products/tag_list.html b/myproject/products/templates/products/tag_list.html
index 5de779a..1d6f738 100644
--- a/myproject/products/templates/products/tag_list.html
+++ b/myproject/products/templates/products/tag_list.html
@@ -87,11 +87,13 @@
{{ tag.kits_count }}
|
- {% if tag.is_active %}
- Активен
- {% else %}
- Неактивен
- {% endif %}
+
+
+
|
@@ -241,6 +243,45 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
});
+
+// Обработчик переключения статуса тега
+document.querySelectorAll('.tag-status-switch').forEach(toggle => {
+ toggle.addEventListener('click', async function(e) {
+ e.preventDefault(); // Предотвращаем стандартное поведение checkbox
+
+ const tagId = this.dataset.tagId;
+ const toggleSwitch = this;
+ const wasChecked = toggleSwitch.checked; // Сохраняем ТЕКУЩЕЕ состояние
+
+ try {
+ // Отправляем AJAX запрос
+ const apiUrl = `/products/api/tags/${tagId}/toggle/`;
+ const response = await fetch(apiUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': '{{ csrf_token }}'
+ }
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ // Устанавливаем переключатель в НОВОЕ состояние согласно ответу сервера
+ toggleSwitch.checked = data.is_active;
+
+ // Показываем сообщение об успехе
+ showMessage(data.message, 'success');
+ } else {
+ // Переключатель остаётся в исходном состоянии (мы его не меняли)
+ showMessage(data.error || 'Ошибка при обновлении тега', 'danger');
+ }
+ } catch (error) {
+ // При ошибке сети - переключатель остаётся как был
+ showMessage('Ошибка сети: ' + error.message, 'danger');
+ }
+ });
+});
{% endblock %}
diff --git a/myproject/products/urls.py b/myproject/products/urls.py
index daa3b19..c5ebc2c 100644
--- a/myproject/products/urls.py
+++ b/myproject/products/urls.py
@@ -1,5 +1,6 @@
from django.urls import path
from . import views
+from .views import api_views
app_name = 'products'
@@ -37,7 +38,8 @@ 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'),
+ path('api/tags/create/', api_views.create_tag_api, name='api-tag-create'),
+ path('api/tags//toggle/', api_views.toggle_tag_status_api, name='api-tag-toggle'),
# CRUD URLs for ProductVariantGroup (Варианты товаров)
path('variant-groups/', views.ProductVariantGroupListView.as_view(), name='variantgroup-list'),
diff --git a/myproject/products/views/api_views.py b/myproject/products/views/api_views.py
index cb0bf68..677485b 100644
--- a/myproject/products/views/api_views.py
+++ b/myproject/products/views/api_views.py
@@ -98,7 +98,27 @@ def search_products_and_variants(request):
}],
'pagination': {'more': False}
})
- except (Product.DoesNotExist, ProductKit.DoesNotExist, ValueError):
+ elif item_type == 'variant':
+ # Для групп вариантов: получаем по ID с prefetch приоритетов
+ variant = ProductVariantGroup.objects.prefetch_related(
+ 'items__product'
+ ).get(id=numeric_id)
+
+ variant_price = variant.price or 0
+ count = variant.items.count()
+
+ return JsonResponse({
+ 'results': [{
+ 'id': variant.id,
+ 'text': f"{variant.name} ({count} вариантов)",
+ 'price': str(variant_price),
+ 'actual_price': str(variant_price),
+ 'type': 'variant',
+ 'count': count
+ }],
+ 'pagination': {'more': False}
+ })
+ except (Product.DoesNotExist, ProductKit.DoesNotExist, ProductVariantGroup.DoesNotExist, ValueError):
return JsonResponse({'results': [], 'pagination': {'more': False}})
query = request.GET.get('q', '').strip()
@@ -337,15 +357,18 @@ def search_products_and_variants(request):
variants = ProductVariantGroup.objects.filter(
models.Q(name__icontains=query) |
models.Q(description__icontains=query)
- ).prefetch_related('products')[:page_size]
+ ).prefetch_related('items__product')[:page_size]
for variant in variants:
- count = variant.products.filter(is_active=True).count()
+ count = variant.items.count()
+ variant_price = variant.price or 0
variant_results.append({
'id': variant.id,
'text': f"{variant.name} ({count} вариантов)",
'type': 'variant',
- 'count': count
+ 'count': count,
+ 'price': str(variant_price),
+ 'actual_price': str(variant_price)
})
# Формируем финальный результат с группировкой или без
@@ -711,3 +734,59 @@ def create_tag_api(request):
'success': False,
'error': f'Ошибка при создании тега: {str(e)}'
}, status=500)
+
+
+def toggle_tag_status_api(request, pk):
+ """
+ AJAX endpoint для переключения статуса активности тега.
+
+ Принимает POST запрос и переключает is_active на противоположное значение.
+
+ Возвращает JSON:
+ {
+ "success": true,
+ "is_active": true/false,
+ "message": "Тег активирован" / "Тег деактивирован"
+ }
+
+ Или при ошибке:
+ {
+ "success": false,
+ "error": "Описание ошибки"
+ }
+ """
+ if request.method != 'POST':
+ return JsonResponse({
+ 'success': False,
+ 'error': 'Метод не поддерживается'
+ }, status=405)
+
+ try:
+ from ..models import ProductTag
+
+ # Получаем тег
+ tag = ProductTag.objects.get(pk=pk)
+
+ # Переключаем статус
+ tag.is_active = not tag.is_active
+ tag.save()
+
+ # Определяем сообщение
+ message = "Тег активирован" if tag.is_active else "Тег деактивирован"
+
+ return JsonResponse({
+ 'success': True,
+ 'is_active': tag.is_active,
+ 'message': message
+ })
+
+ except ProductTag.DoesNotExist:
+ return JsonResponse({
+ 'success': False,
+ 'error': 'Тег не найден'
+ }, status=404)
+ except Exception as e:
+ return JsonResponse({
+ 'success': False,
+ 'error': f'Ошибка при обновлении тега: {str(e)}'
+ }, status=500)
diff --git a/myproject/products/views/product_views.py b/myproject/products/views/product_views.py
index 5416224..d5a149c 100644
--- a/myproject/products/views/product_views.py
+++ b/myproject/products/views/product_views.py
@@ -63,7 +63,7 @@ class ProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# Данные для фильтров
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', ''),
diff --git a/myproject/products/views/productkit_views.py b/myproject/products/views/productkit_views.py
index 8c867c3..eb89591 100644
--- a/myproject/products/views/productkit_views.py
+++ b/myproject/products/views/productkit_views.py
@@ -8,7 +8,7 @@ from django.urls import reverse_lazy
from django.shortcuts import redirect
from django.db import transaction
-from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto
+from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto, Product, ProductVariantGroup
from ..forms import ProductKitForm, KitItemFormSetCreate, KitItemFormSetUpdate
from .utils import handle_photos
@@ -52,7 +52,7 @@ class ProductKitListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
# Данные для фильтров
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', ''),
@@ -146,10 +146,20 @@ class ProductKitCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVi
if '-variant_group' in key and value:
try:
- variant_group = ProductVariantGroup.objects.get(id=value)
+ variant_group = ProductVariantGroup.objects.prefetch_related(
+ 'items__product'
+ ).get(id=value)
+
+ variant_price = variant_group.price or 0
+ count = variant_group.items.count()
+
selected_variants[key] = {
'id': variant_group.id,
- 'text': variant_group.name
+ 'text': f"{variant_group.name} ({count} вариантов)",
+ 'price': str(variant_price),
+ 'actual_price': str(variant_price),
+ 'type': 'variant',
+ 'count': count
}
except ProductVariantGroup.DoesNotExist:
pass
@@ -248,9 +258,98 @@ class ProductKitUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVi
if self.request.POST:
context['kititem_formset'] = KitItemFormSetUpdate(self.request.POST, instance=self.object, prefix='kititem')
+
+ # При ошибке валидации - подготавливаем данные для Select2
+ selected_products = {}
+ selected_variants = {}
+
+ for key, value in self.request.POST.items():
+ if '-product' in key and value:
+ try:
+ # Очищаем ID от префикса если есть
+ numeric_value = value.split('_')[1] if '_' in value else value
+ product = Product.objects.get(id=numeric_value)
+
+ text = product.name
+ if product.sku:
+ text += f" ({product.sku})"
+
+ actual_price = product.sale_price if product.sale_price else product.price
+ selected_products[key] = {
+ 'id': product.id,
+ 'text': text,
+ 'price': str(product.price) if product.price else None,
+ 'actual_price': str(actual_price) if actual_price else '0'
+ }
+ except Product.DoesNotExist:
+ pass
+
+ if '-variant_group' in key and value:
+ try:
+ variant_group = ProductVariantGroup.objects.prefetch_related(
+ 'items__product'
+ ).get(id=value)
+
+ variant_price = variant_group.price or 0
+ count = variant_group.items.count()
+
+ selected_variants[key] = {
+ 'id': variant_group.id,
+ 'text': f"{variant_group.name} ({count} вариантов)",
+ 'price': str(variant_price),
+ 'actual_price': str(variant_price),
+ 'type': 'variant',
+ 'count': count
+ }
+ except ProductVariantGroup.DoesNotExist:
+ pass
+
+ context['selected_products'] = selected_products
+ context['selected_variants'] = selected_variants
else:
context['kititem_formset'] = KitItemFormSetUpdate(instance=self.object, prefix='kititem')
+ # Подготавливаем данные для предзагрузки в Select2
+ selected_products = {}
+ selected_variants = {}
+
+ for item in self.object.kit_items.all():
+ form_prefix = f"kititem-{item.id}"
+
+ if item.product:
+ product = item.product
+ text = product.name
+ if product.sku:
+ text += f" ({product.sku})"
+
+ actual_price = product.sale_price if product.sale_price else product.price
+ selected_products[f"{form_prefix}-product"] = {
+ 'id': product.id,
+ 'text': text,
+ 'price': str(product.price) if product.price else None,
+ 'actual_price': str(actual_price) if actual_price else '0'
+ }
+
+ if item.variant_group:
+ variant_group = ProductVariantGroup.objects.prefetch_related(
+ 'items__product'
+ ).get(id=item.variant_group.id)
+
+ variant_price = variant_group.price or 0
+ count = variant_group.items.count()
+
+ selected_variants[f"{form_prefix}-variant_group"] = {
+ 'id': variant_group.id,
+ 'text': f"{variant_group.name} ({count} вариантов)",
+ 'price': str(variant_price),
+ 'actual_price': str(variant_price),
+ 'type': 'variant',
+ 'count': count
+ }
+
+ context['selected_products'] = selected_products
+ context['selected_variants'] = selected_variants
+
context['productkit_photos'] = self.object.photos.all().order_by('order', 'created_at')
context['photos_count'] = self.object.photos.count()
diff --git a/myproject/products/views/tag_views.py b/myproject/products/views/tag_views.py
index 0df5401..f433c45 100644
--- a/myproject/products/views/tag_views.py
+++ b/myproject/products/views/tag_views.py
@@ -62,8 +62,15 @@ class ProductTagDetailView(LoginRequiredMixin, DetailView):
tag = self.get_object()
# Получаем товары и комплекты с этим тегом
- context['products'] = tag.products.filter(is_active=True).order_by('name')[:20]
- context['kits'] = tag.kits.filter(is_active=True).order_by('name')[:20]
+ # Если тег неактивный, показываем ВСЕ товары/комплекты (для возможности очистки)
+ # Если тег активный, показываем только активные товары/комплекты
+ if tag.is_active:
+ context['products'] = tag.products.filter(is_active=True).order_by('name')[:20]
+ context['kits'] = tag.kits.filter(is_active=True).order_by('name')[:20]
+ else:
+ context['products'] = tag.products.all().order_by('name')[:20]
+ context['kits'] = tag.kits.all().order_by('name')[:20]
+
context['total_products'] = tag.products.count()
context['total_kits'] = tag.kits.count()
|