diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index 24cdca6bb4..ea4c4e951d 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -77,7 +77,8 @@ class CheckinSerializer(I18nAwareModelSerializer): class OrderDownloadsField(serializers.Field): def to_representation(self, instance: Order): if instance.status != Order.STATUS_PAID: - return [] + if instance.status != Order.STATUS_PENDING or instance.require_approval or not instance.event.settings.ticket_download_pending: + return [] request = self.context['request'] res = [] @@ -100,7 +101,8 @@ class OrderDownloadsField(serializers.Field): class PositionDownloadsField(serializers.Field): def to_representation(self, instance: OrderPosition): if instance.order.status != Order.STATUS_PAID: - return [] + if instance.order.status != Order.STATUS_PENDING or instance.order.require_approval or not instance.order.event.settings.ticket_download_pending: + return [] if instance.addon_to_id and not instance.order.event.settings.ticket_download_addons: return [] if not instance.item.admission and not instance.order.event.settings.ticket_download_nonadm: diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 43444369f5..0839f1c87c 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -420,6 +420,20 @@ class Order(LoggedModel): dl_date = dl_date.datetime(self.event) return dl_date + @property + def ticket_download_available(self): + return self.event.settings.ticket_download and ( + self.event.settings.ticket_download_date is None + or now() > self.ticket_download_date + ) and ( + self.status == Order.STATUS_PAID + or ( + (self.event.settings.ticket_download_pending or self.total == Decimal("0.00")) and + self.status == Order.STATUS_PENDING and + not self.require_approval + ) + ) + @property def payment_term_last(self): tz = pytz.timezone(self.event.settings.timezone) diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 0b77d5165c..8bb52c1e01 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -634,7 +634,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str], email_subject, email_template, email_context, log_entry, invoices=[invoice] if invoice and event.settings.invoice_email_attachment else [], - attach_tickets=(payment_provider == 'free') + attach_tickets=True ) except SendMailException: logger.exception('Order received email could not be sent') diff --git a/src/pretix/base/services/tickets.py b/src/pretix/base/services/tickets.py index ce06b9644c..5f2bb170b2 100644 --- a/src/pretix/base/services/tickets.py +++ b/src/pretix/base/services/tickets.py @@ -1,7 +1,6 @@ import logging import os from datetime import timedelta -from decimal import Decimal from django.core.files.base import ContentFile from django.utils.timezone import now @@ -155,11 +154,7 @@ def get_tickets_for_order(order): can_download = all([r for rr, r in allow_ticket_download.send(order.event, order=order)]) if not can_download: return [] - if order.status != Order.STATUS_PAID and order.total != Decimal("0.00"): - return [] - if (not order.event.settings.ticket_download - or (order.event.settings.ticket_download_date is not None - and now() < order.ticket_download_date)): + if not order.ticket_download_available: return [] providers = [ diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index fe0ec93670..0cb1da04a1 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -949,12 +949,14 @@ class TicketSettingsForm(SettingsForm): ticket_download_addons = forms.BooleanField( label=_("Offer to download tickets separately for add-on products"), required=False, - widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_ticket_download'}), ) ticket_download_nonadm = forms.BooleanField( label=_("Generate tickets for non-admission products"), required=False, - widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_ticket_download'}), + ) + ticket_download_pending = forms.BooleanField( + label=_("Offer to download tickets even before an order is paid"), + required=False, ) def prepare_fields(self): diff --git a/src/pretix/control/templates/pretixcontrol/event/tickets.html b/src/pretix/control/templates/pretixcontrol/event/tickets.html index 201662426f..ab7488875d 100644 --- a/src/pretix/control/templates/pretixcontrol/event/tickets.html +++ b/src/pretix/control/templates/pretixcontrol/event/tickets.html @@ -19,6 +19,7 @@ {% bootstrap_field form.ticket_download_date layout="control" %} {% bootstrap_field form.ticket_download_addons layout="control" %} {% bootstrap_field form.ticket_download_nonadm layout="control" %} + {% bootstrap_field form.ticket_download_pending layout="control" %} {% for provider in providers %}
diff --git a/src/pretix/presale/templates/pretixpresale/event/order.html b/src/pretix/presale/templates/pretixpresale/event/order.html index 914f812ff8..3af584f38f 100644 --- a/src/pretix/presale/templates/pretixpresale/event/order.html +++ b/src/pretix/presale/templates/pretixpresale/event/order.html @@ -84,28 +84,28 @@
{% endif %} - {% if order.status == 'p' and event.settings.ticket_download %} - {% if can_download and download_buttons %} -
- {% blocktrans trimmed %} - You can download your tickets using the buttons below. Please have your ticket ready when entering the event. - {% endblocktrans %} -
- {% if cart.positions|length > 1 and can_download_multi %} -

- {% trans "Download all tickets at once:" %} - {% for b in download_buttons %} - {% if b.multi %} - - {{ b.text }} - - {% endif %} - {% endfor %} -

- {% endif %} - {% elif not download_buttons and ticket_download_date %} + {% if can_download and download_buttons %} +
+ {% blocktrans trimmed %} + You can download your tickets using the buttons below. Please have your ticket ready when entering the event. + {% endblocktrans %} +
+ {% if cart.positions|length > 1 and can_download_multi %} +

+ {% trans "Download all tickets at once:" %} + {% for b in download_buttons %} + {% if b.multi %} + + {{ b.text }} + + {% endif %} + {% endfor %} +

+ {% endif %} + {% elif not download_buttons and ticket_download_date %} + {% if order.status == 'p' %}
{% blocktrans trimmed with date=ticket_download_date|date:"SHORT_DATE_FORMAT" %} You will be able to download your tickets here starting on {{ date }}. diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 4a27266ce3..dcf9b423d2 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -95,13 +95,7 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TemplateView): can_download = all([r for rr, r in allow_ticket_download.send(self.request.event, order=self.order)]) if self.request.event.settings.ticket_download_date: ctx['ticket_download_date'] = self.order.ticket_download_date - ctx['can_download'] = ( - can_download and self.request.event.settings.ticket_download - and ( - self.request.event.settings.ticket_download_date is None - or now() > self.order.ticket_download_date - ) and self.order.status == Order.STATUS_PAID - ) + ctx['can_download'] = can_download and self.order.ticket_download_available ctx['download_buttons'] = self.download_buttons ctx['cart'] = self.get_cart( answers=True, downloads=ctx['can_download'], @@ -682,12 +676,8 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View): return self.error(_('You requested an invalid ticket output type.')) if not self.order or ('position' in kwargs and not self.order_position): raise Http404(_('Unknown order code or not authorized to access this order.')) - if self.order.status != Order.STATUS_PAID: - return self.error(_('Order is not paid.')) - if (not self.request.event.settings.ticket_download - or (self.request.event.settings.ticket_download_date is not None - and now() < self.order.ticket_download_date)): - return self.error(_('Ticket download is not (yet) enabled.')) + if not self.order.ticket_download_available: + return self.error(_('Ticket download is not (yet) enabled for this order.')) if 'position' in kwargs and (self.order_position.addon_to and not self.request.event.settings.ticket_download_addons): return self.error(_('Ticket download is not enabled for add-on products.')) if 'position' in kwargs and (not self.order_position.item.admission and not self.request.event.settings.ticket_download_nonadm): diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 1213ba052d..d45a1e4e8b 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -283,6 +283,19 @@ def test_order_detail(token_client, organizer, event, order, item, taxrule, ques assert len(resp.data['downloads']) == 1 assert len(resp.data['positions'][0]['downloads']) == 1 + order.status = 'n' + order.save() + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/'.format(organizer.slug, event.slug, + order.code)) + assert len(resp.data['downloads']) == 0 + assert len(resp.data['positions'][0]['downloads']) == 0 + + event.settings.ticket_download_pending = True + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/'.format(organizer.slug, event.slug, + order.code)) + assert len(resp.data['downloads']) == 1 + assert len(resp.data['positions'][0]['downloads']) == 1 + @pytest.mark.django_db def test_payment_list(token_client, organizer, event, order): diff --git a/src/tests/presale/test_orders.py b/src/tests/presale/test_orders.py index f7c47d4430..5f9e30f9ad 100644 --- a/src/tests/presale/test_orders.py +++ b/src/tests/presale/test_orders.py @@ -306,6 +306,36 @@ class OrdersTest(TestCase): assert 'alert-success' in response.rendered_content assert self.order.invoices.exists() + def test_orders_download_pending(self): + self.event.settings.set('ticket_download', True) + del self.event.settings['ticket_download_date'] + + self.order.status = Order.STATUS_PENDING + self.order.save() + self.event.settings.set('ticket_download_pending', True) + response = self.client.get( + '/%s/%s/order/%s/%s/download/%d/testdummy' % (self.orga.slug, self.event.slug, self.order.code, + self.order.secret, self.ticket_pos.pk), + ) + assert response.status_code == 200 + + def test_orders_download_pending_only_approved(self): + self.event.settings.set('ticket_download', True) + del self.event.settings['ticket_download_date'] + + self.order.status = Order.STATUS_PENDING + self.order.require_approval = True + self.order.save() + self.event.settings.set('ticket_download_pending', True) + response = self.client.get( + '/%s/%s/order/%s/%s/download/%d/testdummy' % (self.orga.slug, self.event.slug, self.order.code, + self.order.secret, self.ticket_pos.pk), + ) + self.assertRedirects(response, + '/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, + self.order.secret), + target_status_code=200) + def test_orders_download(self): self.event.settings.set('ticket_download', True) del self.event.settings['ticket_download_date']