【问题标题】:Django Drama: (Drag-n-Drop File Uploader in Multi-part Form) + (Bind File to Form / Model & Save to Postgres)Django Drama: (Drag-n-Drop File Upload in Multipart Form) + (Bind File to Form / Model & Save to Postgres)
【发布时间】:2024-04-20 08:30:02
【问题描述】:

这里隐藏了一些相关的小问题,但它们确实指向一个大而棘手的最佳实践问题。这是一个很难实现的功能,因为它应该同时做一些棘手的事情......

  • 拖放式多文件上传器(通过 Javascript)
  • 多页表单(第一页:上传文件并将文件与现有文档模型关联; 第二页:更新和保存文件/文档对象和元数据到数据库)

...而且我还没有在任何地方找到预先存在的代码示例或实现。 (根据一个人的方法,它可以扫除桌面或自动回答所有相关/嵌入/后续问题。)底线,这篇文章的目的是回答这个问题:What's the最优雅的方法,最大限度地减少干预问题/问题?

我在 Django 中使用这种拖放式 JQuery File Uploader 实现来上传文件...

https://github.com/miki725/Django-jQuery-File-Uploader-Integration-demo

我上面链接到的解决方案当然是将文件保存在文件系统上,但是在每个上传会话中分批,通过为每批文件创建一个目录,然后为每个文件分配一个 UUID那些目录。文件系统上每个唯一命名的目录都包含在该特定上传会话期间上传的文件。这意味着任何类型的数据库存储方法都必须首先梳理并迭代此解决方案为每个上传会话创建的文件系统目录中的所有文件。

注意:上面链接的 JQuery 解决方案不使用 app 目录中的表单(在 forms.py 中)。表单被硬编码到模板中,这已经有点令人失望了......因为现在我还必须找到一种很好的方法将每批中的上述每个文件绑定到一个表单。

认为最简单的——尽管可能是性能最差的解决方案——是为两种形式创建两个视图...在第一页的视图中将每个文件保存到数据库中,然后在第二页更新数据库。这是我目前正在前进的方向:

在模板中...

...uploader javascripts in header...

<form action="{% url my_upload_handler %}" method="POST" enctype="multipart/form-data">
    <input type="file" name="files[]" multiple
</form>

IN VIEWS.PY...

def my_upload_handler_0r_form_part_one(request):

# POST(在上传处理程序中;由上传操作触发的请求)

    if request.method == 'POST':
        if not ("f" in request.GET.keys()):

          ...validators and exception handling...
          ...response_data, which is a dict...

            uid = request.POST[u"uid"]

            file = request.FILES[u'files[]']
            filename = os.path.join(temp_path, str(uuid.uuid4()) + file.name)
            destination = open(filename, "wb+")
            for chunk in file.chunks():
                destination.write(chunk)
            destination.close()

            response_data = simplejson.dumps([response_data])
            response_type = "application/json"
            # return the data to the JQuery uploader plugin...
            return HttpResponse(response_data, mimetype=response_type)

# GET(在同一个上传处理程序中)

    else:    
            return render_to_response('my_first_page_template.html',
                              {                     <---NO 'form':form HERE
                              'uid': uuid.uuid4(),
                              },
                              context_instance = RequestContext(request))


def form_part_two(request):

    #here I need to retrieve and update stuff uploaded on first page

    return render_to_response('my_second_page_template.html', 
    {}, 
    context_instance = RequestContext(request))

第一页的这个视图利用了 JQuery 上传器,它非常适合每个会话的多文件上传,并且可以完成它应该做的事情。然而,正如上面所暗示的,作为上传处理程序的视图只是需要两页表单的第一页。在第二页,最终用户随后需要检索每个上传的文件,将附加数据附加到他们刚刚在第一页上传的文件中,然后重新保存到数据库中。

我尝试通过各种解决方案将这项工作作为两部分表单工作,包括表单向导和/或基于通用类的视图...以下示例主要通过会话实现数据持久性。这些解决方案很快就会变得相当棘手。

总之,我需要...

  • 以唯一标识的批次上传多个文件(通过拖放)
  • 对每批上传的文件进行梳理和迭代
  • 将批处理中的每个文件绑定到一个表单并将其与现有文档模型相关联
  • 一次将所有这些文件提交/保存到数据库中
  • 在可能的新表单的下一页/模板中检索每个文件
  • 更新每个文件的元数据
  • 一次将所有这些文件重新提交/保存到数据库中

所以...通过涉及以下相关问题,您可以看到上述所有因素如何增加了简单文件上传的复杂性,并增加了提供该功能的复杂性:

forms.py:如何最好地将每个文件绑定到表单

models.py:如何将每个文件与预先存在的文档模型相关联

views.py 如何在第一页按照Postgres中已有的文档模型保存每个文件;更新并保存第二页中的每个文档

...再说一次,我想在没有表单向导和基于类的视图的情况下完成所有这些操作。 (尤其是对于这个用例,CBV 让我有点难以理解。)换句话说:我正在寻找可能导致最防弹且易于阅读/理解的解决方案的建议。如果它导致对数据库的多次点击,那对我来说很好。 (如果将文件保存到数据库似乎是反最佳做法,请参阅其他帖子:Storing file content in DB

我是否可以为两个表单创建一个单独的视图,并将标准上传表单子类化,就像这样...

在 forms.py...

class FileUploadForm(forms.Form):

    files = forms.FileField(widget=forms.ClearableFileInput(attrs={'name':'files[]', 'multiple':'multiple'}))
    #how to iterate over files in list or batch of files here...?
        file = forms.FileField()

    file = forms.FileField()

    def clean_file(self):
        data = self.cleaned_data["file"]

        # read, parse, and create `data_dict` from file...
        # subclass pre-existing UploadModelForm      
        **form = UploadModelForm(data_dict)**

        if form.is_valid():
            self.instance = form.save(commit=False) 
        else:
            raise forms.ValidationError
        return data

...然后用类似...的东西重构上面的早期上传处理程序

在 views.py 中,用以下内容替换当前的上传处理程序...

    def view_for_form_one(request):  
        ...
        # the aforementioned upload handler logic, plus...
        ...
        form = FileUploadForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
        else:
            # display errors
            pass
        ...

    def view_for_form_two(request):

        # update and commit all data here

...?

【问题讨论】:

    标签: django file-upload jquery-file-upload django-formwizard


    【解决方案1】:

    一般来说,对于这种类型的问题,我喜欢创建带有一个&lt;form&gt; 的单个页面,但用户使用 javascript 浏览多个部分。

    使用 javascript 将表单分解为多部分、向导式的表单系列要容易得多,尤其是如果它产生的数据本质上是动态的。

    如果您绝对必须将其拆分为多个页面,我建议您将应用设置为能够在每个步骤结束时将数据保存到数据库中。

    您可以通过将用户在第 2 步中添加的元数据设置为可空字段,甚至将元数据移动到单独的模型来做到这一点。

    【讨论】: