Вложенная структура набора форм Django Неправильно отображает динамические поля

Я работаю над Django вложенным набором форм, где пользователи могут:

  • Добавьте к изделию несколько цветов.
  • Для каждого цвета добавьте несколько размеров динамически, используя JavaScript.
  • Для каждого размера должно быть свое собственное поле size_name, stock, и price_increment.
<время работы/>

Проблема

При отправке формы Django неправильно группирует значения нескольких полей размера в списки вместо того, чтобы рассматривать их как отдельные записи.

Ожидаемые данные Django POST (правильная структура)

sizes-0-0-size_name = "Small"
sizes-0-0-stock = "100"
sizes-0-0-price_increment = "50"

sizes-0-1-size_name = "Medium"
sizes-0-1-stock = "150"
sizes-0-1-price_increment = "75"

Фактические данные публикации в Django (неправильная структура)

sizes-0-0-size_name = ["Small", "Medium"]
sizes-0-0-stock = ["100", "150"]
sizes-0-0-price_increment = ["50", "75"]
  • Вместо отдельных полей для каждого размера в Django значения группируются в один список.
  • Поле sizes-0-TOTAL_FORMS появляется дважды в запросе POST, что может указывать на проблему дублирования JavaScript.
<время работы/>

Отладка данных запроса (request.POST)

<QueryDict: {
    'colors-TOTAL_FORMS': ['1'],
    'sizes-0-TOTAL_FORMS': ['1', '1'],  # This should be a single value, not duplicated
    'sizes-0-0-size_name': ['Small', 'Medium'],
    'sizes-0-0-stock': ['100', '150'],
    'sizes-0-0-price_increment': ['50', '75']
}>

Возможные причины:

  1. Проблема с JavaScript:

    • Динамическим добавлением формы может быть неправильное присвоение имен входным данным, в результате чего Django интерпретирует несколько значений как список.
    • TOTAL_FORMS размеры for могут быть обновлены неправильно, что приведет к дублированию значений.
  2. Проблема с набором форм Django:

    • Возможно, Django неправильно распознает входные данные индивидуального размера из-за неправильной обработки prefix.
<время работы/>

Реализация кода

Формы (forms.py)

class ProductForm(forms.ModelForm):
    class Meta:
        model = VendorProduct
        fields = ['title', 'cagtegory', 'base_price']

class ProductColorForm(forms.ModelForm):
    class Meta:
        model = ProductColor
        fields = ['color_name', 'color_code']

class ProductSizeForm(forms.ModelForm):
    class Meta:
        model = ProductSize
        fields = ['size_name', 'stock', 'price_increment']

ProductColorFormSet = inlineformset_factory(
    VendorProduct, ProductColor, form=ProductColorForm, extra=1, can_delete=True
)
ProductSizeFormSet = inlineformset_factory(
    ProductColor, ProductSize, form=ProductSizeForm, extra=1, can_delete=True
)

Вид (views.py)

@login_required
def add_product(request):
    if request.method == 'POST':
        product_form = ProductForm(request.POST)
        color_formset = ProductColorFormSet(request.POST, prefix='colors')

        if product_form.is_valid() and color_formset.is_valid():
            product = product_form.save()
            for color_index, color_form in enumerate(color_formset):
                if color_form.cleaned_data.get('color_name'):
                    color = color_form.save(commit=False)
                    color.product = product
                    color.save()

                    # **Check if sizes are structured properly**
                    size_formset = ProductSizeFormSet(
                        request.POST, instance=color, prefix=f'sizes-{color_index}'
                    )
                    print(f"Processing sizes for color index {color_index}:")
                    print(request.POST)

                    if size_formset.is_valid():
                        size_formset.save()

            return redirect('vendorpannel:vendor_shop')

    else:
        product_form = ProductForm()
        color_formset = ProductColorFormSet(prefix='colors')
        color_size_formsets = [
            ProductSizeFormSet(instance=color_form.instance, prefix=f'sizes-{index}')
            for index, color_form in enumerate(color_formset.forms)
        ]

    return render(request, 'vendorpannel/add-product.html', {
        'product_form': product_form,
        'color_formset': color_formset,
        'color_size_formsets': color_size_formsets,
    })

JavaScript для динамической обработки форм (add_product.html)

document.addEventListener("DOMContentLoaded", function () {
    let colorIndex = document.querySelectorAll(".color-item").length;
    function addColor() {
        let totalForms = document.querySelector('[name="colors-TOTAL_FORMS"]');
        let newColor = document.querySelector(".color-item").cloneNode(true);
        newColor.querySelectorAll("input").forEach(input => {
            input.name = input.name.replace(/colors-\d+/g, `colors-${colorIndex}`);
            input.value = "";
        });

        let sizeContainer = newColor.querySelector(".sizeContainer");
        sizeContainer.innerHTML = "";

        let sizeTotalForms = document.createElement("input");
        sizeTotalForms.type = "hidden";
        sizeTotalForms.name = `sizes-${colorIndex}-TOTAL_FORMS`;
        sizeTotalForms.value = "0";
        sizeContainer.appendChild(sizeTotalForms);

        document.getElementById("colorContainer").appendChild(newColor);
        totalForms.value = colorIndex + 1;
        colorIndex++;
    }

    document.getElementById("addColorButton")?.addEventListener("click", addColor);
});
<время работы/>

То, что я пробовал:

✅ Убедитесь, что sizes-{colorIndex}-TOTAL_FORMS существует, прежде чем добавлять размеры динамически.
✅ Правильно использовал name.replace() для обновления входных имен.
✅ Проверено использование prefix в формах и наборах форм Django.

<время работы/>

Вопрос:

Как я могу гарантировать, что каждое поле ввода размера получит уникальное имя вместо того, чтобы группировать несколько значений в списки в Django?

<время работы/>

Полный шаблон, который отображает наборы форм

Вам нужно сгруппировать color_size_formsets по color_formset, таким образом:

@login_required
def add_product(request):
    if request.method == 'POST':
        # ...
    else:
        product_form = ProductForm()
        color_formset = ProductColorFormSet(prefix='colors')
        for index, color_form in enumerate(color_formset.forms):
            color_form.size_formset = ProductSizeFormSet(instance=color_form.instance, prefix=f'sizes-{index}')

    return render(request, 'vendorpannel/add-product.html', {
        'product_form': product_form,
        'color_formset': color_formset,
    })

это важно, потому что в противном случае вы каждый раз будете отображать все ColorSizeFormSet в списке за color_form.

Затем шаблон будет содержать:

{% for color_form in color_formset %}

    <div class="dynamic-item color-item">
        {{ color_form.color_name.label_tag }}
        {{ color_form.color_name }}

        {{ color_form.color_code.label_tag }}
        {{ color_form.color_code }}

        <!-- Size Section -->
        <div class="sizeContainer">
            <h4>Sizes</h4>
            {{ color_form.size_formset.management_form }}
            {% for size_form in color_form.size_formset  %}
                <div class="dynamic-item size-item">
                    {{ size_form.size_name.label_tag }}
                    {{ size_form.size_name }}

                    {{ size_form.stock.label_tag }}
                    {{ size_form.stock }}

                    {{ size_form.price_increment.label_tag }}
                    {{ size_form.price_increment }}
                </div>
            {% endfor %}
        </div>

        <button type="button" class="add-size-btn add-btn">Add Size</button>
        <button type="button" class="remove-btn" onclick="removeColor(this)">Remove Color</button>
    </div>
{% endfor %}
Вернуться на верх