使用Django Crispy Forms+HTMX无法保存含图片的ModelForm
问题
无法保存表单,提交的payload格式错误。使用HTMX在模态框中渲染表单,表单包含ImageField(需要enctype="multipart/form-data"),但验证不通过,重新渲染后title字段为空并提示必填错误。打印request.POST得到格式混乱的payload。
forms.py
class RecipeForm(forms.ModelForm): title = forms.CharField( label="Titolo", ) method = forms.CharField( widget=forms.Textarea(), label="Procedimento", ) tags = CustomTagsMultipleChoice( queryset=Tag.objects.all(), widget=forms.CheckboxSelectMultiple, label="Tag", ) hero_image = forms.ImageField( label="Immagine", ) class Meta: model = Recipe fields = ["title", "method", "tags", "hero_image"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.attrs = { "hx-post": reverse_lazy("recipes:recipe_create"), "hx-target": "#dialog", } self.fields["tags"].required = False self.fields["method"].required = False self.fields["hero_image"].required = False self.fields["tags"].label = False self.helper.layout = Layout( FloatingField("title"), FloatingField("method", css_class="fl-textarea"), "hero_image", InlineCheckboxes("tags"), Div( Submit("submit", "Salva"), Submit("button", "Cancella", css_class="btn btn-danger", data_bs_dismiss="modal"), css_class="text-end", ), )
views.py
@login_required def recipe_create(request): if request.method == "POST": form = RecipeForm(request.POST,request.FILES) if form.is_valid(): form.save() return HttpResponse(status=204, headers={"HX-Trigger": "recipeSaved"}) form = RecipeForm(request.POST,request.FILES) context = {"recipe_form": form} return render(request, "recipes/partials/_recipe_create.html", context) form = RecipeForm() context = {"recipe_form": form} return render(request, "recipes/partials/_recipe_create.html", context)
_recipe_create.html
{% load crispy_forms_tags %} <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">Aggiungi una ricetta</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button> </div> <div class="modal-body"> {% crispy recipe_form %} </div> </div>
错误的payload
<QueryDict: {'------WebKitFormBoundaryIbg12MlAkITtzU0g\r\nContent-Disposition: form-data; name': ['"submit"\r\n\r\nSalva\r\n------WebKitFormBoundaryIbg12MlAkITtzU0g\r\nContent-Disposition: form-data; name="csrfmiddlewaretoken"\r\n\r\n0DWNbShUV0r8mFSPUNQaSQapC38FQFrMeOtmqFY64H4x3ReOuG7suKqUip7sSpqv\r\n------WebKitFormBoundaryIbg12MlAkITtzU0g\r\nContent-Disposition: form-data; name="title"\r\n\r\ntest\r\n------WebKitFormBoundaryIbg12MlAkITtzU0g\r\nContent-Disposition: form-data; name="method"\r\n\r\n\r\n------WebKitFormBoundaryIbg12MlAkITtzU0g--\r\n']}>
解决方案
核心问题是HTMX默认不会自动处理multipart/form-data编码,导致表单数据未被正确解析。
- 给表单添加HTMX编码属性:在
RecipeForm的__init__方法中,给self.helper.attrs添加hx-encoding和enctype属性,确保HTMX正确处理文件上传的表单编码:
self.helper.attrs = { "hx-post": reverse_lazy("recipes:recipe_create"), "hx-target": "#dialog", "hx-encoding": "multipart/form-data", "enctype": "multipart/form-data" }
- 优化视图重复代码:验证失败后无需重新实例化表单,直接使用验证后的
form对象即可:
@login_required def recipe_create(request): if request.method == "POST": form = RecipeForm(request.POST, request.FILES) if form.is_valid(): form.save() return HttpResponse(status=204, headers={"HX-Trigger": "recipeSaved"}) context = {"recipe_form": form} return render(request, "recipes/partials/_recipe_create.html", context) form = RecipeForm() context = {"recipe_form": form} return render(request, "recipes/partials/_recipe_create.html", context)
添加hx-encoding属性后,HTMX会按正确格式提交表单数据,Django就能正常解析request.POST和request.FILES,表单验证即可通过。
内容的提问来源于stack exchange,提问作者WEBBOgrafico
相关产品推荐
相关产品推荐

