如何在Django类视图+Crispy Forms场景下实现多文件上传
问题原因
你当前遇到仅1个文件被保存的问题,主要有三个核心错误:
- 单字段无法存储多文件:
Post模型的file_1是单文件字段,每个Post实例只能存储1个文件对象 - 字段名不匹配:循环创建实例时使用的字段名是
file,但模型中实际字段名为file_1,上传的文件没有被正确写入 - 保存逻辑冲突:循环创建多个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
相关产品推荐
相关产品推荐

