Django中如何从文件输入元素提取文件名?
问题描述
我想在Django中实现一个表单,其中一个输入项是文件名列表。不需要访问或上传文件本身,只需要便捷获取文件名列表,再和其他表单数据一起提交。
我尝试用模板里的多文件输入元素,通过htmx提取文件名,但不管怎么操作,都只能得到字符串%5Bobject%20File%5D(解码后是[object File]),拿不到实际文件名。
以下是简化后的代码:
urls.py
from django.urls import path from .views import * urlpatterns = [ path("home_1b/", home_1b, name="home_1b"), path("", home, name="home"), ]
views.py
from django.shortcuts import render def home(request): return render(request,'home.html') def home_1b(request): print('*********************') print(request) print(request.GET) print(request.GET['fi_file_names']) print(request.FILES) print('*********************') return render(request,'home_1b.html')
home.html
<!-- templates/home.html --> {% extends "base.html" %} {% block head %} {% load static %} <script src="{% static '/htmx/htmx.min.js' %}"></script> <style> .column {float:left; width:50%;} .row:after{content:""; display:table; clear:both;} label {font-family:Verdana; font-size:small;} input[type="file"] {width:350px; padding:1px 3px 1px 3px; font-family:Verdana; font-size:small; border-style:solid; border-width:1px; background-color:lightgray;} input[type="file"]::file-selector-button {display:none;} </style> {% endblock head %} {% block content %} <h2>File name entry</h2> <div id="home_r1c1" class="row"> <h3>Enter the file name(s) to be cited in the report</h3> <div id="home_1a_slot" hx-get="{% url 'home_1b' %}" hx-trigger="change" hx-encoding="multipart/form-data" hx-target="#home_1b_slot" hx-include="#fi_file_names"> <label for="fi_file_names" style="border:1px solid; border-radius:3px; padding:1px 5px 1px 5px; background-color:lightgray;">...</label> <input type='file' multiple id="fi_file_names" name="fi_file_names"></input> </div> <h3>Review the files specified</h3> <div id="home_1b_slot"> </div> </div> {% endblock content %}
运行输出
输入单个文件时:
********************* <WSGIRequest: GET '/home_1b/?fi_file_names=%5Bobject%20File%5D'> <QueryDict: {'fi_file_names': ['[object File]']}> [object File] <MultiValueDict: {}> ********************* [04/Oct/2023 15:03:50] "GET /home_1b/?fi_file_names=%5Bobject%20File%5D HTTP/1.1" 200 56
输入多个文件时:
********************* <WSGIRequest: GET '/home_1b/?fi_file_names=%5Bobject%20File%5D&fi_file_names=%5Bobject%20File%5D&fi_file_names=%5Bobject%20File%5D'> <QueryDict: {'fi_file_names': ['[object File]', '[object File]', '[object File]']}> [object File] <MultiValueDict: {}> ********************* [04/Oct/2023 15:05:26] "GET /home_1b/?fi_file_names=%5Bobject%20File%5D&fi_file_names=%5Bobject%20File%5D&fi_file_names=%5Bobject%20File%5D HTTP/1.1" 200 56
原因分析
你用的是hx-get请求,GET请求会把表单数据拼在URL里,但File对象无法直接转成URL参数,浏览器只能把它转成字符串[object File],所以后端拿到的就是这个无效值。另外,即使设置了hx-encoding="multipart/form-data",GET请求也不支持这种编码方式,只有POST请求才会正确处理multipart数据。
解决方案
方法一:前端用JS提取文件名,再发送请求
不需要上传文件的话,可以直接在前端通过JavaScript获取选中文件的文件名,然后把这些文件名作为普通参数发送给后端。
修改home.html,添加JS处理逻辑:
<!-- templates/home.html --> {% extends "base.html" %} {% block head %} {% load static %} <script src="{% static '/htmx/htmx.min.js' %}"></script> <style> .column {float:left; width:50%;} .row:after{content:""; display:table; clear:both;} label {font-family:Verdana; font-size:small;} input[type="file"] {width:350px; padding:1px 3px 1px 3px; font-family:Verdana; font-size:small; border-style:solid; border-width:1px; background-color:lightgray;} input[type="file"]::file-selector-button {display:none;} </style> <script> document.getElementById('fi_file_names').addEventListener('change', function(e) { // 获取选中的文件名列表 const fileNames = Array.from(this.files).map(file => file.name); // 用htmx发送POST请求,把文件名作为参数传递 htmx.ajax('POST', "{% url 'home_1b' %}", { target: '#home_1b_slot', params: { 'fi_file_names': fileNames } }); }); </script> {% endblock head %} {% block content %} <h2>File name entry</h2> <div id="home_r1c1" class="row"> <h3>Enter the file name(s) to be cited in the report</h3> <div id="home_1a_slot"> <label for="fi_file_names" style="border:1px solid; border-radius:3px; padding:1px 5px 1px 5px; background-color:lightgray;">...</label> <input type='file' multiple id="fi_file_names" name="fi_file_names"></input> </div> <h3>Review the files specified</h3> <div id="home_1b_slot"> </div> </div> {% endblock content %}
修改views.py,从POST请求中获取文件名:
def home_1b(request): print('*********************') print(request) # 获取文件名列表,request.POST.getlist会拿到所有同名参数 file_names = request.POST.getlist('fi_file_names') print(file_names) print('*********************') # 可以把文件名传给模板渲染 return render(request,'home_1b.html', {'file_names': file_names})
home_1b.html可以这样写来展示文件名:
<ul> {% for name in file_names %} <li>{{ name }}</li> {% endfor %} </ul>
方法二:改用POST请求,让Django处理File对象
如果坚持用htmx的自动表单提交,可以把请求改成POST,这样Django能正确解析multipart/form-data,拿到File对象后提取文件名。
修改home.html的htmx属性:
<div id="home_1a_slot" hx-post="{% url 'home_1b' %}" <!-- 把get改成post --> hx-trigger="change" hx-encoding="multipart/form-data" hx-target="#home_1b_slot" hx-include="#fi_file_names"> <label for="fi_file_names" style="border:1px solid; border-radius:3px; padding:1px 5px 1px 5px; background-color:lightgray;">...</label> <input type='file' multiple id="fi_file_names" name="fi_file_names"></input> </div>
修改views.py,从request.FILES中获取文件对象,提取文件名:
def home_1b(request): print('*********************') print(request) file_names = [] # 从FILES中获取所有上传的文件对象 if 'fi_file_names' in request.FILES: for file in request.FILES.getlist('fi_file_names'): file_names.append(file.name) print(file_names) print('*********************') return render(request,'home_1b.html', {'file_names': file_names})
同样,home_1b.html可以用循环展示文件名。
内容的提问来源于stack exchange,提问作者JDM
相关产品推荐
相关产品推荐

