Apache+Cpanel部署Django网站无法生成可下载PDF求助
解决Cpanel部署Django时PDF生成的
io.UnsupportedOperation: fileno错误 问题背景
在Cpanel的Python Web App(基于Apache)部署的Django数据仪表盘上,点击下载按钮生成含用户提交数据的PDF时出现报错:控制台返回通用错误,webapp的stderr.log中显示io.UnsupportedOperation: fileno。本地开发环境完全正常,仅部署后出现问题。
相关代码如下:
JavaScript请求代码
function downloadPDF(id, date) { const csrftoken = getCookie('csrftoken'); $.ajax({ url: `/generate-pdf/${id}`, method: 'GET', headers: { 'X-CSRFToken': csrftoken, }, mode: 'same-origin', xhrFields: { responseType: 'blob' // 指定响应类型为blob }, success: function(response) { console.log(response) var url = URL.createObjectURL(response); var link = document.createElement('a'); link.href = url; link.download = `${id}-${date}.pdf`; link.click(); URL.revokeObjectURL(url); }, error: function(xhr, status, error) { console.error('Error generating PDF:', error); } }); }
Django视图代码
def generate_pdf(request, id): buffer = io.BytesIO() doc = SimpleDocTemplate(buffer, pagesize=letter) # PDF生成逻辑(从数据库取数写入等) buffer.seek(0) return FileResponse(buffer, as_attachment=True, filename="gen_pdf.pdf")
原因分析
Apache搭配的WSGI环境对FileResponse的要求更严格:它期望传入的文件对象支持fileno()方法(用于获取文件描述符),但io.BytesIO是纯内存中的字节流对象,不具备这个属性,因此触发报错。而本地开发用的Django自带WSGI服务器对内存对象兼容性更好,所以不会出现问题。
解决方案
方案1:改用HttpResponse替代FileResponse
直接用HttpResponse返回字节流,手动设置响应头,绕开FileResponse对文件描述符的要求:
import io from django.http import HttpResponse from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate def generate_pdf(request, id): buffer = io.BytesIO() doc = SimpleDocTemplate(buffer, pagesize=letter) # 你的PDF生成逻辑(添加内容、从数据库取数等) buffer.seek(0) # 构造HttpResponse并设置正确的响应头 response = HttpResponse(buffer, content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="gen_pdf.pdf"' return response
方案2:用临时文件替代内存字节流
如果PDF内容较大,用系统临时文件存储生成的内容,临时文件对象支持fileno()方法:
import io import tempfile from django.http import FileResponse from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate def generate_pdf(request, id): # 创建临时文件,delete=False避免文件被立即删除 with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file: doc = SimpleDocTemplate(temp_file, pagesize=letter) # 你的PDF生成逻辑 temp_file.seek(0) response = FileResponse(temp_file, as_attachment=True, filename="gen_pdf.pdf") # 可选:后续可以通过os.unlink(temp_file.name)手动删除临时文件 return response
方案3:简化前端下载逻辑(避免AJAX处理Blob)
对于GET请求,可以直接通过表单提交的方式触发下载,无需AJAX处理Blob,减少兼容性问题:
function downloadPDF(id, date) { const csrftoken = getCookie('csrftoken'); // 通过表单提交发起GET请求,浏览器自动处理下载 const form = document.createElement('form'); form.method = 'GET'; form.action = `/generate-pdf/${id}`; form.target = '_blank'; // 添加CSRF令牌 const csrfInput = document.createElement('input'); csrfInput.type = 'hidden'; csrfInput.name = 'csrfmiddlewaretoken'; csrfInput.value = csrftoken; form.appendChild(csrfInput); document.body.appendChild(form); form.submit(); document.body.removeChild(form); }
验证方法
修改代码后重新部署到Cpanel,测试下载按钮功能,同时查看stderr.log确认是否还有fileno相关错误。
内容的提问来源于stack exchange,提问作者Heroo
相关产品推荐
相关产品推荐

