Files
octopus/myproject/products/views/productkit_views.py
Andrey Smakotin 9d5ce11951 refactor: Двухэтапное создание комплектов товаров
Реализован современный двухэтапный подход к созданию комплектов:

**Шаг 1: Создание базовой информации** (productkit_create.html)
- Упрощённая форма только с базовой информацией
- Название, описание, категории, теги
- Фотографии с предпросмотром
- Настройки ценообразования
- Чистый современный UI с breadcrumbs
- Прогрессивное раскрытие (collapsible секции)

**Шаг 2: Добавление товаров** (productkit_edit.html)
- Автоматический redirect после создания
- Работа с товарами через существующий функционал
- Улучшенный заголовок с указанием шага

**Изменения в коде:**
- ProductKitCreateView: упрощён, убраны formsets
- ProductKitUpdateView: использует новый шаблон
- productkit_form.html → productkit_edit.html
- Новый шаблон productkit_create.html

**Преимущества:**
 Простая и надёжная логика сохранения
 Нет проблем с disabled полями в formsets
 Понятный UX с чёткими шагами
 Современный дизайн с иконками Bootstrap
 Предпросмотр фото перед загрузкой

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 16:05:13 +03:00

225 lines
9.2 KiB
Python
Raw 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 представления для комплектов товаров (ProductKit).
"""
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
from django.urls import reverse_lazy
from django.shortcuts import redirect
from django.db import transaction
from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto
from ..forms import ProductKitForm, KitItemFormSetCreate, KitItemFormSetUpdate
from .utils import handle_photos
class ProductKitListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = ProductKit
template_name = 'products/productkit_list.html'
context_object_name = 'kits'
permission_required = 'products.view_productkit'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.prefetch_related('categories', 'photos', 'kit_items', 'tags')
# Поиск по названию
search_query = self.request.GET.get('search')
if search_query:
queryset = queryset.filter(name__icontains=search_query)
# Фильтр по категории
category_id = self.request.GET.get('category')
if category_id:
queryset = queryset.filter(categories__id=category_id)
# Фильтр по статусу
is_active = self.request.GET.get('is_active')
if is_active == '1':
queryset = queryset.filter(is_active=True)
elif is_active == '0':
queryset = queryset.filter(is_active=False)
return queryset.order_by('-created_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Данные для фильтров
context['filters'] = {
'categories': ProductCategory.objects.filter(is_active=True),
'tags': ProductTag.objects.all(),
'current': {
'search': self.request.GET.get('search', ''),
'category': self.request.GET.get('category', ''),
'is_active': self.request.GET.get('is_active', ''),
'tags': self.request.GET.getlist('tags'),
}
}
# Кнопки действий
action_buttons = []
if self.request.user.has_perm('products.add_productkit'):
action_buttons.append({
'url': reverse_lazy('products:productkit-create'),
'text': 'Создать комплект',
'class': 'btn-primary',
'icon': 'plus-circle'
})
action_buttons.append({
'url': reverse_lazy('products:product-list'),
'text': 'К товарам',
'class': 'btn-outline-primary',
'icon': 'box'
})
context['action_buttons'] = action_buttons
return context
class ProductKitCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
"""
View для создания нового комплекта (только базовая информация).
После создания redirect на страницу редактирования для добавления товаров.
"""
model = ProductKit
form_class = ProductKitForm
template_name = 'products/productkit_create.html'
permission_required = 'products.add_productkit'
def form_valid(self, form):
try:
with transaction.atomic():
# Сохраняем основную форму
self.object = form.save()
# Обработка фотографий
handle_photos(self.request, self.object, ProductKitPhoto, 'kit')
messages.success(
self.request,
f'Комплект "{self.object.name}" создан! Теперь добавьте товары в комплект.'
)
# Всегда redirect на страницу редактирования для добавления товаров
return redirect('products:productkit-update', pk=self.object.pk)
except Exception as e:
messages.error(self.request, f'Ошибка при сохранении: {str(e)}')
return self.form_invalid(form)
def get_success_url(self):
# Этот метод не используется, т.к. мы делаем redirect в form_valid
return reverse_lazy('products:productkit-update', kwargs={'pk': self.object.pk})
class ProductKitUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
"""
View для редактирования существующего комплекта и добавления товаров.
"""
model = ProductKit
form_class = ProductKitForm
template_name = 'products/productkit_edit.html'
permission_required = 'products.change_productkit'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['kititem_formset'] = KitItemFormSetUpdate(self.request.POST, instance=self.object)
else:
context['kititem_formset'] = KitItemFormSetUpdate(instance=self.object)
context['productkit_photos'] = self.object.photos.all().order_by('order', 'created_at')
context['photos_count'] = self.object.photos.count()
return context
def form_valid(self, form):
# Получаем формсет из POST
kititem_formset = KitItemFormSetUpdate(self.request.POST, instance=self.object)
# Проверяем валидность формсета
if kititem_formset.is_valid():
try:
with transaction.atomic():
# Сохраняем основную форму
self.object = form.save()
# Сохраняем компоненты
kititem_formset.instance = self.object
kititem_formset.save()
# Обработка фотографий
handle_photos(self.request, self.object, ProductKitPhoto, 'kit')
messages.success(self.request, f'Комплект "{self.object.name}" успешно обновлен!')
# Проверяем, какую кнопку нажали
if self.request.POST.get('action') == 'continue':
return redirect('products:productkit-update', pk=self.object.pk)
else:
return redirect('products:productkit-list')
except Exception as e:
messages.error(self.request, f'Ошибка при сохранении: {str(e)}')
return self.form_invalid(form)
else:
# Если формсет невалиден, показываем форму с ошибками
messages.error(self.request, 'Пожалуйста, исправьте ошибки в компонентах комплекта.')
return self.form_invalid(form)
def form_invalid(self, form):
# Получаем формсет для отображения ошибок
context = self.get_context_data(form=form)
return self.render_to_response(context)
def get_success_url(self):
return reverse_lazy('products:productkit-list')
class ProductKitDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
"""
View для просмотра деталей комплекта.
Показывает все компоненты, цены, фотографии.
"""
model = ProductKit
template_name = 'products/productkit_detail.html'
context_object_name = 'kit'
permission_required = 'products.view_productkit'
def get_queryset(self):
# Prefetch для оптимизации запросов
return super().get_queryset().prefetch_related(
'photos',
'kit_items__product',
'kit_items__variant_group',
'tags'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Добавляем фотографии комплекта в контекст
context['productkit_photos'] = self.object.photos.all().order_by('order', 'created_at')
context['photos_count'] = self.object.photos.count()
# Добавляем компоненты
context['kit_items'] = self.object.kit_items.all().select_related('product', 'variant_group')
return context
class ProductKitDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
"""
View для удаления комплекта.
"""
model = ProductKit
template_name = 'products/productkit_confirm_delete.html'
context_object_name = 'kit'
permission_required = 'products.delete_productkit'
def get_success_url(self):
messages.success(self.request, f'Комплект "{self.object.name}" успешно удален!')
return reverse_lazy('products:productkit-list')