如何使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
相关产品推荐
相关产品推荐

