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

如何在Django类视图+Crispy Forms场景下实现多文件上传

问题原因

你当前遇到仅1个文件被保存的问题,主要有三个核心错误:

  1. 单字段无法存储多文件:Post模型的file_1是单文件字段,每个Post实例只能存储1个文件对象
  2. 字段名不匹配:循环创建实例时使用的字段名是file,但模型中实际字段名为file_1,上传的文件没有被正确写入
  3. 保存逻辑冲突:循环创建多个Post实例后,又调用super().form_valid(form)再次保存原始表单实例,最终仅保留最后一次保存内容

推荐解决方案(规范一对多存储,适合生产环境)

一个帖子对应多个附件的场景,建议用一对多关联模型存储,方便后续管理单个附件。

1. 调整模型代码(models.py)

from django.db import models
from django.contrib.auth import get_user_model
# 你原来的validate_file_size验证器保留
User = get_user_model()

class Post(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    # 删掉原来的file_1单文件字段,其他原有字段保留

# 新增附件表,关联到Post主表
class PostFile(models.Model):
    post = models.ForeignKey(Post, related_name="attachments", on_delete=models.CASCADE)
    file = models.FileField(
        upload_to='PN_files/%Y/%m/%d/', 
        validators=[validate_file_size], 
        help_text="单文件最大50MB"
    )

2. 自定义表单(新增forms.py)

直接在表单中给控件加multiple属性,不需要额外写JS注入属性

from django import forms
from .models import Post

class PostCreateForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ["title"]
    
    # 新增多文件上传字段,自动生成带multiple属性的控件
    attachments = forms.FileField(
        widget=forms.ClearableFileInput(attrs={"multiple": True}),
        required=False,
        label="上传附件"
    )

3. 调整视图代码(views.py)

from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from .models import Post, PostFile
from .forms import PostCreateForm

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    form_class = PostCreateForm
    # 配置提交成功后的跳转路由,比如跳转到帖子详情页
    success_url = "/your-success-path/"

    def form_valid(self, form):
        # 先保存帖子主表数据
        form.instance.author = self.request.user
        post = form.save()
        
        # 循环存储所有上传的附件
        upload_files = self.request.FILES.getlist("attachments")
        if upload_files:
            for f in upload_files:
                PostFile.objects.create(post=post, file=f)
        
        # 直接跳转,不需要再调用super的form_valid避免重复保存
        return redirect(self.success_url)

4. 调整模板代码

删除原来手动加multiple属性的JS代码,表单正常渲染即可,Crispy会自动生成带multiple属性的上传控件。


快速适配方案(不修改原有模型)

如果你不想改动原有模型结构,可以把多个文件路径以数组形式存储在file_1字段中,需要先把file_1字段改为JSON类型:

# models.py修改file_1字段
file_1 = models.JSONField(blank=True, default=list, verbose_name="附件", help_text="允许上传多个文件,单文件最大50MB")

对应视图修改:

from django.core.files.storage import default_storage
from django.utils import timezone

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'file_1']

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        # 直接给字段控件加multiple属性,不需要JS注入
        form.fields["file_1"].widget.attrs["multiple"] = True
        return form

    def form_valid(self, form):
        form.instance.author = self.request.user
        post = form.save(commit=False)
        file_urls = []
        # 处理所有上传的文件
        for f in self.request.FILES.getlist("file_1"):
            # 手动保存文件到存储目录
            save_path = f"PN_files/{timezone.now().strftime('%Y/%m/%d')}/{f.name}"
            saved_name = default_storage.save(save_path, f)
            file_urls.append(default_storage.url(saved_name))
        # 把所有文件路径存入字段
        post.file_1 = file_urls
        post.save()
        return super().form_valid(form)

内容的提问来源于stack exchange,提问作者MarkoZg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 03:57:03