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.')