Django点击组件下载文件报错:不允许加载本地资源
相关代码
# Models.py(用于FileField文件上传的自定义类)
class CustomStorage(FileSystemStorage): # 在media中创建新存储 def __init__(self, *args, **kwargs): super().__init__(location=os.path.join(settings.BASE_DIR, 'media','companies'), *args, **kwargs) def submission_directory_path(instance, filename): # 获取公司商业名称和客户ID company_name = slugify(instance.client.company.comercial_name) client_data = f"{instance.client.id}_{slugify(instance.client.name)}_{instance.type_submission.name}" # 构建目录路径 directory_path = os.path.join(company_name, client_data) # 返回文件完整路径 return os.path.join(directory_path, filename)
# Urls.py
urlpatterns = [ path('admin/', admin.site.urls), #ENDPOINTS path('client-details/<uuid:client_id>/', views.get_contact_details, name='client-details'), path('panel/submission/<uuid:submission_id>/change/cancel', views.cancel_submission, name='cancel_submission'), path('panel/submission/<uuid:submission_id>/change/no-apply', views.no_apply_submission, name='no_apply_submission'), # 跳转至账号激活页面 path('<str:name>/<str:uidb64>/<str:token>/', views.account_activation, name='account_activation'), path('panel/submission/<uuid:submission_id>/download/<str:file_path>/', views.download_file, name='download_file'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# View.py
def download_file(request, file_path): print("文件路径:", file_path) file_path = os.path.join(settings.MEDIA_ROOT, file_path.replace("\\", "/")) print("文件完整路径:", file_path) if os.path.exists(file_path): with open(file_path, 'rb') as file: # response = HttpResponse(FileWrapper(file), content_type='application/pdf') response = HttpResponse(FileWrapper(file), content_type=mimetypes.guess_type(file_path)[0]) response['Content-Disposition'] = f'attachment; filename="{os.path.basename(file_path)}"' return response else: print("未找到文件!") return HttpResponse('File not found', status=404)
# forms.py
class DownloadFileWidget(forms.Widget): template_name = 'download_widget.html' # 替换为你的模板实际路径 # def render(self, name, value, attrs=None, renderer=None): # output = super().render(name, value, attrs, renderer) # if value: # file_path = value.url # download_link = f'<a href="{file_path}" download>下载文件</a>' # output += mark_safe(download_link) # return output def render(self, name, value, attrs=None, renderer=None): output = super().render(name, value, attrs, renderer) if value: file_path = value.path # 从'value.url'改为'value.path' download_link = f'<a href="{file_path}" download>下载文件</a>' output += mark_safe(download_link) return output
# Download_widget.html
<!DOCTYPE html> {% if widget.has_file %} <form method="post" action="{% url 'download_file' file_path=widget.value %}" target="_blank"> {% csrf_token %} <button type="submit">下载文件</button> </form> {% endif %}
问题分析与修复方案
核心原因
报错是因为直接使用了本地文件路径(value.path)作为链接,浏览器出于安全限制,不允许页面直接加载file://协议的本地资源,必须通过Django的URL路由间接访问文件。
具体修复步骤
1. 修正表单Widget逻辑(forms.py)
放弃使用value.path,改用value.name(文件相对于MEDIA_ROOT的路径),生成合法的Django下载URL:
class DownloadFileWidget(forms.Widget): template_name = 'download_widget.html' def render(self, name, value, attrs=None, renderer=None): output = super().render(name, value, attrs, renderer) if value: # 获取文件相对MEDIA_ROOT的路径 file_rel_path = value.name # 从实例中获取submission_id(需确保Widget能访问到关联的instance) submission_id = self.instance.id # 生成下载视图的URL download_url = reverse('download_file', kwargs={'submission_id': submission_id, 'file_path': file_rel_path}) download_link = f'<a href="{download_url}" download>下载文件</a>' output += mark_safe(download_link) return output
2. 修正模板文件(Download_widget.html)
模板中不能直接传入widget.value(这是File对象而非字符串路径),需传入widget.value.name,同时补全URL所需的submission_id参数:
{% if widget.value %} <!-- 用GET请求更简洁,无需CSRF令牌 --> <a href="{% url 'download_file' submission_id=widget.instance.id file_path=widget.value.name %}" download> <button type="button">下载文件</button> </a> {% endif %}
3. 优化下载视图(View.py)
简化路径处理逻辑,避免手动替换分隔符:
def download_file(request, submission_id, file_path): full_path = os.path.join(settings.MEDIA_ROOT, file_path) if os.path.exists(full_path): with open(full_path, 'rb') as f: # 自动识别文件类型, fallback到通用二进制类型 content_type = mimetypes.guess_type(full_path)[0] or 'application/octet-stream' response = HttpResponse(f.read(), content_type=content_type) response['Content-Disposition'] = f'attachment; filename="{os.path.basename(full_path)}"' return response return HttpResponse('文件未找到', status=404)
4. 额外注意事项
- 生产环境下不要依赖
static()函数处理媒体文件,需配置Nginx等服务器直接托管MEDIA_ROOT目录。 - 如果不需要额外权限校验,直接用GET请求的
<a>标签比POST表单更简洁,省去CSRF令牌的处理。
内容的提问来源于stack exchange,提问作者user22767461
相关产品推荐
相关产品推荐

