You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 21:45:38