Django项目中如何传递request至Celery任务参数?PDF生成邮件咨询
问题解答:Django中传递Request给Celery任务 + wkhtmltopdf生成PDF异步发邮件
一、为什么不能直接传Request给Celery?
先给你说清楚核心原因:Django的request对象是没法直接序列化的,Celery任务要求参数必须是可被JSON序列化的类型,但request里包含了大量不可序列化的属性(比如WSGI连接对象、请求上下文状态等),直接传肯定会报错。所以正确的思路是提取request里的关键信息,把这些可序列化的信息传给Celery任务,而非整个request对象。
具体实现方式
举个实际场景的例子:如果你需要在任务里用到当前用户、语言偏好或者请求里的特定参数,可以这么做:
- 在视图里提取必要信息,触发任务:
# 你的视图代码中 def get(self, request, **kwargs): # 提取关键信息 user_id = request.user.id if request.user.is_authenticated else None language = request.LANGUAGE_CODE challan_id = kwargs['pk'] # 调用Celery任务,传提取后的信息 generate_challan_pdf_email.delay(challan_id, user_id, language) # 快速返回响应,不用等任务执行完 return HttpResponse("Challan邮件正在后台发送,请注意查收")
- 在Celery任务里,用这些信息重构你需要的上下文:
from celery import shared_task from django.contrib.auth.models import User from django.utils.translation import activate @shared_task def generate_challan_pdf_email(challan_id, user_id, language): # 恢复语言环境(如果有国际化需求) activate(language) # 通过user_id获取用户对象(如果任务里需要用到) user = User.objects.get(id=user_id) if user_id else None # 后续处理你的PDF生成和邮件逻辑...
如果确实需要用到session数据,可以传递request.session.session_key,然后在任务里通过SessionStore获取session内容,但要注意session的有效期问题,避免任务执行时session已经过期。
二、结合wkhtmltopdf的异步PDF生成+邮件发送优化
从你给出的视图代码来看,当前是同步处理PDF生成和邮件发送,这会让用户等待很久。我们把这部分耗时逻辑移到Celery任务里,让视图快速返回,后台异步处理。
步骤1:修改视图,只负责触发任务
class ChallanEmail(AtomicMixin, View, LoginRequiredMixin): template = "europarts/challan/email_template.html" def get(self, request, **kwargs): challan_id = kwargs['pk'] # 提取request里的必要信息 user_id = request.user.id language = request.LANGUAGE_CODE # 触发异步任务 generate_challan_pdf_email.delay(challan_id, user_id, language) # 返回友好提示,或者重定向到列表页 return HttpResponse("邮件正在后台处理,稍后会发送到收件箱")
步骤2:编写Celery任务处理核心逻辑
from celery import shared_task from django.contrib.auth.models import User from django.template.loader import render_to_string from django.core.mail import EmailMessage from django.utils.translation import activate import tempfile import pdfkit import os @shared_task(bind=True, retry_backoff=3) def generate_challan_pdf_email(self, challan_id, user_id, language): try: # 激活对应语言环境 activate(language) # 获取用户对象(如果任务中需要用到) user = User.objects.get(id=user_id) # 查询Challan相关数据 challan = Challan.objects.get(pk=challan_id) ref_no = challan.ref_no date = challan.created recipient = challan.recipient address = challan.recipient_address challan_rows = ChallanRow.objects.filter(challan=challan) # 构建模板上下文 context = { "ref_no": ref_no, "date": date, "recipient": recipient, "address": address, "challan_rows": challan_rows, "user": user } # 渲染HTML模板 html_content = render_to_string("europarts/challan/email_template.html", context) # 用wkhtmltopdf生成PDF(用临时文件存储) with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as temp_pdf: # 注意:要指定wkhtmltopdf的实际路径,避免worker找不到 config = pdfkit.configuration(wkhtmltopdf='/usr/bin/wkhtmltopdf') pdfkit.from_string(html_content, temp_pdf.name, configuration=config) # 准备并发送邮件 email = EmailMessage( subject=f"Challan 单据 #{ref_no}", body="您好,附件是您的Challan单据,请查收。", from_email="your-service@example.com", to=[recipient], ) email.attach_file(temp_pdf.name, mimetype='application/pdf') email.send() # 清理临时文件 os.unlink(temp_pdf.name) except Challan.DoesNotExist: # 如果Challan被删除,标记任务失败,不重试 self.retry(max_retries=0) except Exception as e: # 其他异常重试3次,间隔3秒 self.retry(exc=e, max_retries=3)
关键注意点
- 确保Celery worker能加载Django配置:启动worker时要设置环境变量
export DJANGO_SETTINGS_MODULE=your_project.settings。 - wkhtmltopdf必须在worker所在服务器上安装好,并且在代码中指定正确的路径(比如上面的
/usr/bin/wkhtmltopdf,根据你的实际安装路径调整)。 - 任务中处理数据库对象时,最好重新查询最新数据,避免视图传递过来的对象已经被修改。
- 配置了重试机制,确保临时网络问题导致的发送失败能自动重试。
内容的提问来源于stack exchange,提问作者MiniGunnR
相关产品推荐
相关产品推荐

