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:
172
myproject/test_workflow.py
Normal file
172
myproject/test_workflow.py
Normal 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)
|
||||
Reference in New Issue
Block a user