Замена простого select на autocomplete с поиском для привязки атрибутов к товарам/комплектам

- Переиспользован модуль select2-product-search.js из orders
- Заменен простой select на Select2 с AJAX поиском через API search_products_and_variants
- Добавлена поддержка привязки как ProductKit, так и Product к значениям атрибутов
- Обновлен метод _save_attributes_from_cards для обработки item_ids и item_types
- Удалены дублирующиеся подключения jQuery и Select2 (используются из base.html)
- Улучшен UX: живой поиск, отображение типа товара (🌹/💐), цены и наличия
This commit is contained in:
2025-12-30 02:59:45 +03:00
parent a3f2185714
commit a95bd56b2b
2 changed files with 144 additions and 85 deletions

View File

@@ -96,11 +96,6 @@ class ConfigurableProductDetailView(LoginRequiredMixin, ManagerOwnerRequiredMixi
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Добавляем доступные комплекты для выбора (активные, не временные)
context['available_kits'] = ProductKit.objects.filter(
status='active',
is_temporary=False
).order_by('name')
return context
@@ -138,12 +133,6 @@ class ConfigurableProductCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
prefix='attributes'
)
# Доступные комплекты для JavaScript (для выбора при добавлении значений атрибутов)
context['available_kits'] = ProductKit.objects.filter(
status='active',
is_temporary=False
).order_by('name')
# Справочник атрибутов для autocomplete
context['product_attributes'] = ProductAttribute.objects.prefetch_related('values').order_by('position', 'name')
@@ -247,10 +236,12 @@ class ConfigurableProductCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
- attributes-X-visible: видимость
- attributes-X-DELETE: помечен ли для удаления
- attributes-X-values: JSON массив значений параметра
- attributes-X-kits: JSON массив ID комплектов для каждого значения
- attributes-X-item_ids: JSON массив ID товаров/комплектов
- attributes-X-item_types: JSON массив типов ('product' или 'kit')
"""
import json
from products.models.kits import ProductKit
from products.models.products import Product
# Сначала удаляем все старые атрибуты
ConfigurableProductAttribute.objects.filter(parent=self.object).delete()
@@ -282,9 +273,10 @@ class ConfigurableProductCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
visible = self.request.POST.get(f'attributes-{idx}-visible') == 'on'
# Получаем значения и их привязанные комплекты
# Получаем значения, ID и типы (kit/product)
values_json = self.request.POST.get(f'attributes-{idx}-values', '[]')
kits_json = self.request.POST.get(f'attributes-{idx}-kits', '[]')
item_ids_json = self.request.POST.get(f'attributes-{idx}-item_ids', '[]')
item_types_json = self.request.POST.get(f'attributes-{idx}-item_types', '[]')
try:
values = json.loads(values_json)
@@ -292,15 +284,21 @@ class ConfigurableProductCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
values = []
try:
kit_ids = json.loads(kits_json)
item_ids = json.loads(item_ids_json)
except (json.JSONDecodeError, TypeError):
kit_ids = []
item_ids = []
try:
item_types = json.loads(item_types_json)
except (json.JSONDecodeError, TypeError):
item_types = []
# Создаём ConfigurableProductAttribute для каждого значения
for value_idx, value in enumerate(values):
if value and value.strip():
# Получаем соответствующий ID комплекта
kit_id = kit_ids[value_idx] if value_idx < len(kit_ids) else None
# Получаем соответствующие ID и тип
item_id = item_ids[value_idx] if value_idx < len(item_ids) else None
item_type = item_types[value_idx] if value_idx < len(item_types) else None
# Приготавливаем параметры создания
create_kwargs = {
@@ -311,13 +309,17 @@ class ConfigurableProductCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
'visible': visible
}
# Добавляем комплект если указан
if kit_id:
# Добавляем комплект или товар если указан
if item_id and item_type:
try:
kit = ProductKit.objects.get(id=kit_id)
create_kwargs['kit'] = kit
except ProductKit.DoesNotExist:
# Комплект не найден - создаём без привязки
if item_type == 'kit':
kit = ProductKit.objects.get(id=item_id)
create_kwargs['kit'] = kit
elif item_type == 'product':
product = Product.objects.get(id=item_id)
create_kwargs['product'] = product
except (ProductKit.DoesNotExist, Product.DoesNotExist):
# Комплект/товар не найден - создаём без привязки
pass
ConfigurableProductAttribute.objects.create(**create_kwargs)
@@ -527,10 +529,12 @@ class ConfigurableProductUpdateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
- attributes-X-visible: видимость
- attributes-X-DELETE: помечен ли для удаления
- attributes-X-values: JSON массив значений параметра
- attributes-X-kits: JSON массив ID комплектов для каждого значения
- attributes-X-item_ids: JSON массив ID товаров/комплектов
- attributes-X-item_types: JSON массив типов ('product' или 'kit')
"""
import json
from products.models.kits import ProductKit
from products.models.products import Product
# Сначала удаляем все старые атрибуты
ConfigurableProductAttribute.objects.filter(parent=self.object).delete()
@@ -562,9 +566,10 @@ class ConfigurableProductUpdateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
visible = self.request.POST.get(f'attributes-{idx}-visible') == 'on'
# Получаем значения и их привязанные комплекты
# Получаем значения, ID и типы (kit/product)
values_json = self.request.POST.get(f'attributes-{idx}-values', '[]')
kits_json = self.request.POST.get(f'attributes-{idx}-kits', '[]')
item_ids_json = self.request.POST.get(f'attributes-{idx}-item_ids', '[]')
item_types_json = self.request.POST.get(f'attributes-{idx}-item_types', '[]')
try:
values = json.loads(values_json)
@@ -572,15 +577,21 @@ class ConfigurableProductUpdateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
values = []
try:
kit_ids = json.loads(kits_json)
item_ids = json.loads(item_ids_json)
except (json.JSONDecodeError, TypeError):
kit_ids = []
item_ids = []
try:
item_types = json.loads(item_types_json)
except (json.JSONDecodeError, TypeError):
item_types = []
# Создаём ConfigurableProductAttribute для каждого значения
for value_idx, value in enumerate(values):
if value and value.strip():
# Получаем соответствующий ID комплекта
kit_id = kit_ids[value_idx] if value_idx < len(kit_ids) else None
# Получаем соответствующие ID и тип
item_id = item_ids[value_idx] if value_idx < len(item_ids) else None
item_type = item_types[value_idx] if value_idx < len(item_types) else None
# Приготавливаем параметры создания
create_kwargs = {
@@ -591,13 +602,17 @@ class ConfigurableProductUpdateView(LoginRequiredMixin, ManagerOwnerRequiredMixi
'visible': visible
}
# Добавляем комплект если указан
if kit_id:
# Добавляем комплект или товар если указан
if item_id and item_type:
try:
kit = ProductKit.objects.get(id=kit_id)
create_kwargs['kit'] = kit
except ProductKit.DoesNotExist:
# Комплект не найден - создаём без привязки
if item_type == 'kit':
kit = ProductKit.objects.get(id=item_id)
create_kwargs['kit'] = kit
elif item_type == 'product':
product = Product.objects.get(id=item_id)
create_kwargs['product'] = product
except (ProductKit.DoesNotExist, Product.DoesNotExist):
# Комплект/товар не найден - создаём без привязки
pass
ConfigurableProductAttribute.objects.create(**create_kwargs)