Update tasks and cronjobs

This commit is contained in:
Raphael Michel
2019-06-13 12:18:39 +02:00
parent 867667cd12
commit 69a80f2540
22 changed files with 307 additions and 251 deletions

View File

@@ -2,6 +2,7 @@ from datetime import timedelta
from django.dispatch import Signal, receiver from django.dispatch import Signal, receiver
from django.utils.timezone import now from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.api.models import ApiCall, WebHookCall from pretix.api.models import ApiCall, WebHookCall
from pretix.base.signals import periodic_task from pretix.base.signals import periodic_task
@@ -17,10 +18,12 @@ instances.
@receiver(periodic_task) @receiver(periodic_task)
@scopes_disabled()
def cleanup_webhook_logs(sender, **kwargs): def cleanup_webhook_logs(sender, **kwargs):
WebHookCall.objects.filter(datetime__lte=now() - timedelta(days=30)).delete() WebHookCall.objects.filter(datetime__lte=now() - timedelta(days=30)).delete()
@receiver(periodic_task) @receiver(periodic_task)
@scopes_disabled()
def cleanup_api_logs(sender, **kwargs): def cleanup_api_logs(sender, **kwargs):
ApiCall.objects.filter(created__lte=now() - timedelta(hours=24)).delete() ApiCall.objects.filter(created__lte=now() - timedelta(hours=24)).delete()

View File

@@ -8,6 +8,7 @@ from celery.exceptions import MaxRetriesExceededError
from django.db.models import Exists, OuterRef, Q from django.db.models import Exists, OuterRef, Q
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_scopes import scope, scopes_disabled
from requests import RequestException from requests import RequestException
from pretix.api.models import WebHook, WebHookCall, WebHookEventListener from pretix.api.models import WebHook, WebHookCall, WebHookEventListener
@@ -203,51 +204,52 @@ def notify_webhooks(logentry_id: int):
@app.task(base=ProfiledTask, bind=True, max_retries=9) @app.task(base=ProfiledTask, bind=True, max_retries=9)
def send_webhook(self, logentry_id: int, action_type: str, webhook_id: int): def send_webhook(self, logentry_id: int, action_type: str, webhook_id: int):
# 9 retries with 2**(2*x) timing is roughly 72 hours # 9 retries with 2**(2*x) timing is roughly 72 hours
logentry = LogEntry.all.get(id=logentry_id) with scopes_disabled():
webhook = WebHook.objects.get(id=webhook_id) webhook = WebHook.objects.get(id=webhook_id)
with scope(organizer=webhook.organizer):
logentry = LogEntry.all.get(id=logentry_id)
types = get_all_webhook_events()
event_type = types.get(action_type)
if not event_type or not webhook.enabled:
return # Ignore, e.g. plugin not installed
types = get_all_webhook_events() payload = event_type.build_payload(logentry)
event_type = types.get(action_type) t = time.time()
if not event_type or not webhook.enabled:
return # Ignore, e.g. plugin not installed
payload = event_type.build_payload(logentry)
t = time.time()
try:
try: try:
resp = requests.post( try:
webhook.target_url, resp = requests.post(
json=payload, webhook.target_url,
allow_redirects=False json=payload,
) allow_redirects=False
WebHookCall.objects.create( )
webhook=webhook, WebHookCall.objects.create(
action_type=logentry.action_type, webhook=webhook,
target_url=webhook.target_url, action_type=logentry.action_type,
is_retry=self.request.retries > 0, target_url=webhook.target_url,
execution_time=time.time() - t, is_retry=self.request.retries > 0,
return_code=resp.status_code, execution_time=time.time() - t,
payload=json.dumps(payload), return_code=resp.status_code,
response_body=resp.text[:1024 * 1024], payload=json.dumps(payload),
success=200 <= resp.status_code <= 299 response_body=resp.text[:1024 * 1024],
) success=200 <= resp.status_code <= 299
if resp.status_code == 410: )
webhook.enabled = False if resp.status_code == 410:
webhook.save() webhook.enabled = False
elif resp.status_code > 299: webhook.save()
elif resp.status_code > 299:
raise self.retry(countdown=2 ** (self.request.retries * 2))
except RequestException as e:
WebHookCall.objects.create(
webhook=webhook,
action_type=logentry.action_type,
target_url=webhook.target_url,
is_retry=self.request.retries > 0,
execution_time=time.time() - t,
return_code=0,
payload=json.dumps(payload),
response_body=str(e)[:1024 * 1024]
)
raise self.retry(countdown=2 ** (self.request.retries * 2)) raise self.retry(countdown=2 ** (self.request.retries * 2))
except RequestException as e: except MaxRetriesExceededError:
WebHookCall.objects.create( pass
webhook=webhook,
action_type=logentry.action_type,
target_url=webhook.target_url,
is_retry=self.request.retries > 0,
execution_time=time.time() - t,
return_code=0,
payload=json.dumps(payload),
response_body=str(e)[:1024 * 1024]
)
raise self.retry(countdown=2 ** (self.request.retries * 2))
except MaxRetriesExceededError:
pass

View File

@@ -23,7 +23,7 @@ from pretix.base.reldate import RelativeDateWrapper
from pretix.base.services.checkin import _save_answers from pretix.base.services.checkin import _save_answers
from pretix.base.services.locking import LockTimeoutException, NoLockManager from pretix.base.services.locking import LockTimeoutException, NoLockManager
from pretix.base.services.pricing import get_price from pretix.base.services.pricing import get_price
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.templatetags.rich_text import rich_text from pretix.base.templatetags.rich_text import rich_text
from pretix.celery_app import app from pretix.celery_app import app
@@ -902,7 +902,7 @@ def get_fees(event, request, total, invoice_address, provider):
return fees return fees
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,)) @app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, locale='en', def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, locale='en',
invoice_address: int=None, widget_data=None, sales_channel='web') -> None: invoice_address: int=None, widget_data=None, sales_channel='web') -> None:
""" """
@@ -913,8 +913,6 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
:raises CartError: On any error that occured :raises CartError: On any error that occured
""" """
with language(locale): with language(locale):
event = Event.objects.get(id=event)
ia = False ia = False
if invoice_address: if invoice_address:
try: try:
@@ -934,8 +932,8 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
raise CartError(error_messages['busy']) raise CartError(error_messages['busy'])
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,)) @app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def remove_cart_position(self, event: int, position: int, cart_id: str=None, locale='en') -> None: def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en') -> None:
""" """
Removes a list of items from a user's cart. Removes a list of items from a user's cart.
:param event: The event ID in question :param event: The event ID in question
@@ -943,7 +941,6 @@ def remove_cart_position(self, event: int, position: int, cart_id: str=None, loc
:param session: Session ID of a guest :param session: Session ID of a guest
""" """
with language(locale): with language(locale):
event = Event.objects.get(id=event)
try: try:
try: try:
cm = CartManager(event=event, cart_id=cart_id) cm = CartManager(event=event, cart_id=cart_id)
@@ -955,15 +952,14 @@ def remove_cart_position(self, event: int, position: int, cart_id: str=None, loc
raise CartError(error_messages['busy']) raise CartError(error_messages['busy'])
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,)) @app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def clear_cart(self, event: int, cart_id: str=None, locale='en') -> None: def clear_cart(self, event: Event, cart_id: str=None, locale='en') -> None:
""" """
Removes a list of items from a user's cart. Removes a list of items from a user's cart.
:param event: The event ID in question :param event: The event ID in question
:param session: Session ID of a guest :param session: Session ID of a guest
""" """
with language(locale): with language(locale):
event = Event.objects.get(id=event)
try: try:
try: try:
cm = CartManager(event=event, cart_id=cart_id) cm = CartManager(event=event, cart_id=cart_id)
@@ -975,8 +971,8 @@ def clear_cart(self, event: int, cart_id: str=None, locale='en') -> None:
raise CartError(error_messages['busy']) raise CartError(error_messages['busy'])
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,)) @app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
def set_cart_addons(self, event: int, addons: List[dict], cart_id: str=None, locale='en', def set_cart_addons(self, event: Event, addons: List[dict], cart_id: str=None, locale='en',
invoice_address: int=None, sales_channel='web') -> None: invoice_address: int=None, sales_channel='web') -> None:
""" """
Removes a list of items from a user's cart. Removes a list of items from a user's cart.
@@ -985,8 +981,6 @@ def set_cart_addons(self, event: int, addons: List[dict], cart_id: str=None, loc
:param session: Session ID of a guest :param session: Session ID of a guest
""" """
with language(locale): with language(locale):
event = Event.objects.get(id=event)
ia = False ia = False
if invoice_address: if invoice_address:
try: try:

View File

@@ -2,6 +2,7 @@ from datetime import timedelta
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import now from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.base.models import CachedCombinedTicket, CachedTicket from pretix.base.models import CachedCombinedTicket, CachedTicket
@@ -10,6 +11,7 @@ from ..signals import periodic_task
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled()
def clean_cart_positions(sender, **kwargs): def clean_cart_positions(sender, **kwargs):
for cp in CartPosition.objects.filter(expires__lt=now() - timedelta(days=14), addon_to__isnull=False): for cp in CartPosition.objects.filter(expires__lt=now() - timedelta(days=14), addon_to__isnull=False):
cp.delete() cp.delete()
@@ -20,12 +22,14 @@ def clean_cart_positions(sender, **kwargs):
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled()
def clean_cached_files(sender, **kwargs): def clean_cached_files(sender, **kwargs):
for cf in CachedFile.objects.filter(expires__isnull=False, expires__lt=now()): for cf in CachedFile.objects.filter(expires__isnull=False, expires__lt=now()):
cf.delete() cf.delete()
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled()
def clean_cached_tickets(sender, **kwargs): def clean_cached_tickets(sender, **kwargs):
for cf in CachedTicket.objects.filter(created__lte=now() - timedelta(days=30)): for cf in CachedTicket.objects.filter(created__lte=now() - timedelta(days=30)):
cf.delete() cf.delete()

View File

@@ -6,7 +6,7 @@ from django.utils.translation import ugettext
from pretix.base.i18n import LazyLocaleException, language from pretix.base.i18n import LazyLocaleException, language
from pretix.base.models import CachedFile, Event, cachedfile_name from pretix.base.models import CachedFile, Event, cachedfile_name
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.signals import register_data_exporters from pretix.base.signals import register_data_exporters
from pretix.celery_app import app from pretix.celery_app import app
@@ -15,9 +15,8 @@ class ExportError(LazyLocaleException):
pass pass
@app.task(base=ProfiledTask, throws=(ExportError,)) @app.task(base=ProfiledEventTask, throws=(ExportError,))
def export(event: str, fileid: str, provider: str, form_data: Dict[str, Any]) -> None: def export(event: Event, fileid: str, provider: str, form_data: Dict[str, Any]) -> None:
event = Event.objects.get(id=event)
file = CachedFile.objects.get(id=fileid) file = CachedFile.objects.get(id=fileid)
with language(event.settings.locale), override(event.settings.timezone): with language(event.settings.locale), override(event.settings.timezone):
responses = register_data_exporters.send(event) responses = register_data_exporters.send(event)

View File

@@ -15,6 +15,7 @@ from django.utils import timezone
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import pgettext, ugettext as _ from django.utils.translation import pgettext, ugettext as _
from django_countries.fields import Country from django_countries.fields import Country
from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from pretix.base.i18n import language from pretix.base.i18n import language
@@ -244,16 +245,18 @@ def generate_invoice(order: Order, trigger_pdf=True):
@app.task(base=TransactionAwareTask) @app.task(base=TransactionAwareTask)
def invoice_pdf_task(invoice: int): def invoice_pdf_task(invoice: int):
i = Invoice.objects.get(pk=invoice) with scopes_disabled():
if i.shredded: i = Invoice.objects.get(pk=invoice)
return None with scope(organizer=i.order.event.organizer):
if i.file: if i.shredded:
i.file.delete() return None
with language(i.locale): if i.file:
fname, ftype, fcontent = i.event.invoice_renderer.generate(i) i.file.delete()
i.file.save(fname, ContentFile(fcontent)) with language(i.locale):
i.save() fname, ftype, fcontent = i.event.invoice_renderer.generate(i)
return i.file.name i.file.save(fname, ContentFile(fcontent))
i.save()
return i.file.name
def invoice_qualified(order: Order): def invoice_qualified(order: Order):

View File

@@ -10,6 +10,7 @@ from django.conf import settings
from django.core.mail import EmailMultiAlternatives, get_connection from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import get_template from django.template.loader import get_template
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from pretix.base.email import ClassicMailRenderer from pretix.base.email import ClassicMailRenderer
@@ -234,83 +235,87 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
pass pass
if event: if event:
event = Event.objects.get(id=event) with scopes_disabled():
event = Event.objects.get(id=event)
backend = event.get_mail_backend() backend = event.get_mail_backend()
cm = lambda: scope(organizer=event.organizer) # noqa
else: else:
backend = get_connection(fail_silently=False) backend = get_connection(fail_silently=False)
cm = lambda: scopes_disabled() # noqa
if event: with cm():
if order: if event:
try: if order:
order = event.orders.get(pk=order) try:
except Order.DoesNotExist: order = event.orders.get(pk=order)
order = None except Order.DoesNotExist:
else: order = None
if position: else:
try: if position:
position = order.positions.get(pk=position) try:
except OrderPosition.DoesNotExist: position = order.positions.get(pk=position)
attach_tickets = False except OrderPosition.DoesNotExist:
if attach_tickets: attach_tickets = False
args = [] if attach_tickets:
attach_size = 0 args = []
for name, ct in get_tickets_for_order(order, base_position=position): attach_size = 0
content = ct.file.read() for name, ct in get_tickets_for_order(order, base_position=position):
args.append((name, content, ct.type)) content = ct.file.read()
attach_size += len(content) args.append((name, content, ct.type))
attach_size += len(content)
if attach_size < 4 * 1024 * 1024: if attach_size < 4 * 1024 * 1024:
# Do not attach more than 4MB, it will bounce way to often. # Do not attach more than 4MB, it will bounce way to often.
for a in args: for a in args:
try: try:
email.attach(*a) email.attach(*a)
except: except:
pass pass
else: else:
order.log_action( order.log_action(
'pretix.event.order.email.attachments.skipped', 'pretix.event.order.email.attachments.skipped',
data={ data={
'subject': 'Attachments skipped', 'subject': 'Attachments skipped',
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size), 'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
'recipient': '', 'recipient': '',
'invoices': [], 'invoices': [],
} }
) )
email = email_filter.send_chained(event, 'message', message=email, order=order) email = email_filter.send_chained(event, 'message', message=email, order=order)
try: try:
backend.send_messages([email]) backend.send_messages([email])
except smtplib.SMTPResponseException as e: except smtplib.SMTPResponseException as e:
if e.smtp_code in (101, 111, 421, 422, 431, 442, 447, 452): if e.smtp_code in (101, 111, 421, 422, 431, 442, 447, 452):
self.retry(max_retries=5, countdown=2 ** (self.request.retries * 2)) self.retry(max_retries=5, countdown=2 ** (self.request.retries * 2))
logger.exception('Error sending email') logger.exception('Error sending email')
if order: if order:
order.log_action( order.log_action(
'pretix.event.order.email.error', 'pretix.event.order.email.error',
data={ data={
'subject': 'SMTP code {}'.format(e.smtp_code), 'subject': 'SMTP code {}'.format(e.smtp_code),
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error), 'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
'recipient': '', 'recipient': '',
'invoices': [], 'invoices': [],
} }
) )
raise SendMailException('Failed to send an email to {}.'.format(to)) raise SendMailException('Failed to send an email to {}.'.format(to))
except Exception as e: except Exception as e:
if order: if order:
order.log_action( order.log_action(
'pretix.event.order.email.error', 'pretix.event.order.email.error',
data={ data={
'subject': 'Internal error', 'subject': 'Internal error',
'message': str(e), 'message': str(e),
'recipient': '', 'recipient': '',
'invoices': [], 'invoices': [],
} }
) )
logger.exception('Error sending email') logger.exception('Error sending email')
raise SendMailException('Failed to send an email to {}.'.format(to)) raise SendMailException('Failed to send an email to {}.'.format(to))
def mail_send(*args, **kwargs): def mail_send(*args, **kwargs):

View File

@@ -1,5 +1,6 @@
from django.conf import settings from django.conf import settings
from django.template.loader import get_template from django.template.loader import get_template
from django_scopes import scope, scopes_disabled
from inlinestyler.utils import inline_css from inlinestyler.utils import inline_css
from pretix.base.i18n import language from pretix.base.i18n import language
@@ -12,6 +13,7 @@ from pretix.helpers.urls import build_absolute_uri
@app.task(base=TransactionAwareTask) @app.task(base=TransactionAwareTask)
@scopes_disabled()
def notify(logentry_id: int): def notify(logentry_id: int):
logentry = LogEntry.all.get(id=logentry_id) logentry = LogEntry.all.get(id=logentry_id)
if not logentry.event: if not logentry.event:
@@ -66,17 +68,22 @@ def notify(logentry_id: int):
@app.task(base=ProfiledTask) @app.task(base=ProfiledTask)
def send_notification(logentry_id: int, action_type: str, user_id: int, method: str): def send_notification(logentry_id: int, action_type: str, user_id: int, method: str):
logentry = LogEntry.all.get(id=logentry_id) logentry = LogEntry.all.get(id=logentry_id)
user = User.objects.get(id=user_id) if logentry.event:
types = get_all_notification_types(logentry.event) sm = lambda: scope(organizer=logentry.event.organizer) # noqa
notification_type = types.get(action_type) else:
if not notification_type: sm = lambda: scopes_disabled() # noqa
return # Ignore, e.g. plugin not active for this event with sm():
user = User.objects.get(id=user_id)
types = get_all_notification_types(logentry.event)
notification_type = types.get(action_type)
if not notification_type:
return # Ignore, e.g. plugin not active for this event
with language(user.locale): with language(user.locale):
notification = notification_type.build_notification(logentry) notification = notification_type.build_notification(logentry)
if method == "mail": if method == "mail":
send_notification_mail(notification, user) send_notification_mail(notification, user)
def send_notification_mail(notification: Notification, user: User): def send_notification_mail(notification: Notification, user: User):

View File

@@ -16,6 +16,7 @@ from django.utils.formats import date_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import make_aware, now from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django_scopes import scopes_disabled
from pretix.api.models import OAuthApplication from pretix.api.models import OAuthApplication
from pretix.base.i18n import ( from pretix.base.i18n import (
@@ -42,7 +43,7 @@ from pretix.base.services.invoices import (
from pretix.base.services.locking import LockTimeoutException, NoLockManager from pretix.base.services.locking import LockTimeoutException, NoLockManager
from pretix.base.services.mail import SendMailException from pretix.base.services.mail import SendMailException
from pretix.base.services.pricing import get_price from pretix.base.services.pricing import get_price
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledEventTask, ProfiledTask
from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import ( from pretix.base.signals import (
allow_ticket_download, order_approved, order_canceled, order_changed, allow_ticket_download, order_approved, order_canceled, order_changed,
@@ -715,10 +716,8 @@ def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosi
logger.exception('Order received email could not be sent to attendee') logger.exception('Order received email could not be sent to attendee')
def _perform_order(event: str, payment_provider: str, position_ids: List[str], def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web'): email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web'):
event = Event.objects.get(id=event)
if payment_provider: if payment_provider:
pprov = event.get_payment_providers().get(payment_provider) pprov = event.get_payment_providers().get(payment_provider)
if not pprov: if not pprov:
@@ -804,6 +803,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled()
def expire_orders(sender, **kwargs): def expire_orders(sender, **kwargs):
eventcache = {} eventcache = {}
@@ -818,6 +818,7 @@ def expire_orders(sender, **kwargs):
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled()
def send_expiry_warnings(sender, **kwargs): def send_expiry_warnings(sender, **kwargs):
eventcache = {} eventcache = {}
today = now().replace(hour=0, minute=0, second=0) today = now().replace(hour=0, minute=0, second=0)
@@ -875,6 +876,7 @@ def send_expiry_warnings(sender, **kwargs):
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled()
def send_download_reminders(sender, **kwargs): def send_download_reminders(sender, **kwargs):
today = now().replace(hour=0, minute=0, second=0, microsecond=0) today = now().replace(hour=0, minute=0, second=0, microsecond=0)
@@ -1497,8 +1499,8 @@ class OrderChangeManager:
return pprov return pprov
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,)) @app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
def perform_order(self, event: str, payment_provider: str, positions: List[str], def perform_order(self, event: Event, payment_provider: str, positions: List[str],
email: str=None, locale: str=None, address: int=None, meta_info: dict=None, email: str=None, locale: str=None, address: int=None, meta_info: dict=None,
sales_channel: str='web'): sales_channel: str='web'):
with language(locale): with language(locale):
@@ -1513,6 +1515,7 @@ def perform_order(self, event: str, payment_provider: str, positions: List[str],
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,)) @app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
@scopes_disabled()
def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None, def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None,
device=None, cancellation_fee=None, try_auto_refund=False): device=None, cancellation_fee=None, try_auto_refund=False):
try: try:

View File

@@ -4,6 +4,7 @@ from django.conf import settings
from django.db.models import Max, Q from django.db.models import Max, Q
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import now from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.base.models import Event, LogEntry from pretix.base.models import Event, LogEntry
from pretix.celery_app import app from pretix.celery_app import app
@@ -17,6 +18,7 @@ def build_all_quota_caches(sender, **kwargs):
@app.task @app.task
@scopes_disabled()
def refresh_quota_caches(): def refresh_quota_caches():
# Active events # Active events
active = LogEntry.objects.using(settings.DATABASE_REPLICA).filter( active = LogEntry.objects.using(settings.DATABASE_REPLICA).filter(

View File

@@ -11,14 +11,13 @@ from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretix.base.models import CachedFile, Event, cachedfile_name from pretix.base.models import CachedFile, Event, cachedfile_name
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.shredder import ShredError from pretix.base.shredder import ShredError
from pretix.celery_app import app from pretix.celery_app import app
@app.task(base=ProfiledTask) @app.task(base=ProfiledEventTask)
def export(event: str, shredders: List[str]) -> None: def export(event: Event, shredders: List[str]) -> None:
event = Event.objects.get(id=event)
known_shredders = event.get_data_shredders() known_shredders = event.get_data_shredders()
with NamedTemporaryFile() as rawfile: with NamedTemporaryFile() as rawfile:
@@ -63,9 +62,8 @@ def export(event: str, shredders: List[str]) -> None:
return cf.pk return cf.pk
@app.task(base=ProfiledTask, throws=(ShredError,)) @app.task(base=ProfiledEventTask, throws=(ShredError,))
def shred(event: str, fileid: str, confirm_code: str) -> None: def shred(event: Event, fileid: str, confirm_code: str) -> None:
event = Event.objects.get(id=event)
known_shredders = event.get_data_shredders() known_shredders = event.get_data_shredders()
try: try:
cf = CachedFile.objects.get(pk=fileid) cf = CachedFile.objects.get(pk=fileid)

View File

@@ -14,10 +14,12 @@ import time
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
from django_scopes import scope, scopes_disabled
from pretix.base.metrics import ( from pretix.base.metrics import (
pretix_task_duration_seconds, pretix_task_runs_total, pretix_task_duration_seconds, pretix_task_runs_total,
) )
from pretix.base.models import Event
from pretix.celery_app import app from pretix.celery_app import app
@@ -61,6 +63,30 @@ class ProfiledTask(app.Task):
return super().on_success(retval, task_id, args, kwargs) return super().on_success(retval, task_id, args, kwargs)
class EventTask(app.Task):
def __call__(self, *args, **kwargs):
if 'event_id' in kwargs:
event_id = kwargs.get('event_id')
with scopes_disabled():
event = Event.objects.select_related('organizer').get(pk=event_id)
del kwargs['event_id']
kwargs['event'] = event
else:
args = list(args)
event_id = args[0]
with scopes_disabled():
event = Event.objects.select_related('organizer').get(pk=event_id)
args[0] = event
with scope(organizer=event.organizer):
ret = super().__call__(*args, **kwargs)
return ret
class ProfiledEventTask(ProfiledTask, EventTask):
pass
class TransactionAwareTask(ProfiledTask): class TransactionAwareTask(ProfiledTask):
""" """
Task class which is aware of django db transactions and only executes tasks Task class which is aware of django db transactions and only executes tasks

View File

@@ -4,13 +4,14 @@ import os
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django_scopes import scopes_disabled
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import ( from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, Order, CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, Order,
OrderPosition, OrderPosition,
) )
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import EventTask, ProfiledTask
from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import allow_ticket_download, register_ticket_outputs from pretix.base.signals import allow_ticket_download, register_ticket_outputs
from pretix.celery_app import app from pretix.celery_app import app
@@ -57,10 +58,11 @@ def generate_order(order: int, provider: str):
@app.task(base=ProfiledTask) @app.task(base=ProfiledTask)
def generate(model: str, pk: int, provider: str): def generate(model: str, pk: int, provider: str):
if model == 'order': with scopes_disabled():
return generate_order(pk, provider) if model == 'order':
elif model == 'orderposition': return generate_order(pk, provider)
return generate_orderposition(pk, provider) elif model == 'orderposition':
return generate_orderposition(pk, provider)
class DummyRollbackException(Exception): class DummyRollbackException(Exception):
@@ -165,9 +167,8 @@ def get_tickets_for_order(order, base_position=None):
return tickets return tickets
@app.task(base=ProfiledTask) @app.task(base=EventTask)
def invalidate_cache(event: int, item: int=None, provider: str=None, order: int=None, **kwargs): def invalidate_cache(event: Event, item: int=None, provider: str=None, order: int=None, **kwargs):
event = Event.objects.get(id=event)
qs = CachedTicket.objects.filter(order_position__order__event=event) qs = CachedTicket.objects.filter(order_position__order__event=event)
qsc = CachedCombinedTicket.objects.filter(order__event=event) qsc = CachedCombinedTicket.objects.filter(order__event=event)

View File

@@ -6,6 +6,7 @@ import requests
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from django_scopes import scopes_disabled
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from pretix import __version__ from pretix import __version__
@@ -29,6 +30,7 @@ def run_update_check(sender, **kwargs):
@app.task @app.task
@scopes_disabled()
def update_check(): def update_check():
gs = GlobalSettingsObject() gs = GlobalSettingsObject()

View File

@@ -1,17 +1,17 @@
import sys import sys
from django.dispatch import receiver from django.dispatch import receiver
from django_scopes import scopes_disabled
from pretix.base.models import Event, User, WaitingListEntry from pretix.base.models import Event, User, WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException from pretix.base.models.waitinglist import WaitingListException
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import EventTask
from pretix.base.signals import periodic_task from pretix.base.signals import periodic_task
from pretix.celery_app import app from pretix.celery_app import app
@app.task(base=ProfiledTask) @app.task(base=EventTask)
def assign_automatically(event_id: int, user_id: int=None, subevent_id: int=None): def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None):
event = Event.objects.get(id=event_id)
if user_id: if user_id:
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
else: else:
@@ -69,6 +69,7 @@ def assign_automatically(event_id: int, user_id: int=None, subevent_id: int=None
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled()
def process_waitinglist(sender, **kwargs): def process_waitinglist(sender, **kwargs):
qs = Event.objects.filter( qs = Event.objects.filter(
live=True live=True

View File

@@ -7,6 +7,7 @@ from pretix.base.models import (
CachedFile, Event, OrderPosition, cachedfile_name, CachedFile, Event, OrderPosition, cachedfile_name,
) )
from pretix.base.services.orders import OrderError from pretix.base.services.orders import OrderError
from pretix.base.services.tasks import EventTask
from pretix.celery_app import app from pretix.celery_app import app
from .exporters import render_pdf from .exporters import render_pdf
@@ -14,8 +15,8 @@ from .exporters import render_pdf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@app.task(throws=(OrderError,)) @app.task(base=EventTask, throws=(OrderError,))
def badges_create_pdf(fileid: int, event: int, positions: List[int]) -> int: def badges_create_pdf(event: int, fileid: int, positions: List[int]) -> int:
file = CachedFile.objects.get(id=fileid) file = CachedFile.objects.get(id=fileid)
event = Event.objects.get(id=event) event = Event.objects.get(id=event)

View File

@@ -223,7 +223,7 @@ class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View):
else: else:
positions = [p.pk for p in order.positions.all()] positions = [p.pk for p in order.positions.all()]
return self.do( return self.do(
str(cf.id),
self.request.event.pk, self.request.event.pk,
str(cf.id),
positions, positions,
) )

View File

@@ -9,6 +9,7 @@ from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.translation import ugettext, ugettext_noop from django.utils.translation import ugettext, ugettext_noop
from django_scopes import scope, scopes_disabled
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import ( from pretix.base.models import (
@@ -194,51 +195,53 @@ def _get_unknown_transactions(job: BankImportJob, data: list, event: Event=None,
@app.task(base=TransactionAwareTask, bind=True, max_retries=5, default_retry_delay=1) @app.task(base=TransactionAwareTask, bind=True, max_retries=5, default_retry_delay=1)
def process_banktransfers(self, job: int, data: list) -> None: def process_banktransfers(self, job: int, data: list) -> None:
with language("en"): # We'll translate error messages at display time with language("en"): # We'll translate error messages at display time
job = BankImportJob.objects.get(pk=job) with scopes_disabled():
job.state = BankImportJob.STATE_RUNNING job = BankImportJob.objects.get(pk=job)
job.save() with scope(organizer=job.organizer or job.event.organizer):
prefixes = [] job.state = BankImportJob.STATE_RUNNING
job.save()
prefixes = []
try:
# Delete left-over transactions from a failed run before so they can reimported
BankTransaction.objects.filter(state=BankTransaction.STATE_UNCHECKED, **job.owner_kwargs).delete()
transactions = _get_unknown_transactions(job, data, **job.owner_kwargs)
code_len = settings.ENTROPY['order_code']
if job.event:
pattern = re.compile(job.event.slug.upper() + r"[ \-_]*([A-Z0-9]{%s})" % code_len)
else:
if not prefixes:
prefixes = [e.slug.upper().replace(".", r"\.").replace("-", r"[\- ]*")
for e in job.organizer.events.all()]
pattern = re.compile("(%s)[ \\-_]*([A-Z0-9]{%s})" % ("|".join(prefixes), code_len))
for trans in transactions:
match = pattern.search(trans.reference.replace(" ", "").replace("\n", "").upper())
if match:
if job.event:
code = match.group(1)
_handle_transaction(trans, code, event=job.event)
else:
slug = match.group(1)
code = match.group(2)
_handle_transaction(trans, code, organizer=job.organizer, slug=slug)
else:
trans.state = BankTransaction.STATE_NOMATCH
trans.save()
except LockTimeoutException:
try: try:
self.retry() # Delete left-over transactions from a failed run before so they can reimported
except MaxRetriesExceededError: BankTransaction.objects.filter(state=BankTransaction.STATE_UNCHECKED, **job.owner_kwargs).delete()
logger.exception('Maximum number of retries exceeded for task.')
transactions = _get_unknown_transactions(job, data, **job.owner_kwargs)
code_len = settings.ENTROPY['order_code']
if job.event:
pattern = re.compile(job.event.slug.upper() + r"[ \-_]*([A-Z0-9]{%s})" % code_len)
else:
if not prefixes:
prefixes = [e.slug.upper().replace(".", r"\.").replace("-", r"[\- ]*")
for e in job.organizer.events.all()]
pattern = re.compile("(%s)[ \\-_]*([A-Z0-9]{%s})" % ("|".join(prefixes), code_len))
for trans in transactions:
match = pattern.search(trans.reference.replace(" ", "").replace("\n", "").upper())
if match:
if job.event:
code = match.group(1)
_handle_transaction(trans, code, event=job.event)
else:
slug = match.group(1)
code = match.group(2)
_handle_transaction(trans, code, organizer=job.organizer, slug=slug)
else:
trans.state = BankTransaction.STATE_NOMATCH
trans.save()
except LockTimeoutException:
try:
self.retry()
except MaxRetriesExceededError:
logger.exception('Maximum number of retries exceeded for task.')
job.state = BankImportJob.STATE_ERROR
job.save()
except Exception as e:
job.state = BankImportJob.STATE_ERROR job.state = BankImportJob.STATE_ERROR
job.save() job.save()
except Exception as e: raise e
job.state = BankImportJob.STATE_ERROR else:
job.save() job.state = BankImportJob.STATE_COMPLETED
raise e job.save()
else:
job.state = BankImportJob.STATE_COMPLETED
job.save()

View File

@@ -5,15 +5,14 @@ from i18nfield.strings import LazyI18nString
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Event, InvoiceAddress, Order, User from pretix.base.models import Event, InvoiceAddress, Order, User
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import SendMailException, mail
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledEventTask
from pretix.celery_app import app from pretix.celery_app import app
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
@app.task(base=ProfiledTask) @app.task(base=ProfiledEventTask)
def send_mails(event: int, user: int, subject: dict, message: dict, orders: list, items: list, recipients: str) -> None: def send_mails(event: Event, user: int, subject: dict, message: dict, orders: list, items: list, recipients: str) -> None:
failures = [] failures = []
event = Event.objects.get(pk=event)
user = User.objects.get(pk=user) if user else None user = User.objects.get(pk=user) if user else None
orders = Order.objects.filter(pk__in=orders, event=event) orders = Order.objects.filter(pk__in=orders, event=event)
subject = LazyI18nString(subject) subject = LazyI18nString(subject)

View File

@@ -5,6 +5,7 @@ import stripe
from django.conf import settings from django.conf import settings
from pretix.base.models import Event from pretix.base.models import Event
from pretix.base.services.tasks import EventTask
from pretix.celery_app import app from pretix.celery_app import app
from pretix.multidomain.urlreverse import get_domain from pretix.multidomain.urlreverse import get_domain
from pretix.plugins.stripe.models import RegisteredApplePayDomain from pretix.plugins.stripe.models import RegisteredApplePayDomain
@@ -27,7 +28,7 @@ def get_stripe_account_key(prov):
return prov.settings.publishable_key return prov.settings.publishable_key
@app.task(max_retries=5, default_retry_delay=1) @app.task(base=EventTask, max_retries=5, default_retry_delay=1)
def stripe_verify_domain(event_id, domain): def stripe_verify_domain(event_id, domain):
from pretix.plugins.stripe.payment import StripeCC from pretix.plugins.stripe.payment import StripeCC
event = Event.objects.get(pk=event_id) event = Event.objects.get(pk=event_id)

View File

@@ -4,6 +4,7 @@ from django.conf import settings
from django.core.files.base import ContentFile, File from django.core.files.base import ContentFile, File
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django_scopes import scopes_disabled
from pretix.base.models import Event_SettingsStore, Organizer_SettingsStore from pretix.base.models import Event_SettingsStore, Organizer_SettingsStore
from pretix.base.settings import GlobalSettingsObject from pretix.base.settings import GlobalSettingsObject
@@ -15,6 +16,7 @@ from ...style import regenerate_css, regenerate_organizer_css
class Command(BaseCommand): class Command(BaseCommand):
help = "Re-generate all custom stylesheets and scripts" help = "Re-generate all custom stylesheets and scripts"
@scopes_disabled()
def handle(self, *args, **options): def handle(self, *args, **options):
for es in Organizer_SettingsStore.objects.filter(key="presale_css_file"): for es in Organizer_SettingsStore.objects.filter(key="presale_css_file"):
regenerate_organizer_css.apply_async(args=(es.object_id,)) regenerate_organizer_css.apply_async(args=(es.object_id,))

View File

@@ -11,9 +11,10 @@ from django.core.files.base import ContentFile
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.dispatch import Signal from django.dispatch import Signal
from django.templatetags.static import static as _static from django.templatetags.static import static as _static
from django_scopes import scope
from pretix.base.models import Event, Event_SettingsStore, Organizer from pretix.base.models import Event, Event_SettingsStore, Organizer
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledEventTask, ProfiledTask
from pretix.celery_app import app from pretix.celery_app import app
from pretix.multidomain.urlreverse import get_domain from pretix.multidomain.urlreverse import get_domain
from pretix.presale.signals import sass_postamble, sass_preamble from pretix.presale.signals import sass_postamble, sass_preamble
@@ -78,10 +79,8 @@ def compile_scss(object, file="main.scss", fonts=True):
return css, checksum return css, checksum
@app.task(base=ProfiledTask) @app.task(base=ProfiledEventTask)
def regenerate_css(event_id: int): def regenerate_css(event):
event = Event.objects.select_related('organizer').get(pk=event_id)
# main.scss # main.scss
css, checksum = compile_scss(event) css, checksum = compile_scss(event)
fname = 'pub/{}/{}/presale.{}.css'.format(event.organizer.slug, event.slug, checksum[:16]) fname = 'pub/{}/{}/presale.{}.css'.format(event.organizer.slug, event.slug, checksum[:16])
@@ -105,28 +104,29 @@ def regenerate_css(event_id: int):
def regenerate_organizer_css(organizer_id: int): def regenerate_organizer_css(organizer_id: int):
organizer = Organizer.objects.get(pk=organizer_id) organizer = Organizer.objects.get(pk=organizer_id)
# main.scss with scope(organizer=organizer):
css, checksum = compile_scss(organizer) # main.scss
fname = 'pub/{}/presale.{}.css'.format(organizer.slug, checksum[:16]) css, checksum = compile_scss(organizer)
if organizer.settings.get('presale_css_checksum', '') != checksum: fname = 'pub/{}/presale.{}.css'.format(organizer.slug, checksum[:16])
newname = default_storage.save(fname, ContentFile(css.encode('utf-8'))) if organizer.settings.get('presale_css_checksum', '') != checksum:
organizer.settings.set('presale_css_file', newname) newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
organizer.settings.set('presale_css_checksum', checksum) organizer.settings.set('presale_css_file', newname)
organizer.settings.set('presale_css_checksum', checksum)
# widget.scss # widget.scss
css, checksum = compile_scss(organizer, file='widget.scss', fonts=False) css, checksum = compile_scss(organizer, file='widget.scss', fonts=False)
fname = 'pub/{}/widget.{}.css'.format(organizer.slug, checksum[:16]) fname = 'pub/{}/widget.{}.css'.format(organizer.slug, checksum[:16])
if organizer.settings.get('presale_widget_css_checksum', '') != checksum: if organizer.settings.get('presale_widget_css_checksum', '') != checksum:
newname = default_storage.save(fname, ContentFile(css.encode('utf-8'))) newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
organizer.settings.set('presale_widget_css_file', newname) organizer.settings.set('presale_widget_css_file', newname)
organizer.settings.set('presale_widget_css_checksum', checksum) organizer.settings.set('presale_widget_css_checksum', checksum)
non_inherited_events = set(Event_SettingsStore.objects.filter( non_inherited_events = set(Event_SettingsStore.objects.filter(
object__organizer=organizer, key__in=affected_keys object__organizer=organizer, key__in=affected_keys
).values_list('object_id', flat=True)) ).values_list('object_id', flat=True))
for event in organizer.events.all(): for event in organizer.events.all():
if event.pk not in non_inherited_events: if event.pk not in non_inherited_events:
regenerate_css.apply_async(args=(event.pk,)) regenerate_css.apply_async(args=(event.pk,))
register_fonts = Signal() register_fonts = Signal()