Django Formset некорректно заполняет данные формы
Я пытаюсь создать страницу, на которой к заказу можно сделать возврат, с необходимым количеством элементов возврата. Пустые формы для дополнительных ReturnItems генерируются JavaScript, передающим HTML-строку пустой формы, созданной фабрикой форм. Когда формы правильно заполнены, все работает как ожидалось. Однако, когда я пытаюсь отправить сообщение, когда несколько форм ReturnItems пусты, валидация форм не работает должным образом. Я не понимаю, что, хотя данные request.POST верны, поля 'reason' и 'opened' не заполнены для форм, что приводит к тому, что формы оказываются пустыми и пропускаются, вместо того, чтобы выдать ошибку ValidationError из-за отсутствия названия/кода товара.
Любая помощь или предложения были бы полезны, спасибо.
Пример данных request.POST
<QueryDict: {'csrfmiddlewaretoken': ['token'], 'first_name': ['first'], 'last_name': ['last'], 'email': ['e@email.com'], 'telephone': ['12345'], 'order_id': ['123'], 'order_date': ['2022-10-05'], 'form-TOTAL_FORMS': ['2'], 'form-INITIAL_FORMS': ['0'], 'form-MIN_NUM_FORMS': ['1'], 'form-MAX_NUM_FORMS': ['15'], 'form-0-product_name': ['prod1'], 'form-0-product_code': ['code1'], 'form-0-quantity': ['2'], 'form-0-reason': ['1'], 'form-0-opened': ['False'], 'form-1-product_name': ['prod2'], 'form-1-product_code': ['code2'], 'form-1-quantity': ['3'], 'form-1-reason': ['1'], 'form-1-opened': ['False'], 'comment': ['comment']>
Views.py
def return_view(request):
sitekey = settings.H_CAPTCHA_SITEKEY
ReturnItemFormSet = formset_factory(
ReturnItemForm,
extra=0,
min_num=1,
max_num=15,
validate_min=True,
absolute_max=15,
)
return_form = ReturnForm()
return_item_formset = ReturnItemFormSet()
empty_form = return_item_formset.empty_form
if request.method == "GET":
context = {
"return_form": return_form,
"return_item_formset": return_item_formset,
"sitekey": sitekey,
"empty_form_template": empty_form.as_p(),
}
return render(request, "shop/returns.html", context)
if request.method == "POST":
data = request.POST
res = requests.post(
"https://hcaptcha.com/siteverify",
{
"secret": settings.H_CAPTCHA_SECRET,
"response": data.get("h-captcha-response"),
"sitekey": sitekey,
},
)
if res.ok and res.json()["success"]:
return_form = ReturnForm(data)
return_item_formset = ReturnItemFormSet(request.POST)
if return_form.is_valid() and return_item_formset.is_valid():
create_return(return_form, return_item_formset) #Logic to create instances
return redirect(reverse("shop:returns"))
context = {
"return_form": return_form,
"return_item_formset": return_item_formset,
"sitekey": sitekey,
"empty_form_template": empty_form.as_p(),
}
return render(request, "shop/returns.html", context)
ReturnItem model
class ReturnItem(models.Model):
RETURN_REASON = [
(1, "Dead on Arrival"),
(2, "Faulty (please specify below)"),
(3, "Item Error"),
(4, "Order Error"),
(5, "Other (please specify below)"),
]
PRODUCT_OPENED = [(True, "Yes"), (False, "No")]
return_info = models.ForeignKey(
"Return",
on_delete=models.CASCADE,
related_name="items",
related_query_name="item",
)
product_name = models.CharField(max_length=200, verbose_name="Product Name")
product_code = models.CharField(max_length=100, verbose_name="Product Code")
quantity = models.PositiveSmallIntegerField(verbose_name="Quantity")
reason = models.SmallIntegerField(
choices=RETURN_REASON, default=1, verbose_name="Reason for Return"
)
opened = models.BooleanField(
choices=PRODUCT_OPENED, default=False, verbose_name="Product is Opened"
)
Форма ВозвратИзменения
class ReturnItemForm(StyledModelForm):
class Meta:
model = ReturnItem
fields = ["product_name", "product_code", "quantity", "reason", "opened"]
widgets = {
"reason": forms.RadioSelect(),
"opened": forms.RadioSelect(),
}
def __init__(self, *args, **kwargs):
super(ReturnItemForm, self).__init__(*args, **kwargs)
for visible_field in self.visible_fields():
if visible_field.name == "reason" or visible_field.name == "opened":
visible_field.field.widget.attrs["class"] = ""
def clean(self):
cleaned_data = super(ReturnItemForm, self).clean()
if not (
cleaned_data.get("product_name")
or cleaned_data.get("product_code")
or cleaned_data.get("quantity")
):
raise ValidationError("All fields must filled for the product.")
JS для добавления новых форм
//Dynamic form generation
const totalFormsCount = document.querySelector("[name=form-TOTAL_FORMS]");
const addFormBtn = document.getElementById("btn-add-product");
addFormBtn.addEventListener("click", (e) => {
e.preventDefault();
totalFormsCount.setAttribute("value", parseInt(totalFormsCount.value) + 1);
const newReturnItemForm = new DOMParser().parseFromString(
formTemplate,
"text/html"
);
//Replace all '__prefix__' of form template with the correct form number for correct tracking.
let formTemplateAsString = newReturnItemForm.body.innerHTML.replaceAll(
"__prefix__",
(parseInt(totalFormsCount.value) - 1).toString()
);
addFormBtn.insertAdjacentHTML(
"beforebegin",
`<div class="return-item-header">Product ${totalFormsCount.value}</div>`
);
addFormBtn.insertAdjacentHTML("beforebegin", formTemplateAsString);
});