如何在 Django 项目中将请求传递给 Celery 任务参数?

Posted

技术标签:

【中文标题】如何在 Django 项目中将请求传递给 Celery 任务参数?【英文标题】:How to pass request to Celery task parameter in Django project? 【发布时间】:2018-10-07 00:08:12 【问题描述】:

我正在使用wkhtmltopdf 和 Django 生成 PDF 文件并将其通过电子邮件发送给某人。这是我的看法:

class ChallanEmail(AtomicMixin, View, LoginRequiredMixin):
    template = "europarts/challan/email_template.html"

    def get(self, request, **kwargs):
        challan = Challan.objects.get(pk=kwargs['pk'])
        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,
        

        response = PDFTemplateResponse(
            request=request,
            template=self.template,
            filename='challan_email.pdf',
            context=context,
            show_content_in_browser=True,
            cmd_options='margin-top': 10,
                         'zoom': 1,
                         'viewport-size': '1366 x 513',
                         'javascript-delay': 1000,
                         'no-stop-slow-scripts': True,
        )

        file_path = os.path.join(settings.BASE_DIR, settings.MEDIA_ROOT, 'challan_email.pdf')
        with open(file_path, 'wb') as f:
            f.write(response.rendered_content)

        subject = 'From Design Ace Limited'
        body = self.request.GET.get('email_body', '')
        from_email = 'Sorower Hossain <sorower@europartsbd.com>'
        to = [''.format(self.request.GET.get('to_address'))]
        attachment = os.path.join(settings.MEDIA_ROOT, 'challan_email.pdf')

        send_email(subject, body, from_email, to, attachment)

        return HttpResponseRedirect(reverse('europarts:challan_details', args=(kwargs['pk'],)))

正如您在最后看到的那样,我正在使用celery 发送带有任务(send_email)的电子邮件。但是,最耗时的过程是 PDF 创建过程。这是执行此操作的代码部分:

response = PDFTemplateResponse(
    request=request,
    template=self.template,
    filename='challan_email.pdf',
    context=context,
    show_content_in_browser=True,
    cmd_options='margin-top': 10,
                 'zoom': 1,
                 'viewport-size': '1366 x 513',
                 'javascript-delay': 1000,
                 'no-stop-slow-scripts': True,
    )

我面临的问题是,每当我尝试将所有参数传输到 celery 任务时,它都会显示错误,因为 request 不可序列化。如何将请求转移到我的 celery 任务并节省 PDF 生成时间?这个任务几乎要花三秒钟时间。

【问题讨论】:

您生成的 PDF 只是为了通过电子邮件发送? @SzymonP。是的,我必须发送一个附件。 【参考方案1】:

如果您仅使用PDFTemplateResponse 生成pdf 文件并通过电子邮件发送(不是http 响应),您可以在celery 任务中模拟它。

你的 celery 任务,在&lt;app_name&gt;/tasks.py 文件中:

from django.test.client import RequestFactory
# other necessary imports
...

@shared_task
def create_pdf_and_send_email(template, context, email_body, to_address):
    request = RequestFactory().get('') # any valid url
    challan_rows = ChallanRow.objects.filter(pk__in=context['challan_rows'])
    context['challan_rows'] = challan_rows
    response = PDFTemplateResponse(
            request=request,
            template=template,
            filename='challan_email.pdf',
            context=context,
            show_content_in_browser=True,
            cmd_options='margin-top': 10,
                         'zoom': 1,
                         'viewport-size': '1366 x 513',
                         'javascript-delay': 1000,
                         'no-stop-slow-scripts': True,
        )

        file_path = os.path.join(settings.BASE_DIR, settings.MEDIA_ROOT, 'challan_email.pdf')
        with open(file_path, 'wb') as f:
            f.write(response.rendered_content)

        subject = 'From Design Ace Limited'
        body = email_body
        from_email = 'Sorower Hossain <sorower@europartsbd.com>'
        to = [''.format(to_address)]
        attachment = os.path.join(settings.MEDIA_ROOT, 'challan_email.pdf')
        send_email(subject, body, from_email, to, attachment)

还有你重构的get 方法:

from .tasks import create_pdf_and_send_email
...
def get(self, request, **kwargs):
    challan = Challan.objects.get(pk=kwargs['pk'])
    ref_no = challan.ref_no
    date = challan.created
    recipient = challan.recipient
    address = challan.recipient_address
    challan_rows = ChallanRow.objects.filter(challan=challan).values_list('pk', flat=True)

    context = 
        "ref_no": ref_no,
        "date": date,
        "recipient": recipient,
        "address": address,
        "challan_rows": challan_rows,
    

    email_body = self.request.GET.get('email_body', '')
    to_address = self.request.GET.get('to_address')
    create_pdf_and_send_email.delay(template, context, email_body, to_address)
    return HttpResponseRedirect(reverse('europarts:challan_details', args=(kwargs['pk'],)))

我希望它应该有效。

如果出现序列化错误的任何问题(可能与context 变量中的address 字段有关),请在get 方法中将address = challan.recipient_address 更改为address = challan.recipient_address.pk。 然后,在 celery 任务中,使用给定的 pk 检索地址(例如 address = YourAddressModel.objects.get(pk=context['address']),并将 context['address'] 替换为 context['address'] = address

【讨论】:

使用RequestFactory 解决了我的问题,但是,现在它显示&lt;QuerySet [&lt;ChallanRow: 456&gt;]&gt; is not JSON serializable。我猜我的查询集需要以不同的方式传递。 这是唯一的方法吗?为此目的

以上是关于如何在 Django 项目中将请求传递给 Celery 任务参数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django 的基于类的视图中将值传递给模板?

在Django中将上下文从子模板传递给父模板

在 Django 中将值传递给 Bootstrap 模式

如何使用附加的@RequestBody 在 POST 请求中将 Pageable 传递给 Feign 客户端

在django中将动态变量从父模板传递给子模板

在 Django 1.11 中将 QuerySet 传递给 Celery 任务