Files
octopus/myproject/products/views/attribute_views.py

275 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)})
@login_required
def get_attributes_list_api(request):
"""
API для получения списка всех атрибутов с их значениями.
Используется для autocomplete в форме создания вариативного товара.
"""
attributes = ProductAttribute.objects.prefetch_related('values').order_by('position', 'name')
data = []
for attr in attributes:
data.append({
'id': attr.pk,
'name': attr.name,
'slug': attr.slug,
'values': [
{
'id': val.pk,
'value': val.value,
'slug': val.slug
}
for val in attr.values.all().order_by('position', 'value')
]
})
return JsonResponse({'success': True, 'attributes': data})