Реализован полный CRUD для тегов товаров
Упрощена модель ProductTag: - Удалены поля soft delete (is_deleted, deleted_at, deleted_by) - Добавлено поле is_active для управления статусом - Упрощены менеджеры и методы модели Создан CRUD функционал: - ProductTagForm: форма с автогенерацией slug - Views: список, создание, просмотр, редактирование, удаление - URL маршруты: /products/tags/* - Шаблоны: list, form, detail, confirm_delete Особенности: - Поиск по названию и slug - Фильтрация по статусу активности - Статистика использования тегов в товарах/комплектах - Пагинация (20 на страницу) - Предупреждение при удалении с отображением связанных объектов - Добавлена ссылка "Теги" в навигацию 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,15 @@ from .variant_group_views import (
|
||||
product_variant_group_item_move,
|
||||
)
|
||||
|
||||
# CRUD представления для ProductTag
|
||||
from .tag_views import (
|
||||
ProductTagListView,
|
||||
ProductTagCreateView,
|
||||
ProductTagDetailView,
|
||||
ProductTagUpdateView,
|
||||
ProductTagDeleteView,
|
||||
)
|
||||
|
||||
# API представления
|
||||
from .api_views import search_products_and_variants, validate_kit_cost, create_temporary_kit_api
|
||||
|
||||
@@ -129,6 +138,13 @@ __all__ = [
|
||||
'ProductVariantGroupDeleteView',
|
||||
'product_variant_group_item_move',
|
||||
|
||||
# ProductTag CRUD
|
||||
'ProductTagListView',
|
||||
'ProductTagCreateView',
|
||||
'ProductTagDetailView',
|
||||
'ProductTagUpdateView',
|
||||
'ProductTagDeleteView',
|
||||
|
||||
# API
|
||||
'search_products_and_variants',
|
||||
'validate_kit_cost',
|
||||
|
||||
120
myproject/products/views/tag_views.py
Normal file
120
myproject/products/views/tag_views.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
CRUD представления для тегов товаров (ProductTag).
|
||||
"""
|
||||
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 ..models import ProductTag
|
||||
from ..forms import ProductTagForm
|
||||
|
||||
|
||||
class ProductTagListView(LoginRequiredMixin, ListView):
|
||||
"""Список всех тегов с поиском и фильтрацией"""
|
||||
model = ProductTag
|
||||
template_name = 'products/tag_list.html'
|
||||
context_object_name = 'tags'
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Аннотируем количество товаров и комплектов для каждого тега
|
||||
queryset = queryset.annotate(
|
||||
products_count=Count('products', distinct=True),
|
||||
kits_count=Count('kits', 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)
|
||||
)
|
||||
|
||||
# Фильтр по статусу активности
|
||||
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('name')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['search_query'] = self.request.GET.get('search', '')
|
||||
context['is_active_filter'] = self.request.GET.get('is_active', '')
|
||||
return context
|
||||
|
||||
|
||||
class ProductTagDetailView(LoginRequiredMixin, DetailView):
|
||||
"""Детальная информация о теге с привязанными товарами и комплектами"""
|
||||
model = ProductTag
|
||||
template_name = 'products/tag_detail.html'
|
||||
context_object_name = 'tag'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
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]
|
||||
context['total_products'] = tag.products.count()
|
||||
context['total_kits'] = tag.kits.count()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class ProductTagCreateView(LoginRequiredMixin, CreateView):
|
||||
"""Создание нового тега"""
|
||||
model = ProductTag
|
||||
form_class = ProductTagForm
|
||||
template_name = 'products/tag_form.html'
|
||||
success_url = reverse_lazy('products:tag-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
messages.success(self.request, f'Тег "{self.object.name}" успешно создан.')
|
||||
return response
|
||||
|
||||
|
||||
class ProductTagUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""Редактирование существующего тега"""
|
||||
model = ProductTag
|
||||
form_class = ProductTagForm
|
||||
template_name = 'products/tag_form.html'
|
||||
success_url = reverse_lazy('products:tag-list')
|
||||
|
||||
def form_valid(self, form):
|
||||
response = super().form_valid(form)
|
||||
messages.success(self.request, f'Тег "{self.object.name}" успешно обновлен.')
|
||||
return response
|
||||
|
||||
|
||||
class ProductTagDeleteView(LoginRequiredMixin, DeleteView):
|
||||
"""Удаление тега с подтверждением"""
|
||||
model = ProductTag
|
||||
template_name = 'products/tag_confirm_delete.html'
|
||||
success_url = reverse_lazy('products:tag-list')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
tag = self.get_object()
|
||||
|
||||
# Передаем информацию о связанных объектах для предупреждения
|
||||
context['products_count'] = tag.products.count()
|
||||
context['kits_count'] = tag.kits.count()
|
||||
|
||||
return context
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
tag = self.get_object()
|
||||
tag_name = tag.name
|
||||
response = super().delete(request, *args, **kwargs)
|
||||
messages.success(request, f'Тег "{tag_name}" успешно удален.')
|
||||
return response
|
||||
Reference in New Issue
Block a user