diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py
index 919c5bdce7..a1e723b796 100644
--- a/src/pretix/base/services/invoices.py
+++ b/src/pretix/base/services/invoices.py
@@ -26,6 +26,7 @@ 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
+from pretix.helpers.database import rolledback_transaction
@transaction.atomic
@@ -408,45 +409,38 @@ class DummyRollbackException(Exception):
def build_preview_invoice_pdf(event):
locale = event.settings.invoice_language
- pdf = None
if not locale or locale == '__user__':
locale = event.settings.locale
- try:
- with transaction.atomic(), language(locale):
- order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
- expires=now(), code="PREVIEW", total=119)
- invoice = Invoice(
- order=order, event=event, invoice_no="PREVIEW",
- date=date.today(), locale=locale
- )
- invoice.invoice_from = event.settings.get('invoice_address_from')
+ with rolledback_transaction(), language(locale):
+ order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
+ expires=now(), code="PREVIEW", total=119)
+ invoice = Invoice(
+ order=order, event=event, invoice_no="PREVIEW",
+ date=date.today(), locale=locale
+ )
+ invoice.invoice_from = event.settings.get('invoice_address_from')
- introductory = event.settings.get('invoice_introductory_text', as_type=LazyI18nString)
- additional = event.settings.get('invoice_additional_text', as_type=LazyI18nString)
- footer = event.settings.get('invoice_footer_text', as_type=LazyI18nString)
- payment = _("A payment provider specific text might appear here.")
+ introductory = event.settings.get('invoice_introductory_text', as_type=LazyI18nString)
+ additional = event.settings.get('invoice_additional_text', as_type=LazyI18nString)
+ footer = event.settings.get('invoice_footer_text', as_type=LazyI18nString)
+ payment = _("A payment provider specific text might appear here.")
- invoice.introductory_text = str(introductory).replace('\n', '
')
- invoice.additional_text = str(additional).replace('\n', '
')
- invoice.footer_text = str(footer)
- invoice.payment_provider_text = str(payment).replace('\n', '
')
- invoice.invoice_to = _("John Doe\n214th Example Street\n012345 Somecity")
- invoice.file = None
- invoice.save()
- invoice.lines.all().delete()
+ invoice.introductory_text = str(introductory).replace('\n', '
')
+ invoice.additional_text = str(additional).replace('\n', '
')
+ invoice.footer_text = str(footer)
+ invoice.payment_provider_text = str(payment).replace('\n', '
')
+ invoice.invoice_to = _("John Doe\n214th Example Street\n012345 Somecity")
+ invoice.file = None
+ invoice.save()
+ invoice.lines.all().delete()
- InvoiceLine.objects.create(
- invoice=invoice, description=_("Sample product A"),
- gross_value=119, tax_value=19,
- tax_rate=19
- )
- with tempfile.NamedTemporaryFile(suffix=".pdf") as f:
- _invoice_generate_german(invoice, f)
- f.seek(0)
- pdf = f.read()
- raise DummyRollbackException()
- except DummyRollbackException:
- return pdf
- else:
- raise Exception('Invalid state, should have rolled back.')
+ InvoiceLine.objects.create(
+ invoice=invoice, description=_("Sample product A"),
+ gross_value=119, tax_value=19,
+ tax_rate=19
+ )
+ with tempfile.NamedTemporaryFile(suffix=".pdf") as f:
+ _invoice_generate_german(invoice, f)
+ f.seek(0)
+ return f.read()
diff --git a/src/pretix/base/services/tickets.py b/src/pretix/base/services/tickets.py
index a24119811b..5135b958f2 100644
--- a/src/pretix/base/services/tickets.py
+++ b/src/pretix/base/services/tickets.py
@@ -11,6 +11,7 @@ from pretix.base.models import (
)
from pretix.base.signals import register_ticket_outputs
from pretix.celery import app
+from pretix.helpers.database import rolledback_transaction
@app.task
@@ -41,24 +42,17 @@ class DummyRollbackException(Exception):
def preview(event: int, provider: str):
event = Event.objects.get(id=event)
- res = None
- try:
- with transaction.atomic(), language(event.settings.locale):
- item = event.items.create(name=_("Sample product"), default_price=42.23)
+ with rolledback_transaction(), language(event.settings.locale):
+ item = event.items.create(name=_("Sample product"), default_price=42.23)
- order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
- expires=now(), code="PREVIEW1234", total=119)
+ order = event.orders.create(status=Order.STATUS_PENDING, datetime=now(),
+ expires=now(), code="PREVIEW1234", total=119)
- order.positions.create(item=item, attendee_name=_("John Doe"), price=item.default_price)
+ order.positions.create(item=item, attendee_name=_("John Doe"), price=item.default_price)
- responses = register_ticket_outputs.send(event)
- for receiver, response in responses:
- prov = response(event)
- if prov.identifier == provider:
- res = prov.generate(order)
- raise DummyRollbackException()
- except DummyRollbackException:
- return res
- else:
- raise Exception('Invalid state, should have rolled back.')
+ responses = register_ticket_outputs.send(event)
+ for receiver, response in responses:
+ prov = response(event)
+ if prov.identifier == provider:
+ return prov.generate(order)
diff --git a/src/pretix/helpers/database.py b/src/pretix/helpers/database.py
new file mode 100644
index 0000000000..ddd695a821
--- /dev/null
+++ b/src/pretix/helpers/database.py
@@ -0,0 +1,28 @@
+import contextlib
+
+from django.db import transaction
+
+
+class DummyRollbackException(Exception):
+ pass
+
+
+@contextlib.contextmanager
+def rolledback_transaction():
+ """
+ This context manager runs your code in a database transaction that will be rolled back in the end.
+ This can come in handy to simulate the effects of a database operation that you do not actually
+ want to perform.
+
+ Note that rollbacks are a very slow operation on most database backends. Also, long-running
+ transactions can slow down other operations currently running and you should not use this
+ in a place that is called frequently.
+ """
+ try:
+ with transaction.atomic():
+ yield
+ raise DummyRollbackException()
+ except DummyRollbackException:
+ pass
+ else:
+ raise Exception('Invalid state, should have rolled back.')