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']