Implement card-based interface for ConfigurableKitProduct attributes

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>
This commit is contained in:
2025-11-18 20:54:14 +03:00
parent 48938db04f
commit def795f0ad
9 changed files with 1310 additions and 107 deletions

172
myproject/test_workflow.py Normal file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env python
"""
Workflow test: Create a full configurable product with attributes and variants
"""
import os
import sys
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()
from products.models.kits import (
ConfigurableKitProduct,
ConfigurableKitOption,
ConfigurableKitProductAttribute,
ConfigurableKitOptionAttribute,
ProductKit
)
from django_tenants.utils import tenant_context
from tenants.models import Client
from django.db import transaction
try:
client = Client.objects.get(schema_name='grach')
print(f"Found tenant: {client.name}\n")
except Client.DoesNotExist:
print("Tenant 'grach' not found")
sys.exit(1)
with tenant_context(client):
print("=" * 70)
print("WORKFLOW TEST: Complete ConfigurableKitProduct Creation")
print("=" * 70)
# Step 1: Create ConfigurableKitProduct
print("\n[1] Creating ConfigurableKitProduct...")
with transaction.atomic():
try:
# Delete old test products
ConfigurableKitProduct.objects.filter(name__icontains="workflow").delete()
product = ConfigurableKitProduct.objects.create(
name="Workflow Test Product",
sku="WORKFLOW-TEST-001",
description="Test product for workflow validation"
)
print(f" OK: Created product: {product.name} (ID: {product.id})")
except Exception as e:
print(f" ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Step 2: Create attributes with values
print("\n[2] Creating product attributes...")
try:
# Delete old attributes
ConfigurableKitProductAttribute.objects.filter(parent=product).delete()
attrs_data = [
("Dlina", ["50", "60", "70"]),
("Упаковка", ["BEZ", "V_UPAKOVKE"])
]
created_attrs = {}
for attr_name, values in attrs_data:
print(f" Creating attribute: {attr_name}")
created_attrs[attr_name] = []
for pos, value in enumerate(values):
attr = ConfigurableKitProductAttribute.objects.create(
parent=product,
name=attr_name,
option=value,
position=pos,
visible=True
)
created_attrs[attr_name].append(attr)
print(f" - Created value: {value}")
print(f" OK: Created {len(created_attrs)} attribute(s)")
except Exception as e:
print(f" ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Step 3: Get or create ProductKits
print("\n[3] Getting ProductKits for variants...")
try:
kits = ProductKit.objects.all()[:3]
if kits.count() == 0:
print(" WARNING: No ProductKit found in database")
print(" INFO: Skipping variant creation (need ProductKits in DB)")
print("\n To complete testing:")
print(" 1. Create some ProductKit objects in admin")
print(" 2. Then run this script again")
else:
print(f" OK: Found {kits.count()} ProductKit(s)")
for kit in kits:
print(f" - {kit.name} (SKU: {kit.sku})")
# Step 4: Create variants with attribute values
print("\n[4] Creating ConfigurableKitOption variants...")
try:
# Delete old options
ConfigurableKitOption.objects.filter(parent=product).delete()
variant_configs = [
(kits[0], created_attrs["Dlina"][0], created_attrs["Упаковка"][0], True), # 50, BEZ, default
(kits[1], created_attrs["Dlina"][1], created_attrs["Упаковка"][1], False), # 60, V_UPAKOVKE
(kits[2], created_attrs["Dlina"][2], created_attrs["Упаковка"][0], False), # 70, BEZ
]
for kit, dlina_attr, upakovka_attr, is_default in variant_configs:
option = ConfigurableKitOption.objects.create(
parent=product,
kit=kit,
is_default=is_default
)
print(f" Created variant {option.id} for kit: {kit.name}")
# Create M2M relationships
ConfigurableKitOptionAttribute.objects.create(
option=option,
attribute=dlina_attr
)
ConfigurableKitOptionAttribute.objects.create(
option=option,
attribute=upakovka_attr
)
print(f" - Linked attributes: Dlina={dlina_attr.option}, Upakovka={upakovka_attr.option}")
print(f" OK: Created {len(variant_configs)} variant(s)")
except Exception as e:
print(f" ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Step 5: Verify data retrieval
print("\n[5] Verifying variant data...")
try:
options = ConfigurableKitOption.objects.filter(parent=product)
print(f" Found {options.count()} variant(s)")
for opt in options:
print(f"\n Variant {opt.id}:")
print(f" - Kit: {opt.kit.name}")
print(f" - Default: {opt.is_default}")
# Get attributes through M2M
opt_attrs = opt.attributes_set.all()
print(f" - Attributes ({opt_attrs.count()}):")
for opt_attr in opt_attrs:
print(f" * {opt_attr.attribute.name} = {opt_attr.attribute.option}")
print("\n OK: All data retrieves correctly")
except Exception as e:
print(f" ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
except Exception as e:
print(f" ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
print("\n" + "=" * 70)
print("OK: WORKFLOW TEST COMPLETED SUCCESSFULLY!")
print("=" * 70)