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
, andprice_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:
-
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.
-
Django Formset Issue:
- Django might not be detecting individual size inputs properly due to incorrect
prefix
handling.
- Django might not be detecting individual size inputs properly due to incorrect
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?