This commit introduces a new user-friendly interface for managing product attributes:
1. **Form Changes** (products/forms.py):
- Removed 'option' field from ConfigurableKitOptionForm (values now inline)
- Updated ConfigurableKitProductAttributeFormSetCreate to only include name, position, visible
- Updated BaseConfigurableKitProductAttributeFormSet validation for new structure
2. **Template Updates** (products/templates/products/configurablekit_form.html):
- Replaced row-based attribute interface with card-based design
- Each card contains:
- Parameter name field
- Position field
- Visibility toggle
- Inline value inputs with add/remove buttons
- "Add parameter" button creates new cards
- "Add value" button adds inline value inputs
3. **JavaScript Enhancements**:
- addValueField(): Creates new value input with delete button
- initAddValueBtn(): Initializes add value button for each card
- addParameterBtn: Dynamically generates new parameter cards
- serializeAttributeValues(): Converts inline values to JSON for POST submission
- Form submission intercept to serialize data before sending
4. **View Updates** (products/views/configurablekit_views.py):
- Both Create and Update views now have _save_attributes_from_cards() method
- Reads attributes-X-values JSON from POST data
- Creates ConfigurableKitProductAttribute for each parameter+value combination
- Handles parameter deletion and visibility toggling
**Key Features**:
✓ One-time parameter name entry with multiple inline values
✓ Add/remove values without reloading page
✓ Add/remove entire parameters with one click
✓ No database changes required
✓ Better UX: card layout more intuitive than rows
✓ Proper JSON serialization for value transmission
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
178 lines
7.9 KiB
Python
178 lines
7.9 KiB
Python
#!/usr/bin/env python
|
||
"""
|
||
Тестовый скрипт для проверки что JSONField работает корректно
|
||
в модели ConfigurableKitOption (с поддержкой тенанта).
|
||
"""
|
||
import os
|
||
import django
|
||
|
||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
||
django.setup()
|
||
|
||
from products.models.kits import ConfigurableKitProduct, ConfigurableKitOption, ProductKit
|
||
from django_tenants.utils import tenant_context
|
||
from tenants.models import Client
|
||
|
||
# Переходим в нужную схему (тенант)
|
||
try:
|
||
client = Client.objects.get(schema_name='grach')
|
||
print(f"✅ Найден тенант: {client.name} (schema: {client.schema_name})\n")
|
||
except Client.DoesNotExist:
|
||
print("❌ Тенант 'grach' не найден")
|
||
print("📝 Доступные тенанты:")
|
||
for c in Client.objects.all():
|
||
print(f" - {c.name} ({c.schema_name})")
|
||
exit(1)
|
||
|
||
# Весь тест в контексте тенанта
|
||
with tenant_context(client):
|
||
print("=" * 70)
|
||
print("ТЕСТ: JSONField в ConfigurableKitOption")
|
||
print("=" * 70)
|
||
|
||
# Проверка 1: Создание вариативного товара
|
||
print("\n1️⃣ Проверка создания ConfigurableKitProduct...")
|
||
try:
|
||
configurable = ConfigurableKitProduct.objects.filter(name__icontains="тест").first()
|
||
if configurable:
|
||
print(f" ✅ Найден существующий товар: {configurable.name}")
|
||
else:
|
||
configurable = ConfigurableKitProduct.objects.create(
|
||
name="Тестовый букет JSON",
|
||
sku="TEST-BUCKET-JSON",
|
||
description="Тестовый товар для проверки JSON атрибутов"
|
||
)
|
||
print(f" ✅ Создан новый товар: {configurable.name}")
|
||
except Exception as e:
|
||
print(f" ❌ Ошибка: {e}")
|
||
exit(1)
|
||
|
||
# Проверка 2: Создание вариантов с JSON атрибутами
|
||
print("\n2️⃣ Проверка создания ConfigurableKitOption с JSON атрибутами...")
|
||
try:
|
||
# Получаем первый комплект или создаём тестовый
|
||
kit = ProductKit.objects.filter(name__icontains="тест").first()
|
||
if not kit:
|
||
kit = ProductKit.objects.first()
|
||
if not kit:
|
||
print(" ⚠️ В базе нет ProductKit, пропускаем этот тест")
|
||
kit = None
|
||
|
||
if kit:
|
||
print(f" ℹ️ Используем существующий комплект: {kit.name}")
|
||
|
||
# Проверяем есть ли уже вариант для этого комплекта
|
||
option = ConfigurableKitOption.objects.filter(
|
||
parent=configurable,
|
||
kit=kit
|
||
).first()
|
||
|
||
if option:
|
||
print(f" ℹ️ Вариант уже существует, обновляю атрибуты...")
|
||
# Обновляем существующий
|
||
option.attributes = {"length": "60", "color": "red"}
|
||
option.save()
|
||
print(f" ✅ Обновлены атрибуты: {option.attributes}")
|
||
else:
|
||
# Создаём новый вариант с JSON атрибутами
|
||
option = ConfigurableKitOption.objects.create(
|
||
parent=configurable,
|
||
kit=kit,
|
||
attributes={"length": "60", "color": "red"},
|
||
is_default=True
|
||
)
|
||
print(f" ✅ Создан вариант с JSON атрибутами:")
|
||
print(f" - Parent: {option.parent.name}")
|
||
print(f" - Kit: {option.kit.name}")
|
||
print(f" - Attributes (JSON): {option.attributes}")
|
||
print(f" - Type: {type(option.attributes)}")
|
||
except Exception as e:
|
||
print(f" ❌ Ошибка: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
exit(1)
|
||
|
||
# Проверка 3: Получение и работа с JSON атрибутами
|
||
print("\n3️⃣ Проверка получения JSON атрибутов из БД...")
|
||
try:
|
||
options = ConfigurableKitOption.objects.filter(parent=configurable)
|
||
print(f" ℹ️ Найдено {options.count()} вариант(ов)")
|
||
|
||
for idx, opt in enumerate(options, 1):
|
||
print(f"\n Вариант {idx}:")
|
||
print(f" - ID: {opt.id}")
|
||
print(f" - SKU комплекта: {opt.kit.sku}")
|
||
print(f" - Атрибуты (JSON): {opt.attributes}")
|
||
print(f" - Тип данных: {type(opt.attributes)}")
|
||
|
||
# Проверяем доступ к ключам JSON
|
||
if opt.attributes:
|
||
if isinstance(opt.attributes, dict):
|
||
print(f" - Доступ к ключам JSON:")
|
||
for key, value in opt.attributes.items():
|
||
print(f" • {key}: {value}")
|
||
print(f" ✅ JSON работает корректно!")
|
||
else:
|
||
print(f" ❌ Атрибуты не являются dict!")
|
||
except Exception as e:
|
||
print(f" ❌ Ошибка: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
exit(1)
|
||
|
||
# Проверка 4: Фильтрация по JSON атрибутам (PostgreSQL)
|
||
print("\n4️⃣ Проверка фильтрации по JSON атрибутам...")
|
||
try:
|
||
# Попытка использовать JSON фильтрацию (работает в PostgreSQL)
|
||
# Для SQLite это может не работать
|
||
filtered = ConfigurableKitOption.objects.filter(
|
||
parent=configurable,
|
||
attributes__length="60"
|
||
)
|
||
print(f" ℹ️ Попытка фильтрации по attributes__length='60'")
|
||
print(f" ℹ️ Найдено результатов: {filtered.count()}")
|
||
|
||
if filtered.count() > 0:
|
||
print(f" ✅ JSON фильтрация работает!")
|
||
else:
|
||
print(f" ℹ️ JSON фильтрация может не поддерживаться в текущей БД")
|
||
except Exception as e:
|
||
print(f" ℹ️ JSON фильтрация не поддерживается: {type(e).__name__}")
|
||
|
||
# Проверка 5: Сложные JSON структуры
|
||
print("\n5️⃣ Проверка сохранения сложных JSON структур...")
|
||
try:
|
||
complex_attrs = {
|
||
"length": "70",
|
||
"color": "white",
|
||
"quantity": 15,
|
||
"stems": ["rose1", "rose2", "rose3"],
|
||
"metadata": {
|
||
"fresh": True,
|
||
"days_available": 7
|
||
}
|
||
}
|
||
|
||
# Обновляем атрибуты сложной структурой
|
||
if options.exists():
|
||
opt = options.first()
|
||
opt.attributes = complex_attrs
|
||
opt.save()
|
||
|
||
# Проверяем что сохранилось правильно
|
||
opt_reloaded = ConfigurableKitOption.objects.get(pk=opt.pk)
|
||
print(f" ✅ Сохранены сложные JSON атрибуты:")
|
||
print(f" {opt_reloaded.attributes}")
|
||
|
||
# Проверяем вложенность
|
||
if opt_reloaded.attributes.get("metadata", {}).get("fresh"):
|
||
print(f" ✅ Доступ к вложенным полям JSON работает!")
|
||
except Exception as e:
|
||
print(f" ❌ Ошибка: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
print("\n" + "=" * 70)
|
||
print("✅ ВСЕ ТЕСТЫ ПРОЙДЕНЫ! JSONField работает корректно!")
|
||
print("=" * 70)
|