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

如何使Django生成的Excel中文件链接直接触发下载

解决Excel文件链接直接触发下载的问题

问题背景

在Python 3.9 + Django + openpyxl v3.1.2环境下生成的Excel文件中,文件链接点击后会直接在浏览器/对应程序中打开,无法像普通网页下载那样直接触发本地下载。需要调整逻辑,让链接点击后强制触发文件下载。

原代码参考

views.py

def export_applicants_xls(request, pk, applicant_id=None):
    job = get_object_or_404(Job, pk=pk)
    if applicant_id:
        candidate_list = Applicant.objects.filter(job=pk, pk=applicant_id)
    else:
        candidate_list = Applicant.objects.filter(job=pk)

    if not candidate_list:
        messages.error(request, _('No applicants for this job'), 'danger')
        return redirect('employer-job-detail', pk=pk)

    response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
    if applicant_id:
        response['Content-Disposition'] = f'attachment; filename=applicant_{applicant_id}_details.xlsx'
    else:
        response['Content-Disposition'] = f'attachment; filename=all_applicants_#{job.slug}.xlsx'

    wb = Workbook()
    ws = wb.active
    ws.title = 'Applicants'

    # Set header row in bold
    header_font = Font(bold=True)
    columns = [ 'CV File', 'ID card', 'Tawjihi file', 'University File', 'Experience File', 'Master file', 'Profession', 'Injury Insurance', 'Membership File', 'Temporary Membership']
 

  
    for col_num, column in enumerate(columns, 1):
        cell = ws.cell(row=1, column=col_num, value=column)
        cell.font = header_font
        ws.column_dimensions[get_column_letter(col_num)].width = 15  # Adjust column width

    # Populate data rows
    for row_num, applicant in enumerate(candidate_list, 2):
        row_data = [
            applicant.get_file_url('cv_file', request),
            applicant.get_file_url('id_card', request),
            applicant.get_file_url('tawjihi_file', request),
            applicant.get_file_url('uni_file', request),
            applicant.get_file_url('experience_file', request),
            applicant.get_file_url('master_file', request),
            applicant.get_file_url('profession_file', request),
            applicant.get_file_url('injury_insurance', request),
            applicant.get_file_url('membership_file', request),
            applicant.get_file_url('temporary_membership', request),
        ]

       
        for col_num, value in enumerate(row_data, 1):
            cell = ws.cell(row=row_num, column=col_num, value=value)
            if col_num in [34, 35, 36, 37, 38, 39, 40, 41, 42, 43]:
                if value:  # Check if the URL is not None
                    link_1(cell, value, display='Click Here')

    # Save workbook to response
    output = BytesIO()
    wb.save(output)
    response.write(output.getvalue())
    return response


def link_1(cell, link, display=None):
    cell.hyperlink = link
    cell.font = Font(u='single', color=colors.BLUE)
    if display is not None:
        cell.value = display

models.py

def get_file_url(self, field_name, request: HttpRequest):
    file_field = getattr(self, field_name)
    if file_field:
        return request.build_absolute_uri(f"{settings.MEDIA_URL}{str(file_field)}")
    return None

解决方案

直接指向媒体文件的URL由浏览器决定打开/下载行为,要强制下载,需通过Django视图返回带有Content-Disposition: attachment的响应。具体步骤如下:

1. 新增文件下载视图

在views.py中添加专门处理下载的视图,验证文件合法性后强制返回下载响应:

from django.http import FileResponse, Http404
import os
from django.conf import settings

def download_file(request, file_path):
    # 拼接完整文件路径
    full_file_path = os.path.join(settings.MEDIA_ROOT, file_path)
    
    # 校验文件存在且为有效文件
    if not os.path.exists(full_file_path) or not os.path.isfile(full_file_path):
        raise Http404("请求的文件不存在")
    
    # 返回强制下载响应
    return FileResponse(open(full_file_path, 'rb'), as_attachment=True)

2. 配置下载路由

在项目的urls.py中添加下载视图的路由:

from django.urls import path
from . import views

urlpatterns = [
    # 其他现有路由...
    path('download/<path:file_path>/', views.download_file, name='download-file'),
]

3. 修改文件URL生成逻辑

更新models.py中的get_file_url方法,生成指向下载视图的URL,而非直接媒体文件URL:

from django.urls import reverse

def get_file_url(self, field_name, request: HttpRequest):
    file_field = getattr(self, field_name)
    if file_field:
        # 获取文件相对媒体根目录的路径
        relative_file_path = str(file_field)
        # 生成下载视图的完整URL
        return request.build_absolute_uri(reverse('download-file', args=[relative_file_path]))
    return None

4. 修正Excel列号判断问题

原代码中col_num in [34,35,...43]的判断与实际列数不符(仅10个文件列),需修改views.py中的循环逻辑,确保所有文件列都能正确设置链接:

# Populate data rows
for row_num, applicant in enumerate(candidate_list, 2):
    row_data = [
        applicant.get_file_url('cv_file', request),
        applicant.get_file_url('id_card', request),
        applicant.get_file_url('tawjihi_file', request),
        applicant.get_file_url('uni_file', request),
        applicant.get_file_url('experience_file', request),
        applicant.get_file_url('master_file', request),
        applicant.get_file_url('profession_file', request),
        applicant.get_file_url('injury_insurance', request),
        applicant.get_file_url('membership_file', request),
        applicant.get_file_url('temporary_membership', request),
    ]

    for col_num, value in enumerate(row_data, 1):
        # 直接设置显示文本为"Click Here"(如果有URL)
        cell_value = 'Click Here' if value else ''
        cell = ws.cell(row=row_num, column=col_num, value=cell_value)
        if value:
            link_1(cell, value)

原理说明

  • 新增的download_file视图负责校验文件合法性,并通过as_attachment=True强制浏览器触发下载行为。
  • 修改后的get_file_url生成的是下载视图的URL,点击链接时会触发Django后端处理,返回下载响应。
  • 修正列号判断后,所有文件列都会正确生成可下载的链接。

内容的提问来源于stack exchange,提问作者Israa Salameh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 15:42:03