diff --git a/src/pretix/base/services/async.py b/src/pretix/base/services/async.py new file mode 100644 index 000000000..16e4688a0 --- /dev/null +++ b/src/pretix/base/services/async.py @@ -0,0 +1,28 @@ +""" +This code has been taken from +https://blog.hypertrack.io/2016/10/08/dealing-with-database-transactions-in-django-celery/ + +Usage: + from pretix.base.services.async import TransactionAwareTask + @task(base=TransactionAwareTask) + def task_…(): +""" +from celery import Task +from django.db import transaction + + +class TransactionAwareTask(Task): + """ + Task class which is aware of django db transactions and only executes tasks + after transaction has been committed + """ + abstract = True + + def apply_async(self, *args, **kwargs): + """ + Unlike the default task in celery, this task does not return an async + result + """ + transaction.on_commit( + lambda: super(TransactionAwareTask, self).apply_async(*args, **kwargs) + ) diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py index 07580fa06..52239adff 100644 --- a/src/pretix/base/services/invoices.py +++ b/src/pretix/base/services/invoices.py @@ -23,6 +23,7 @@ from reportlab.platypus import ( from pretix.base.i18n import LazyI18nString, language from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order +from pretix.base.services.async import TransactionAwareTask from pretix.base.signals import register_payment_providers from pretix.celery import app @@ -375,7 +376,7 @@ def _invoice_generate_german(invoice, f): return doc -@app.task +@app.task(base=TransactionAwareTask) def invoice_pdf_task(invoice: int): i = Invoice.objects.get(pk=invoice) with language(i.locale): @@ -394,7 +395,8 @@ def invoice_qualified(order: Order): def invoice_pdf(*args, **kwargs): - # We introduce a 2 second delay, because otherwise we run into conditions where + # We call this task asynchroneously, because otherwise we run into conditions where # the task worker tries to generate the PDF even before our database transaction - # was committed and therefore fails to find the invoice object. - invoice_pdf_task.apply_async(args=args, kwargs=kwargs, countdown=2) + # was committed and therefore fails to find the invoice object. The invoice_pdf_task + # will prevent this kind of race condition. + invoice_pdf_task.apply_async(args=args, kwargs=kwargs) diff --git a/src/pretix/plugins/banktransfer/tasks.py b/src/pretix/plugins/banktransfer/tasks.py index e0bec58bc..62bf1e9d1 100644 --- a/src/pretix/plugins/banktransfer/tasks.py +++ b/src/pretix/plugins/banktransfer/tasks.py @@ -9,6 +9,7 @@ from django.utils.translation import ugettext_noop from pretix.base.i18n import language from pretix.base.models import Event, Order, Quota +from pretix.base.services.async import TransactionAwareTask from pretix.base.services.mail import SendMailException from pretix.base.services.orders import mark_order_paid from pretix.celery import app @@ -95,7 +96,7 @@ def _get_unknown_transactions(event: Event, job: BankImportJob, data: list): return transactions -@app.task +@app.task(base=TransactionAwareTask) def process_banktransfers(event: int, job: int, data: list) -> None: with language("en"): # We'll translate error messages at display time event = Event.objects.get(pk=event) diff --git a/src/pretix/plugins/banktransfer/views.py b/src/pretix/plugins/banktransfer/views.py index fe909b5e7..a577a4711 100644 --- a/src/pretix/plugins/banktransfer/views.py +++ b/src/pretix/plugins/banktransfer/views.py @@ -337,7 +337,7 @@ class ImportView(EventPermissionRequiredMixin, ListView): 'event': self.request.event.pk, 'job': job.pk, 'data': parsed - }, countdown=1) + }) return redirect(reverse('plugins:banktransfer:import.job', kwargs={ 'event': self.request.event.slug, 'organizer': self.request.event.organizer.slug,