Django Formset Nested Structure Not Posting Correctly for Dynamic Fields

I’m working on a Django nested formset where users can:

  • Add multiple colors to a product.
  • For each color, add multiple sizes dynamically using JavaScript.
  • Each size should have its own size_name, stock, and price_increment field.

Issue

When submitting the form, Django is incorrectly grouping multiple size field values into lists instead of treating them as separate entries.

Expected Django POST Data (Correct Structure)

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"

Actual Django POST Data (Incorrect Structure)

sizes-0-0-size_name = ["Small", "Medium"]
sizes-0-0-stock = ["100", "150"]
sizes-0-0-price_increment = ["50", "75"]
  • Instead of separate fields for each size, Django is grouping values into a single list.
  • The sizes-0-TOTAL_FORMS field is appearing twice in the POST request, which might indicate a JavaScript duplication issue.

Debugging the Request Data (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']
}>

Potential Causes:

  1. JavaScript Issue:

    • Dynamic form addition might be incorrectly naming inputs, causing Django to interpret multiple values as a list.
    • TOTAL_FORMS for sizes might not be updated properly, leading to duplicate values.
  2. Django Formset Issue:

    • Django might not be detecting individual size inputs properly due to incorrect prefix handling.

Code Implementation

Forms (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
)

View (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 for Dynamic Form Handling (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);
});

What I’ve Tried:

✅ Ensured sizes-{colorIndex}-TOTAL_FORMS exists before adding sizes dynamically.
✅ Used name.replace() correctly to update input names.
✅ Verified prefix usage in Django forms and formsets.


Question:

How can I ensure that each size input field gets a unique name instead of Django grouping multiple values into lists?