You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 02:56:24