Add ProductKit binding to ConfigurableKitProductAttribute values

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>
This commit is contained in:
2025-11-18 21:29:14 +03:00
parent a12f8f990d
commit 3f789785ca
7 changed files with 948 additions and 63 deletions

View File

@@ -0,0 +1,145 @@
#!/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")