Django FormSets是否支持对FileFields字段进行预填充?
解决方案
1 修复上下文变量名冲突问题
你当前代码在get方法中构造了带初始值的procedure_attachment_form并传入模板,但get_context_data方法里又将context["attachments"]赋值为空表单集,且模板引用的变量名是procedure_attachment_form,二者不匹配导致初始值完全失效。
修改get_context_data方法:
def get_context_data(self, **kwargs): context = super(UpdateProcedureView, self).get_context_data(**kwargs) # 移除原有重复构造表单集的逻辑,不要覆盖get方法传入的procedure_attachment_form return context
同时将inlineformset_factory的定义移到类视图外部或类变量位置,避免每次请求重复构造:
# 类视图外定义 UpdateProcedureFileFormSet = inlineformset_factory( UpdateProcedure, UpdateProcedureFiles, form=UpdateProcedureFilesForm, can_delete=True, # 开启删除功能,满足审核后删除附件的需求 can_order=True, min_num=0, validate_min=True )
2 解决FileField无法预填充的展示问题
浏览器安全限制禁止给文件上传框预设值,你需要单独渲染已有附件的信息,修改HTML模板:
{% for fileform in procedure_attachment_form.forms %} {{ fileform.id }} <div class="inline {{ procedure_attachment_form.prefix }}"> <!-- 新增:展示已有附件 --> {% if fileform.instance.attachments %} <p>已上传附件:<a href="{{ fileform.instance.attachments.url }}" target="_blank">{{ fileform.instance.attachments.name }}</a></p> <!-- 隐藏字段存储原有附件路径,用于后续复制到新模型 --> <input type="hidden" name="existing_attachments[]" value="{{ fileform.instance.attachments.name }}"> {{ fileform.DELETE }} <!-- 渲染删除勾选框,支持删除已有附件 --> {% endif %} {{ fileform.attachments }} {% if procedure_attachment_form.non_form_errors %} <h3 class="spacer174"> {{ procedure_attachment_form.non_form_errors }} </h3> {% endif %} {% if fileform.attachments.errors %} <h3 class="spacer174"> {{ fileform.attachments.errors }} </h3> {% endif %} {{ fileform.procedure.as_hidden }} </div> {% endfor %}
同时修改表单,移除multiple=True配置,避免和表单集逻辑冲突:
class UpdateProcedureFilesForm(forms.ModelForm): class Meta: model = UpdateProcedureFiles fields = ['attachments', 'DELETE'] # 新增DELETE字段支持删除 widgets = { 'attachments': ClearableFileInput(), # 移除multiple属性 }
3 修复提交时附件处理逻辑
你当前post方法只处理了新上传的文件,新增原有附件的复制逻辑:
def post(self, request, *args, **kwargs): if "cancel" in request.POST: return HttpResponseRedirect(reverse('Procedures:procedure_main_menu')) else: self.object = None form_class = self.get_form_class() form = self.get_form(form_class) procedure_attachment_form = UpdateProcedureFileFormSet(self.request.POST,self.request.FILES) if (form.is_valid() and procedure_attachment_form.is_valid()): procedure_instance = form.save(commit=False) procedure_instance.user = request.user procedure_instance.save() # 保存表单集的新上传文件 procedure_attachment_form.instance = procedure_instance procedure_attachment_form.save() # 处理原有附件复制:从hidden字段取已有附件路径,复制到新模型 existing_attachments = request.POST.getlist('existing_attachments[]') for i, attach_path in enumerate(existing_attachments): # 检查该附件是否被勾选删除,未删除则复制 delete_check = request.POST.get(f'{procedure_attachment_form.prefix}-{i}-DELETE', None) if not delete_check: # 从原有模型取附件对象 old_attach = ProcedureFiles.objects.filter(attachments=attach_path).first() if old_attach: # 直接复制FieldFile到新模型 UpdateProcedureFiles.objects.create( attachments=old_attach.attachments, update_procedure=procedure_instance ) return self.form_valid(form, procedure_attachment_form) else: return self.form_invalid(form, procedure_attachment_form)
4 补充初始值构造逻辑
修改get方法中表单集的初始化参数,传入queryset而非initial,更适配已有数据的渲染:
def get(self, request, *args, **kwargs): self.object = self.get_object() form = self.get_form() dropdown = self.kwargs["pk"] attachments = ProcedureFiles.objects.filter(procedure_id=dropdown) # 传入queryset渲染已有附件,extra控制新增空白输入框的数量 procedure_attachment_form = UpdateProcedureFileFormSet( queryset=attachments, extra=1 # 留一个空白输入框用于上传新附件 ) return self.render_to_response( self.get_context_data( form=form, procedure_attachment_form=procedure_attachment_form, ) )
内容的提问来源于stack exchange,提问作者Steve Smith
相关产品推荐
相关产品推荐

