""" CRUD представления для справочника атрибутов товаров (ProductAttribute, ProductAttributeValue). """ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView from django.urls import reverse_lazy from django.db.models import Q, Count from django.db import IntegrityError from django.http import JsonResponse from django.views.decorators.http import require_POST from django.contrib.auth.decorators import login_required import json from ..models import ProductAttribute, ProductAttributeValue from ..forms import ProductAttributeForm, ProductAttributeValueFormSet class ProductAttributeListView(LoginRequiredMixin, ListView): """Список всех атрибутов с поиском""" model = ProductAttribute template_name = 'products/attribute_list.html' context_object_name = 'attributes' paginate_by = 20 def get_queryset(self): queryset = super().get_queryset() # Аннотируем количество значений для каждого атрибута queryset = queryset.annotate( num_values=Count('values', distinct=True) ) # Поиск по названию и slug search_query = self.request.GET.get('search') if search_query: queryset = queryset.filter( Q(name__icontains=search_query) | Q(slug__icontains=search_query) ) return queryset.order_by('position', 'name') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['search_query'] = self.request.GET.get('search', '') return context class ProductAttributeDetailView(LoginRequiredMixin, DetailView): """Детальная информация об атрибуте с его значениями""" model = ProductAttribute template_name = 'products/attribute_detail.html' context_object_name = 'attribute' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) attribute = self.get_object() # Получаем все значения атрибута context['values'] = attribute.values.all().order_by('position', 'value') return context class ProductAttributeCreateView(LoginRequiredMixin, CreateView): """Создание нового атрибута с inline значениями""" model = ProductAttribute form_class = ProductAttributeForm template_name = 'products/attribute_form.html' success_url = reverse_lazy('products:attribute-list') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.POST: context['value_formset'] = ProductAttributeValueFormSet(self.request.POST, instance=self.object) else: context['value_formset'] = ProductAttributeValueFormSet(instance=self.object) return context def form_valid(self, form): context = self.get_context_data() value_formset = context['value_formset'] try: self.object = form.save() if value_formset.is_valid(): value_formset.instance = self.object value_formset.save() else: return self.form_invalid(form) messages.success(self.request, f'Атрибут "{self.object.name}" успешно создан.') return super().form_valid(form) except IntegrityError as e: error_msg = str(e).lower() if 'unique' in error_msg: messages.error( self.request, f'Ошибка: атрибут с таким названием уже существует.' ) else: messages.error( self.request, 'Ошибка при сохранении атрибута. Пожалуйста, проверьте введённые данные.' ) return self.form_invalid(form) class ProductAttributeUpdateView(LoginRequiredMixin, UpdateView): """Редактирование существующего атрибута с inline значениями""" model = ProductAttribute form_class = ProductAttributeForm template_name = 'products/attribute_form.html' success_url = reverse_lazy('products:attribute-list') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.POST: context['value_formset'] = ProductAttributeValueFormSet(self.request.POST, instance=self.object) else: context['value_formset'] = ProductAttributeValueFormSet(instance=self.object) return context def form_valid(self, form): context = self.get_context_data() value_formset = context['value_formset'] try: self.object = form.save() if value_formset.is_valid(): value_formset.save() else: return self.form_invalid(form) messages.success(self.request, f'Атрибут "{self.object.name}" успешно обновлен.') return super().form_valid(form) except IntegrityError as e: error_msg = str(e).lower() if 'unique' in error_msg: messages.error( self.request, f'Ошибка: атрибут с таким названием уже существует.' ) else: messages.error( self.request, 'Ошибка при сохранении атрибута. Пожалуйста, проверьте введённые данные.' ) return self.form_invalid(form) class ProductAttributeDeleteView(LoginRequiredMixin, DeleteView): """Удаление атрибута с подтверждением""" model = ProductAttribute template_name = 'products/attribute_confirm_delete.html' success_url = reverse_lazy('products:attribute-list') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) attribute = self.get_object() # Количество значений context['values_count'] = attribute.values.count() return context def delete(self, request, *args, **kwargs): attribute = self.get_object() attribute_name = attribute.name response = super().delete(request, *args, **kwargs) messages.success(request, f'Атрибут "{attribute_name}" успешно удален.') return response # API endpoints @login_required @require_POST def create_attribute_api(request): """API для быстрого создания атрибута""" try: data = json.loads(request.body) name = data.get('name', '').strip() if not name: return JsonResponse({'success': False, 'error': 'Название обязательно'}) attribute = ProductAttribute.objects.create(name=name) return JsonResponse({ 'success': True, 'id': attribute.pk, 'name': attribute.name, 'slug': attribute.slug }) except IntegrityError: return JsonResponse({'success': False, 'error': 'Атрибут с таким названием уже существует'}) except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) @login_required @require_POST def add_attribute_value_api(request, pk): """API для добавления значения к атрибуту""" try: data = json.loads(request.body) value = data.get('value', '').strip() if not value: return JsonResponse({'success': False, 'error': 'Значение обязательно'}) attribute = ProductAttribute.objects.get(pk=pk) attr_value = ProductAttributeValue.objects.create( attribute=attribute, value=value ) return JsonResponse({ 'success': True, 'id': attr_value.pk, 'value': attr_value.value, 'slug': attr_value.slug }) except ProductAttribute.DoesNotExist: return JsonResponse({'success': False, 'error': 'Атрибут не найден'}) except IntegrityError: return JsonResponse({'success': False, 'error': 'Такое значение уже существует'}) except Exception as e: return JsonResponse({'success': False, 'error': str(e)}) @login_required @require_POST def delete_attribute_value_api(request, pk, value_id): """API для удаления значения атрибута""" try: value = ProductAttributeValue.objects.get(pk=value_id, attribute_id=pk) value.delete() return JsonResponse({'success': True}) except ProductAttributeValue.DoesNotExist: return JsonResponse({'success': False, 'error': 'Значение не найдено'}) except Exception as e: return JsonResponse({'success': False, 'error': str(e)})