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

@@ -405,9 +405,13 @@ class ConfigurableKitProduct(BaseProductEntity):
class ConfigurableKitProductAttribute(models.Model):
"""
Атрибут родительского вариативного товара.
Определяет схему атрибутов для экспорта на WooCommerce и подобные площадки.
Например: name="Цвет", option="Красный" или name="Размер", option="M".
Атрибут родительского вариативного товара с привязкой к ProductKit.
Каждое значение атрибута связано с конкретным ProductKit.
Например:
- Длина: 50 → ProductKit (A)
- Длина: 60 → ProductKit (B)
- Длина: 70 → ProductKit (C)
"""
parent = models.ForeignKey(
ConfigurableKitProduct,
@@ -425,6 +429,15 @@ class ConfigurableKitProductAttribute(models.Model):
verbose_name="Значение опции",
help_text="Например: Красный, M, 60см"
)
kit = models.ForeignKey(
ProductKit,
on_delete=models.CASCADE,
related_name='as_attribute_value_in',
verbose_name="Комплект для этого значения",
help_text="Какой ProductKit связан с этим значением атрибута",
blank=True,
null=True
)
position = models.PositiveIntegerField(
default=0,
verbose_name="Порядок отображения",
@@ -440,14 +453,16 @@ class ConfigurableKitProductAttribute(models.Model):
verbose_name = "Атрибут вариативного товара"
verbose_name_plural = "Атрибуты вариативных товаров"
ordering = ['parent', 'position', 'name', 'option']
unique_together = [['parent', 'name', 'option']]
unique_together = [['parent', 'name', 'option', 'kit']]
indexes = [
models.Index(fields=['parent', 'name']),
models.Index(fields=['parent', 'position']),
models.Index(fields=['kit']),
]
def __str__(self):
return f"{self.parent.name} - {self.name}: {self.option}"
kit_str = self.kit.name if self.kit else "no kit"
return f"{self.parent.name} - {self.name}: {self.option} ({kit_str})"
class ConfigurableKitOption(models.Model):