Implementation of kit binding feature for ConfigurableKitProduct variants: - Added ForeignKey field `kit` to ConfigurableKitProductAttribute * References ProductKit with CASCADE delete * Optional field (blank=True, null=True) * Indexed for efficient queries - Created migration 0007_add_kit_to_attribute * Handles existing data (NULL values for all current records) * Properly indexed for performance - Updated template configurablekit_form.html * Injected available ProductKits into JavaScript * Added kit selector dropdown in card interface * Each value now has associated kit selection * JavaScript validates kit selection alongside values - Updated JavaScript in card interface * serializeAttributeValues() now collects kit IDs * Creates parallel JSON arrays: values and kits * Stores in hidden fields: attributes-X-values and attributes-X-kits - Updated views _save_attributes_from_cards() in both Create and Update * Reads kit IDs from POST JSON * Looks up ProductKit objects * Creates ConfigurableKitProductAttribute with FK populated * Gracefully handles missing kits - Fixed _should_delete_form() method * More robust handling of formset deletion_field * Works with all formset types - Updated __str__() method * Handles NULL kit case Example workflow: Dlina: 50 -> Kit A, 60 -> Kit B, 70 -> Kit C Upakovka: BEZ -> Kit A, V_UPAKOVKE -> (no kit) Tested with test_kit_binding.py - all tests passing - Kit creation and retrieval - Attribute creation with kit FK - Mixed kit-bound and unbound attributes - Querying attributes by kit - Reverse queries (get kit for attribute value) Added documentation: KIT_BINDING_IMPLEMENTATION.md 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
Test card-based interface for ConfigurableKitProduct attributes
|
|
"""
|
|
import os
|
|
import sys
|
|
import django
|
|
|
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
|
|
django.setup()
|
|
|
|
from products.models.kits import (
|
|
ConfigurableKitProduct,
|
|
ConfigurableKitProductAttribute,
|
|
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("TEST: Card-Based Attribute Interface")
|
|
print("=" * 70)
|
|
|
|
# Step 1: Create a test product
|
|
print("\n[1] Creating test product...")
|
|
try:
|
|
ConfigurableKitProduct.objects.filter(name__icontains="card-test").delete()
|
|
|
|
product = ConfigurableKitProduct.objects.create(
|
|
name="Card Test Product",
|
|
sku="CARD-TEST-001",
|
|
description="Test card interface"
|
|
)
|
|
print(f" OK: Created product: {product.name}")
|
|
except Exception as e:
|
|
print(f" ERROR: {e}")
|
|
sys.exit(1)
|
|
|
|
# Step 2: Manually create attributes like the interface would
|
|
print("\n[2] Creating attributes (simulating card interface)...")
|
|
try:
|
|
# Parameter 1: Dlina (3 values)
|
|
attr_dlina_50 = ConfigurableKitProductAttribute.objects.create(
|
|
parent=product,
|
|
name="Dlina",
|
|
option="50",
|
|
position=0,
|
|
visible=True
|
|
)
|
|
attr_dlina_60 = ConfigurableKitProductAttribute.objects.create(
|
|
parent=product,
|
|
name="Dlina",
|
|
option="60",
|
|
position=0,
|
|
visible=True
|
|
)
|
|
attr_dlina_70 = ConfigurableKitProductAttribute.objects.create(
|
|
parent=product,
|
|
name="Dlina",
|
|
option="70",
|
|
position=0,
|
|
visible=True
|
|
)
|
|
print(f" OK: Created parameter 'Dlina' with 3 values: 50, 60, 70")
|
|
|
|
# Parameter 2: Upakovka (2 values)
|
|
attr_pack_bez = ConfigurableKitProductAttribute.objects.create(
|
|
parent=product,
|
|
name="Upakovka",
|
|
option="BEZ",
|
|
position=1,
|
|
visible=True
|
|
)
|
|
attr_pack_v = ConfigurableKitProductAttribute.objects.create(
|
|
parent=product,
|
|
name="Upakovka",
|
|
option="V_UPAKOVKE",
|
|
position=1,
|
|
visible=True
|
|
)
|
|
print(f" OK: Created parameter 'Upakovka' with 2 values: BEZ, V_UPAKOVKE")
|
|
except Exception as e:
|
|
print(f" ERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
# Step 3: Verify the structure
|
|
print("\n[3] Verifying attribute structure...")
|
|
try:
|
|
# Get unique parameter names
|
|
params = product.parent_attributes.values_list('name', flat=True).distinct()
|
|
print(f" OK: Found {params.count()} unique parameters:")
|
|
|
|
for param_name in params:
|
|
values = product.parent_attributes.filter(name=param_name).values_list('option', flat=True)
|
|
print(f" - {param_name}: {list(values)}")
|
|
|
|
# Verify counts
|
|
assert product.parent_attributes.count() == 5, "Should have 5 total attributes"
|
|
assert product.parent_attributes.filter(name="Dlina").count() == 3, "Should have 3 Dlina values"
|
|
assert product.parent_attributes.filter(name="Upakovka").count() == 2, "Should have 2 Upakovka values"
|
|
print(f" OK: All assertions passed!")
|
|
|
|
except AssertionError as e:
|
|
print(f" ERROR: {e}")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f" ERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
# Step 4: Test data retrieval
|
|
print("\n[4] Testing data retrieval...")
|
|
try:
|
|
# Get first parameter
|
|
param = product.parent_attributes.first()
|
|
print(f" OK: Retrieved attribute: {param.name} = {param.option}")
|
|
|
|
# Test ordering
|
|
by_position = product.parent_attributes.values('name').distinct('name').order_by('position', 'name')
|
|
print(f" OK: Can order by position and name")
|
|
|
|
except Exception as e:
|
|
print(f" ERROR: {e}")
|
|
sys.exit(1)
|
|
|
|
print("\n" + "=" * 70)
|
|
print("OK: CARD INTERFACE TEST PASSED!")
|
|
print("=" * 70)
|
|
print("\nNotes:")
|
|
print("- The interface is designed to work with this attribute structure")
|
|
print("- Each parameter can have multiple values")
|
|
print("- Position is shared by all values of a parameter")
|
|
print("- This allows clean grouping in the card interface")
|