forked from CGM_Public/pretix_original
Always process tasks through celery (#245)
This commit is contained in:
@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
@@ -10,6 +10,8 @@ from pretix.base.i18n import LazyLocaleException
|
||||
from pretix.base.models import (
|
||||
CartPosition, Event, EventLock, Item, ItemVariation, Quota, Voucher,
|
||||
)
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.celery import app
|
||||
|
||||
|
||||
class CartError(LazyLocaleException):
|
||||
@@ -205,7 +207,8 @@ def _add_items_to_cart(event: Event, items: List[dict], cart_id: str=None) -> No
|
||||
raise CartError(err)
|
||||
|
||||
|
||||
def add_items_to_cart(event: int, items: List[dict], cart_id: str=None) -> None:
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None) -> None:
|
||||
"""
|
||||
Adds a list of items to a user's cart.
|
||||
:param event: The event ID in question
|
||||
@@ -216,8 +219,11 @@ def add_items_to_cart(event: int, items: List[dict], cart_id: str=None) -> None:
|
||||
"""
|
||||
event = Event.objects.get(id=event)
|
||||
try:
|
||||
_add_items_to_cart(event, items, cart_id)
|
||||
except EventLock.LockTimeoutException:
|
||||
try:
|
||||
_add_items_to_cart(event, items, cart_id)
|
||||
except LockTimeoutException:
|
||||
self.retry()
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
raise CartError(error_messages['busy'])
|
||||
|
||||
|
||||
@@ -242,7 +248,8 @@ def _remove_items_from_cart(event: Event, items: List[dict], cart_id: str) -> No
|
||||
cp.delete()
|
||||
|
||||
|
||||
def remove_items_from_cart(event: int, items: List[dict], cart_id: str=None) -> None:
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def remove_items_from_cart(self, event: int, items: List[dict], cart_id: str=None) -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
@@ -251,35 +258,9 @@ def remove_items_from_cart(event: int, items: List[dict], cart_id: str=None) ->
|
||||
"""
|
||||
event = Event.objects.get(id=event)
|
||||
try:
|
||||
_remove_items_from_cart(event, items, cart_id)
|
||||
except EventLock.LockTimeoutException:
|
||||
try:
|
||||
_remove_items_from_cart(event, items, cart_id)
|
||||
except LockTimeoutException:
|
||||
self.retry()
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
raise CartError(error_messages['busy'])
|
||||
|
||||
|
||||
if settings.HAS_CELERY:
|
||||
from pretix.celery import app
|
||||
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def add_items_to_cart_task(self, event: int, items: List[dict], cart_id: str):
|
||||
event = Event.objects.get(id=event)
|
||||
try:
|
||||
try:
|
||||
_add_items_to_cart(event, items, cart_id)
|
||||
except EventLock.LockTimeoutException:
|
||||
self.retry(exc=CartError(error_messages['busy']))
|
||||
except CartError as e:
|
||||
return e
|
||||
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def remove_items_from_cart_task(self, event: int, items: List[dict], cart_id: str):
|
||||
event = Event.objects.get(id=event)
|
||||
try:
|
||||
try:
|
||||
_remove_items_from_cart(event, items, cart_id)
|
||||
except EventLock.LockTimeoutException:
|
||||
self.retry(exc=CartError(error_messages['busy']))
|
||||
except CartError as e:
|
||||
return e
|
||||
|
||||
add_items_to_cart.task = add_items_to_cart_task
|
||||
remove_items_from_cart.task = remove_items_from_cart_task
|
||||
|
||||
@@ -5,8 +5,10 @@ from django.core.files.base import ContentFile
|
||||
|
||||
from pretix.base.models import CachedFile, Event, cachedfile_name
|
||||
from pretix.base.signals import register_data_exporters
|
||||
from pretix.celery import app
|
||||
|
||||
|
||||
@app.task()
|
||||
def export(event: str, fileid: str, provider: str, form_data: Dict[str, Any]) -> None:
|
||||
event = Event.objects.get(id=event)
|
||||
file = CachedFile.objects.get(id=fileid)
|
||||
@@ -17,12 +19,3 @@ def export(event: str, fileid: str, provider: str, form_data: Dict[str, Any]) ->
|
||||
file.filename, file.type, data = ex.render(form_data)
|
||||
file.file.save(cachedfile_name(file, file.filename), ContentFile(data))
|
||||
file.save()
|
||||
|
||||
|
||||
if settings.HAS_CELERY:
|
||||
from pretix.celery import app
|
||||
|
||||
export_task = app.task(export)
|
||||
|
||||
def export(*args, **kwargs):
|
||||
export_task.apply_async(args=args, kwargs=kwargs)
|
||||
|
||||
@@ -24,6 +24,7 @@ from reportlab.platypus import (
|
||||
from pretix.base.i18n import LazyI18nString, language
|
||||
from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.celery import app
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
@@ -374,7 +375,8 @@ def _invoice_generate_german(invoice, f):
|
||||
return doc
|
||||
|
||||
|
||||
def invoice_pdf(invoice: int):
|
||||
@app.task
|
||||
def invoice_pdf_task(invoice: int):
|
||||
i = Invoice.objects.get(pk=invoice)
|
||||
with language(i.locale):
|
||||
with tempfile.NamedTemporaryFile(suffix=".pdf") as f:
|
||||
@@ -391,13 +393,8 @@ def invoice_qualified(order: Order):
|
||||
return True
|
||||
|
||||
|
||||
if settings.HAS_CELERY:
|
||||
from pretix.celery import app
|
||||
|
||||
invoice_pdf_task = app.task(invoice_pdf)
|
||||
|
||||
def invoice_pdf(*args, **kwargs):
|
||||
# We introduce a 2 second delay, 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)
|
||||
def invoice_pdf(*args, **kwargs):
|
||||
# We introduce a 2 second delay, 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)
|
||||
|
||||
@@ -27,13 +27,21 @@ class LockManager:
|
||||
return False
|
||||
|
||||
|
||||
class LockTimeoutException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LockReleaseException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def lock_event(event):
|
||||
"""
|
||||
Issue a lock on this event so nobody can book tickets for this event until
|
||||
you release the lock. Will retry 5 times on failure.
|
||||
|
||||
:raises EventLock.LockTimeoutException: if the event is locked every time we try
|
||||
to obtain the lock
|
||||
:raises LockTimeoutException: if the event is locked every time we try
|
||||
to obtain the lock
|
||||
"""
|
||||
if hasattr(event, '_lock') and event._lock:
|
||||
return True
|
||||
@@ -50,10 +58,10 @@ def release_event(event):
|
||||
the lock will only be released if it was issued in _this_ python
|
||||
representation of the database object.
|
||||
|
||||
:raises EventLock.LockReleaseException: if we do not own the lock
|
||||
:raises LockReleaseException: if we do not own the lock
|
||||
"""
|
||||
if not hasattr(event, '_lock') or not event._lock:
|
||||
raise EventLock.LockReleaseException('Lock is not owned by this thread')
|
||||
raise LockReleaseException('Lock is not owned by this thread')
|
||||
if settings.HAS_REDIS:
|
||||
return release_event_redis(event)
|
||||
else:
|
||||
@@ -70,26 +78,26 @@ def lock_event_db(event):
|
||||
event._lock = l
|
||||
return True
|
||||
elif l.date < now() - timedelta(seconds=LOCK_TIMEOUT):
|
||||
newtoken = uuid.uuid4()
|
||||
newtoken = str(uuid.uuid4())
|
||||
updated = EventLock.objects.filter(event=event.id, token=l.token).update(date=dt, token=newtoken)
|
||||
if updated:
|
||||
l.token = newtoken
|
||||
event._lock = l
|
||||
return True
|
||||
time.sleep(2 ** i / 100)
|
||||
raise EventLock.LockTimeoutException()
|
||||
raise LockTimeoutException()
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def release_event_db(event):
|
||||
if not hasattr(event, '_lock') or not event._lock:
|
||||
raise EventLock.LockReleaseException('Lock is not owned by this thread')
|
||||
raise LockReleaseException('Lock is not owned by this thread')
|
||||
try:
|
||||
lock = EventLock.objects.get(event=event.id, token=event._lock.token)
|
||||
lock.delete()
|
||||
event._lock = None
|
||||
except EventLock.DoesNotExist:
|
||||
raise EventLock.LockReleaseException('Lock is no longer owned by this thread')
|
||||
raise LockReleaseException('Lock is no longer owned by this thread')
|
||||
|
||||
|
||||
def redis_lock_from_event(event):
|
||||
@@ -113,9 +121,9 @@ def lock_event_redis(event):
|
||||
return True
|
||||
except RedisError:
|
||||
logger.exception('Error locking an event')
|
||||
raise EventLock.LockTimeoutException()
|
||||
raise LockTimeoutException()
|
||||
time.sleep(2 ** i / 100)
|
||||
raise EventLock.LockTimeoutException()
|
||||
raise LockTimeoutException()
|
||||
|
||||
|
||||
def release_event_redis(event):
|
||||
@@ -126,5 +134,5 @@ def release_event_redis(event):
|
||||
lock.release()
|
||||
except RedisError:
|
||||
logger.exception('Error releasing an event lock')
|
||||
raise EventLock.LockTimeoutException()
|
||||
raise LockTimeoutException()
|
||||
event._lock = None
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
from pretix.base.i18n import LazyI18nString, language
|
||||
from pretix.base.models import Event, Order
|
||||
from pretix.celery import app
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
logger = logging.getLogger('pretix.base.mail')
|
||||
@@ -90,7 +91,8 @@ def mail(email: str, subject: str, template: str,
|
||||
return mail_send([email], subject, body, sender, event.id if event else None, headers)
|
||||
|
||||
|
||||
def mail_send(to: str, subject: str, body: str, sender: str, event: int=None, headers: dict=None) -> bool:
|
||||
@app.task
|
||||
def mail_send_task(to: str, subject: str, body: str, sender: str, event: int=None, headers: dict=None) -> bool:
|
||||
email = EmailMessage(subject, body, sender, to=to, headers=headers)
|
||||
if event:
|
||||
event = Event.objects.get(id=event)
|
||||
@@ -105,10 +107,5 @@ def mail_send(to: str, subject: str, body: str, sender: str, event: int=None, he
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
|
||||
|
||||
if settings.HAS_CELERY and settings.EMAIL_BACKEND != 'django.core.mail.outbox':
|
||||
from pretix.celery import app
|
||||
|
||||
mail_send_task = app.task(mail_send)
|
||||
|
||||
def mail_send(*args, **kwargs):
|
||||
mail_send_task.apply_async(args=args, kwargs=kwargs)
|
||||
def mail_send(*args, **kwargs):
|
||||
mail_send_task.apply_async(args=args, kwargs=kwargs)
|
||||
|
||||
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
from django.db import transaction
|
||||
from django.dispatch import receiver
|
||||
from django.utils.formats import date_format
|
||||
@@ -23,10 +23,12 @@ from pretix.base.payment import BasePaymentProvider
|
||||
from pretix.base.services.invoices import (
|
||||
generate_cancellation, generate_invoice, invoice_qualified,
|
||||
)
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.base.signals import (
|
||||
order_paid, order_placed, periodic_task, register_payment_providers,
|
||||
)
|
||||
from pretix.celery import app
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
error_messages = {
|
||||
@@ -137,7 +139,7 @@ def mark_order_refunded(order, user=None):
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def cancel_order(order, user=None):
|
||||
def _cancel_order(order, user=None):
|
||||
"""
|
||||
Mark this order as canceled
|
||||
:param order: The order to change
|
||||
@@ -348,16 +350,6 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
return order.id
|
||||
|
||||
|
||||
def perform_order(event: str, payment_provider: str, positions: List[str],
|
||||
email: str=None, locale: str=None, address: int=None):
|
||||
try:
|
||||
return _perform_order(event, payment_provider, positions, email, locale, address)
|
||||
except EventLock.LockTimeoutException:
|
||||
# Is raised when there are too many threads asking for event locks and we were
|
||||
# unable to get one
|
||||
raise OrderError(error_messages['busy'])
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
def expire_orders(sender, **kwargs):
|
||||
eventcache = {}
|
||||
@@ -571,29 +563,24 @@ class OrderChangeManager:
|
||||
raise OrderError(error_messages['internal'])
|
||||
|
||||
|
||||
if settings.HAS_CELERY:
|
||||
from pretix.celery import app
|
||||
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def perform_order_task(self, event: str, payment_provider: str, positions: List[str],
|
||||
email: str=None, locale: str=None, address: int=None):
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def perform_order(self, event: str, payment_provider: str, positions: List[str],
|
||||
email: str=None, locale: str=None, address: int=None):
|
||||
try:
|
||||
try:
|
||||
try:
|
||||
return _perform_order(event, payment_provider, positions, email, locale, address)
|
||||
except EventLock.LockTimeoutException:
|
||||
self.retry(exc=OrderError(error_messages['busy']))
|
||||
except OrderError as e:
|
||||
return e
|
||||
return _perform_order(event, payment_provider, positions, email, locale, address)
|
||||
except LockTimeoutException:
|
||||
self.retry()
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
return OrderError(error_messages['busy'])
|
||||
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def cancel_order_task(self, order: int, user: int=None):
|
||||
|
||||
@app.task(bind=True, max_retries=5, default_retry_delay=1)
|
||||
def cancel_order(self, order: int, user: int=None):
|
||||
try:
|
||||
try:
|
||||
try:
|
||||
return cancel_order(order, user)
|
||||
except EventLock.LockTimeoutException:
|
||||
self.retry(exc=OrderError(error_messages['busy']))
|
||||
except OrderError as e:
|
||||
return e
|
||||
|
||||
perform_order.task = perform_order_task
|
||||
cancel_order.task = cancel_order_task
|
||||
return _cancel_order(order, user)
|
||||
except LockTimeoutException:
|
||||
self.retry(exc=OrderError(error_messages['busy']))
|
||||
except (MaxRetriesExceededError, LockTimeoutException):
|
||||
return OrderError(error_messages['busy'])
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.timezone import now
|
||||
|
||||
from pretix.base.models import CachedFile, CachedTicket, Order, cachedfile_name
|
||||
from pretix.base.signals import register_ticket_outputs
|
||||
from pretix.celery import app
|
||||
|
||||
|
||||
@app.task
|
||||
def generate(order: str, provider: str):
|
||||
order = Order.objects.select_related('event').get(id=order)
|
||||
ct = CachedTicket.objects.get_or_create(order=order, provider=provider)[0]
|
||||
@@ -26,12 +27,3 @@ def generate(order: str, provider: str):
|
||||
ct.cachedfile.filename, ct.cachedfile.type, data = prov.generate(order)
|
||||
ct.cachedfile.file.save(cachedfile_name(ct.cachedfile, ct.cachedfile.filename), ContentFile(data))
|
||||
ct.cachedfile.save()
|
||||
|
||||
|
||||
if settings.HAS_CELERY:
|
||||
from pretix.celery import app
|
||||
|
||||
generate_task = app.task(generate)
|
||||
|
||||
def generate(*args, **kwargs):
|
||||
generate_task.apply_async(args=args, kwargs=kwargs)
|
||||
|
||||
Reference in New Issue
Block a user