Compare commits

..

4 Commits

Author SHA1 Message Date
Raphael Michel
bc64109e11 Fix Pillow version 2021-03-14 12:26:41 +01:00
Raphael Michel
40018b0937 more stuff 2021-03-11 23:11:31 +01:00
Raphael Michel
cffcddaf26 Fix deprecation warnings 2021-03-11 22:47:44 +01:00
Raphael Michel
bd70a2e7bf Some changes 2021-03-11 22:47:44 +01:00
112 changed files with 1476 additions and 1730 deletions

View File

@@ -60,10 +60,7 @@ Here is the currently recommended set of commands::
CREATE INDEX CONCURRENTLY pretix_addidx_ia_company
ON pretixbase_invoiceaddress
USING gin (upper("company") gin_trgm_ops);
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_email_upper
ON public.pretixbase_orderposition (upper((attendee_email)::text));
CREATE INDEX CONCURRENTLY pretix_addidx_voucher_code_upper
ON public.pretixbase_voucher (upper((code)::text));
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_email_upper ON public.pretixbase_orderposition (upper((attendee_email)::text));
Also, if you use our ``pretix-shipping`` plugin::

View File

@@ -1,4 +1,4 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import gettext_lazy as _
class FullAccessSecurityProfile:
@@ -10,7 +10,7 @@ class FullAccessSecurityProfile:
class AllowListSecurityProfile:
allowlist = ()
allowlist = tuple()
def is_allowed(self, request):
key = (request.method, f"{request.resolver_match.namespace}:{request.resolver_match.url_name}")
@@ -95,8 +95,6 @@ class PretixPosSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:taxrule-list'),
('GET', 'api-v1:ticketlayout-list'),
('GET', 'api-v1:ticketlayoutitem-list'),
('GET', 'api-v1:badgelayout-list'),
('GET', 'api-v1:badgeitem-list'),
('GET', 'api-v1:order-list'),
('POST', 'api-v1:order-list'),
('GET', 'api-v1:order-detail'),

View File

@@ -309,7 +309,7 @@ class EventSerializer(I18nAwareModelSerializer):
# Item Meta properties
if item_meta_properties is not None:
current = list(event.item_meta_properties.all())
current = [imp for imp in event.item_meta_properties.all()]
for key, value in item_meta_properties.items():
prop = self.item_meta_props.get(key)
if prop in current:

View File

@@ -18,18 +18,18 @@ class FormFieldWrapperField(serializers.Field):
simple_mappings = (
(forms.DateField, serializers.DateField, ()),
(forms.TimeField, serializers.TimeField, ()),
(forms.SplitDateTimeField, serializers.DateTimeField, ()),
(forms.DateTimeField, serializers.DateTimeField, ()),
(forms.DateField, serializers.DateField, tuple()),
(forms.TimeField, serializers.TimeField, tuple()),
(forms.SplitDateTimeField, serializers.DateTimeField, tuple()),
(forms.DateTimeField, serializers.DateTimeField, tuple()),
(forms.DecimalField, serializers.DecimalField, ('max_digits', 'decimal_places', 'min_value', 'max_value')),
(forms.FloatField, serializers.FloatField, ()),
(forms.IntegerField, serializers.IntegerField, ()),
(forms.EmailField, serializers.EmailField, ()),
(forms.UUIDField, serializers.UUIDField, ()),
(forms.URLField, serializers.URLField, ()),
(forms.NullBooleanField, serializers.NullBooleanField, ()),
(forms.BooleanField, serializers.BooleanField, ()),
(forms.FloatField, serializers.FloatField, tuple()),
(forms.IntegerField, serializers.IntegerField, tuple()),
(forms.EmailField, serializers.EmailField, tuple()),
(forms.UUIDField, serializers.UUIDField, tuple()),
(forms.URLField, serializers.URLField, tuple()),
(forms.NullBooleanField, serializers.NullBooleanField, tuple()),
(forms.BooleanField, serializers.BooleanField, tuple()),
)

View File

@@ -8,9 +8,7 @@ from pretix.api.models import ApiCall, WebHookCall
from pretix.base.signals import periodic_task
from pretix.helpers.periodic import minimum_interval
register_webhook_events = Signal(
providing_args=[]
)
register_webhook_events = Signal()
"""
This signal is sent out to get all known webhook events. Receivers should return an
instance of a subclass of pretix.api.webhooks.WebhookEvent or a list of such

View File

@@ -1,7 +1,7 @@
import importlib
from django.apps import apps
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from rest_framework import routers
from pretix.api.views import cart
@@ -72,30 +72,32 @@ for app in apps.get_app_configs():
importlib.import_module(app.name + '.urls')
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/settings/$', organizer.OrganizerSettingsView.as_view(),
name="organizer.settings"),
url(r'^organizers/(?P<organizer>[^/]+)/giftcards/(?P<giftcard>[^/]+)/', include(giftcard_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/settings/$', event.EventSettingsView.as_view(),
name="event.settings"),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/teams/(?P<team>[^/]+)/', include(team_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/items/(?P<item>[^/]+)/', include(item_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/questions/(?P<question>[^/]+)/',
include(question_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/checkinlists/(?P<list>[^/]+)/',
include(checkinlist_router.urls)),
url(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/orders/(?P<order>[^/]+)/', include(order_router.urls)),
url(r"^oauth/authorize$", oauth.AuthorizationView.as_view(), name="authorize"),
url(r"^oauth/token$", oauth.TokenView.as_view(), name="token"),
url(r"^oauth/revoke_token$", oauth.RevokeTokenView.as_view(), name="revoke-token"),
url(r"^device/initialize$", device.InitializeView.as_view(), name="device.initialize"),
url(r"^device/update$", device.UpdateView.as_view(), name="device.update"),
url(r"^device/roll$", device.RollKeyView.as_view(), name="device.roll"),
url(r"^device/revoke$", device.RevokeKeyView.as_view(), name="device.revoke"),
url(r"^device/eventselection$", device.EventSelectionView.as_view(), name="device.eventselection"),
url(r"^upload$", upload.UploadView.as_view(), name="upload"),
url(r"^me$", user.MeView.as_view(), name="user.me"),
url(r"^version$", version.VersionView.as_view(), name="version"),
re_path(r'^', include(router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/', include(orga_router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/settings/$', organizer.OrganizerSettingsView.as_view(),
name="organizer.settings"),
re_path(r'^organizers/(?P<organizer>[^/]+)/giftcards/(?P<giftcard>[^/]+)/', include(giftcard_router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/settings/$', event.EventSettingsView.as_view(),
name="event.settings"),
re_path(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/', include(event_router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/teams/(?P<team>[^/]+)/', include(team_router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/items/(?P<item>[^/]+)/',
include(item_router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/questions/(?P<question>[^/]+)/',
include(question_router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/checkinlists/(?P<list>[^/]+)/',
include(checkinlist_router.urls)),
re_path(r'^organizers/(?P<organizer>[^/]+)/events/(?P<event>[^/]+)/orders/(?P<order>[^/]+)/',
include(order_router.urls)),
re_path(r"^oauth/authorize$", oauth.AuthorizationView.as_view(), name="authorize"),
re_path(r"^oauth/token$", oauth.TokenView.as_view(), name="token"),
re_path(r"^oauth/revoke_token$", oauth.RevokeTokenView.as_view(), name="revoke-token"),
re_path(r"^device/initialize$", device.InitializeView.as_view(), name="device.initialize"),
re_path(r"^device/update$", device.UpdateView.as_view(), name="device.update"),
re_path(r"^device/roll$", device.RollKeyView.as_view(), name="device.roll"),
re_path(r"^device/revoke$", device.RevokeKeyView.as_view(), name="device.revoke"),
re_path(r"^device/eventselection$", device.EventSelectionView.as_view(), name="device.eventselection"),
re_path(r"^upload$", upload.UploadView.as_view(), name="upload"),
re_path(r"^me$", user.MeView.as_view(), name="user.me"),
re_path(r"^version$", version.VersionView.as_view(), name="version"),
]

View File

@@ -53,8 +53,8 @@ class DeviceSerializer(serializers.ModelSerializer):
class InitializeView(APIView):
authentication_classes = ()
permission_classes = ()
authentication_classes = tuple()
permission_classes = tuple()
def post(self, request, format=None):
serializer = InitializationRequestSerializer(data=request.data)

View File

@@ -100,7 +100,7 @@ class NamePartsWidget(forms.MultiWidget):
if not isinstance(value, list):
value = self.decompress(value)
output = []
final_attrs = self.build_attrs(attrs or {})
final_attrs = self.build_attrs(attrs or dict())
if 'required' in final_attrs:
del final_attrs['required']
id_ = final_attrs.get('id', None)
@@ -122,8 +122,6 @@ class NamePartsWidget(forms.MultiWidget):
these_attrs.pop('data-no-required-attr', None)
these_attrs['autocomplete'] = (self.attrs.get('autocomplete', '') + ' ' + self.autofill_map.get(self.scheme['fields'][i][0], 'off')).strip()
these_attrs['data-size'] = self.scheme['fields'][i][2]
if len(self.widgets) > 1:
these_attrs['aria-label'] = self.scheme['fields'][i][1]
else:
these_attrs = final_attrs
output.append(widget.render(name + '_%s' % i, widget_value, these_attrs, renderer=renderer))
@@ -222,7 +220,7 @@ class WrappedPhonePrefixSelect(Select):
country_name = locale.territories.get(country_code)
if country_name:
choices.append((prefix, "{} {}".format(country_name, prefix)))
super().__init__(choices=sorted(choices, key=lambda item: item[1]), attrs={'aria-label': pgettext_lazy('phonenumber', 'International area code')})
super().__init__(choices=sorted(choices, key=lambda item: item[1]))
def render(self, name, value, *args, **kwargs):
return super().render(name, value or self.initial, *args, **kwargs)
@@ -245,10 +243,7 @@ class WrappedPhonePrefixSelect(Select):
class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
def __init__(self, attrs=None, initial=None):
attrs = {
'aria-label': pgettext_lazy('phonenumber', 'Phone number (without international area code)')
}
widgets = (WrappedPhonePrefixSelect(initial), forms.TextInput(attrs=attrs))
widgets = (WrappedPhonePrefixSelect(initial), forms.TextInput())
super(PhoneNumberPrefixWidget, self).__init__(widgets, attrs)
def render(self, name, value, attrs=None, renderer=None):

View File

@@ -445,7 +445,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
if self.invoice.custom_field:
story.append(Paragraph(
'{}: {}'.format(
bleach.clean(str(self.invoice.event.settings.invoice_address_custom_field), tags=[]).strip().replace('\n', '<br />\n'),
bleach.clean(self.invoice.event.settings.invoice_address_custom_field, tags=[]).strip().replace('\n', '<br />\n'),
bleach.clean(self.invoice.custom_field, tags=[]).strip().replace('\n', '<br />\n'),
),
self.stylesheet['Normal']

View File

@@ -1,6 +1,6 @@
# Generated by Django 2.1 on 2018-10-17 00:24
import jsonfallback.fields
from django.core.exceptions import ImproperlyConfigured
from django.db import migrations
from django_mysql.checks import mysql_connections
@@ -77,19 +77,19 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='cartposition',
name='attendee_name_parts',
field=jsonfallback.fields.FallbackJSONField(null=False, default=dict),
field=models.JSONField(null=False, default=dict),
preserve_default=False,
),
migrations.AddField(
model_name='orderposition',
name='attendee_name_parts',
field=jsonfallback.fields.FallbackJSONField(null=False, default=dict),
field=models.JSONField(null=False, default=dict),
preserve_default=False,
),
migrations.AddField(
model_name='invoiceaddress',
name='name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
preserve_default=False,
),
migrations.RunPython(set_attendee_name_parts, migrations.RunPython.noop)

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.1 on 2018-11-21 12:24
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -2,7 +2,6 @@
import django.db.models.deletion
import django.db.models.manager
import jsonfallback.fields
from django.db import migrations, models

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1 on 2019-01-12 15:12
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.5 on 2019-01-29 13:37
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.5 on 2019-02-01 15:27
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -3,7 +3,6 @@
from decimal import Decimal
import django.db.models.deletion
import jsonfallback.fields
from django.conf import settings
from django.core.cache import cache
from django.db import migrations, models
@@ -190,7 +189,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cartposition',
name='attendee_name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='cartposition',
@@ -210,7 +209,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='invoiceaddress',
name='name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='item',
@@ -225,7 +224,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='orderposition',
name='attendee_name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='orderposition',

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1 on 2019-02-08 14:32
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.5 on 2019-02-19 12:45
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.5 on 2019-02-19 09:49
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.5 on 2019-03-12 09:42
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.7 on 2019-03-16 10:14
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -3,7 +3,6 @@
from decimal import Decimal
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.1.5 on 2019-04-02 07:22
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.2 on 2019-04-23 08:39
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.2 on 2019-05-09 06:54
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields

View File

@@ -1,7 +1,6 @@
# Generated by Django 2.2 on 2019-05-09 07:36
import django.db.models.deletion
import jsonfallback.fields
from django.db import migrations, models
import pretix.base.models.fields
@@ -17,7 +16,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cartposition',
name='attendee_name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='cartposition',
@@ -37,7 +36,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='invoiceaddress',
name='name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='item',
@@ -52,7 +51,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='orderposition',
name='attendee_name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='orderposition',

View File

@@ -2,7 +2,6 @@
import django.db.models.deletion
import django_countries.fields
import jsonfallback.fields
from django.db import migrations, models
import pretix.helpers.countries
@@ -43,7 +42,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='checkinlist',
name='rules',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AlterUniqueTogether(
name='checkin',

View File

@@ -1,6 +1,6 @@
# Generated by Django 3.0.10 on 2021-03-01 15:10
import jsonfallback.fields
import phonenumber_field.modelfields
from django.db import migrations, models
@@ -20,7 +20,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='waitinglistentry',
name='name_parts',
field=jsonfallback.fields.FallbackJSONField(default=dict),
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='waitinglistentry',

View File

@@ -1,49 +0,0 @@
# Generated by Django 3.0.10 on 2021-03-11 16:53
from django.db import migrations
def clean_duplicates(apps, schema_editor):
while True:
delete_options = """
DELETE
FROM pretixbase_questionanswer_options
WHERE questionanswer_id IN (
SELECT MIN(qa.id)
FROM pretixbase_questionanswer qa
GROUP BY qa.cartposition_id, qa.orderposition_id, qa.question_id
HAVING COUNT(*) > 1
);
"""
delete_answers = """
DELETE
FROM pretixbase_questionanswer
WHERE pretixbase_questionanswer.id IN (
SELECT MIN(qa.id)
FROM pretixbase_questionanswer qa
GROUP BY qa.cartposition_id, qa.orderposition_id, qa.question_id
HAVING COUNT(*) > 1
);
"""
with schema_editor.connection.cursor() as cursor:
cursor.execute(delete_options)
cursor.execute(delete_answers)
if cursor.rowcount == 0:
return
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0178_auto_20210308_1326'),
]
operations = [
migrations.RunPython(
clean_duplicates,
migrations.RunPython.noop,
),
migrations.AlterUniqueTogether(
name='questionanswer',
unique_together={('orderposition', 'question'), ('cartposition', 'question')},
),
]

View File

@@ -4,7 +4,6 @@ from django.db.models import Exists, F, Max, OuterRef, Q, Subquery
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes import ScopedManager, scopes_disabled
from jsonfallback.fields import FallbackJSONField
from pretix.base.models import LoggedModel
from pretix.base.models.fields import MultiStringField
@@ -47,7 +46,7 @@ class CheckinList(LoggedModel):
'any of the selected sales channels. This option can be useful when tickets sold at the box office '
'are not checked again before entry and should be considered validated directly upon purchase.')
)
rules = FallbackJSONField(default=dict, blank=True)
rules = models.JSONField(default=dict, blank=True)
objects = ScopedManager(organizer='event__organizer')

View File

@@ -29,7 +29,6 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_countries.fields import Country
from django_scopes import ScopedManager, scopes_disabled
from i18nfield.strings import LazyI18nString
from jsonfallback.fields import FallbackJSONField
from phonenumber_field.modelfields import PhoneNumberField
from phonenumber_field.phonenumber import PhoneNumber
from phonenumbers import NumberParseException
@@ -983,9 +982,6 @@ class QuestionAnswer(models.Model):
objects = ScopedManager(organizer='question__event__organizer')
class Meta:
unique_together = [['orderposition', 'question'], ['cartposition', 'question']]
@property
def backend_file_url(self):
if self.file:
@@ -1135,7 +1131,7 @@ class AbstractPosition(models.Model):
blank=True, null=True,
help_text=_("Empty, if this product is not an admission ticket")
)
attendee_name_parts = FallbackJSONField(
attendee_name_parts = models.JSONField(
blank=True, default=dict
)
attendee_email = models.EmailField(
@@ -2255,7 +2251,7 @@ class InvoiceAddress(models.Model):
is_business = models.BooleanField(default=False, verbose_name=_('Business customer'))
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'))
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
name_parts = FallbackJSONField(default=dict)
name_parts = models.JSONField(default=dict)
street = models.TextField(verbose_name=_('Address'), blank=False)
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False)
city = models.CharField(max_length=255, verbose_name=_('City'), blank=False)

View File

@@ -5,7 +5,6 @@ from django.db import models, transaction
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes import ScopedManager
from jsonfallback.fields import FallbackJSONField
from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.email import get_email_context
@@ -45,7 +44,7 @@ class WaitingListEntry(LoggedModel):
verbose_name=_("Name"),
blank=True, null=True,
)
name_parts = FallbackJSONField(
name_parts = models.JSONField(
blank=True, default=dict
)
email = models.EmailField(

View File

@@ -241,7 +241,7 @@ class CartManager:
raise CartError(_(error_messages['max_items']) % (self.event.settings.max_items_per_order,))
def _check_item_constraints(self, op, current_ops=[]):
if isinstance(op, (self.AddOperation, self.ExtendOperation)):
if isinstance(op, self.AddOperation) or isinstance(op, self.ExtendOperation):
if not (
(isinstance(op, self.AddOperation) and op.addon_to == 'FAKE') or
(isinstance(op, self.ExtendOperation) and op.position.is_bundled)
@@ -863,7 +863,7 @@ class CartManager:
op.position.addons.all().delete()
op.position.delete()
elif isinstance(op, (self.AddOperation, self.ExtendOperation)):
elif isinstance(op, self.AddOperation) or isinstance(op, self.ExtendOperation):
# Create a CartPosition for as much items as we can
requested_count = quota_available_count = voucher_available_count = op.count

View File

@@ -3,21 +3,22 @@ from i18nfield.strings import LazyI18nString
from pretix.base.email import get_email_context
from pretix.base.i18n import language
from pretix.base.models import Event, LogEntry, User, Voucher
from pretix.base.models import Event, User, Voucher
from pretix.base.services.mail import mail
from pretix.base.services.tasks import TransactionAwareProfiledEventTask
from pretix.celery_app import app
def vouchers_send(event: Event, vouchers: list, subject: str, message: str, recipients: list, user: int,
progress=None) -> None:
@app.task(base=TransactionAwareProfiledEventTask, acks_late=True)
def vouchers_send(event: Event, vouchers: list, subject: str, message: str, recipients: list, user: int) -> None:
vouchers = list(Voucher.objects.filter(id__in=vouchers).order_by('id'))
user = User.objects.get(pk=user)
for ir, r in enumerate(recipients):
for r in recipients:
voucher_list = []
for i in range(r['number']):
voucher_list.append(vouchers.pop())
with language(event.settings.locale):
email_context = get_email_context(event=event, name=r.get('name') or '',
voucher_list=[v.code for v in voucher_list])
email_context = get_email_context(event=event, name=r.get('name') or '', voucher_list=[v.code for v in voucher_list])
mail(
r['email'],
subject,
@@ -26,14 +27,14 @@ def vouchers_send(event: Event, vouchers: list, subject: str, message: str, reci
event,
locale=event.settings.locale,
)
logs = []
for v in voucher_list:
if r.get('tag') and r.get('tag') != v.tag:
v.tag = r.get('tag')
if v.comment:
v.comment += '\n\n'
v.comment = gettext('The voucher has been sent to {recipient}.').format(recipient=r['email'])
logs.append(v.log_action(
v.save(update_fields=['tag', 'comment'])
v.log_action(
'pretix.voucher.sent',
user=user,
data={
@@ -41,11 +42,5 @@ def vouchers_send(event: Event, vouchers: list, subject: str, message: str, reci
'name': r.get('name'),
'subject': subject,
'message': message,
},
save=False
))
Voucher.objects.bulk_update(voucher_list, fields=['comment', 'tag'], batch_size=500)
LogEntry.objects.bulk_create(logs, batch_size=500)
if progress and ir % 50 == 0:
progress(ir / len(recipients))
}
)

View File

@@ -1799,7 +1799,7 @@ Your {event} team"""))
),
},
'theme_color_danger': {
'default': '#C44F4F',
'default': '#D36060',
'type': str,
'form_class': forms.CharField,
'serializer_class': serializers.CharField,

View File

@@ -164,9 +164,7 @@ class DeprecatedSignal(django.dispatch.Signal):
super().connect(receiver, sender=None, weak=True, dispatch_uid=None)
event_live_issues = EventPluginSignal(
providing_args=[]
)
event_live_issues = EventPluginSignal()
"""
This signal is sent out to determine whether an event can be taken live. If you want to
prevent the event from going live, return a string that will be displayed to the user
@@ -176,9 +174,7 @@ As with all event-plugin signals, the ``sender`` keyword argument will contain t
"""
register_payment_providers = EventPluginSignal(
providing_args=[]
)
register_payment_providers = EventPluginSignal()
"""
This signal is sent out to get all known payment providers. Receivers should return a
subclass of pretix.base.payment.BasePaymentProvider or a list of these
@@ -186,9 +182,7 @@ subclass of pretix.base.payment.BasePaymentProvider or a list of these
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_mail_placeholders = EventPluginSignal(
providing_args=[]
)
register_mail_placeholders = EventPluginSignal()
"""
This signal is sent out to get all known email text placeholders. Receivers should return
an instance of a subclass of pretix.base.email.BaseMailTextPlaceholder or a list of these.
@@ -196,9 +190,7 @@ an instance of a subclass of pretix.base.email.BaseMailTextPlaceholder or a list
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_html_mail_renderers = EventPluginSignal(
providing_args=[]
)
register_html_mail_renderers = EventPluginSignal()
"""
This signal is sent out to get all known HTML email renderers. Receivers should return a
subclass of pretix.base.email.BaseHTMLMailRenderer or a list of these
@@ -206,9 +198,7 @@ subclass of pretix.base.email.BaseHTMLMailRenderer or a list of these
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_invoice_renderers = EventPluginSignal(
providing_args=[]
)
register_invoice_renderers = EventPluginSignal()
"""
This signal is sent out to get all known invoice renderers. Receivers should return a
subclass of pretix.base.invoice.BaseInvoiceRenderer or a list of these
@@ -216,9 +206,7 @@ subclass of pretix.base.invoice.BaseInvoiceRenderer or a list of these
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_ticket_secret_generators = EventPluginSignal(
providing_args=[]
)
register_ticket_secret_generators = EventPluginSignal()
"""
This signal is sent out to get all known ticket secret generators. Receivers should return a
subclass of ``pretix.base.secrets.BaseTicketSecretGenerator`` or a list of these
@@ -226,9 +214,7 @@ subclass of ``pretix.base.secrets.BaseTicketSecretGenerator`` or a list of these
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_data_shredders = EventPluginSignal(
providing_args=[]
)
register_data_shredders = EventPluginSignal()
"""
This signal is sent out to get all known data shredders. Receivers should return a
subclass of pretix.base.shredder.BaseDataShredder or a list of these
@@ -236,9 +222,7 @@ subclass of pretix.base.shredder.BaseDataShredder or a list of these
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_ticket_outputs = EventPluginSignal(
providing_args=[]
)
register_ticket_outputs = EventPluginSignal()
"""
This signal is sent out to get all known ticket outputs. Receivers should return a
subclass of pretix.base.ticketoutput.BaseTicketOutput
@@ -246,9 +230,7 @@ subclass of pretix.base.ticketoutput.BaseTicketOutput
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_notification_types = EventPluginSignal(
providing_args=[]
)
register_notification_types = EventPluginSignal()
"""
This signal is sent out to get all known notification types. Receivers should return an
instance of a subclass of pretix.base.notifications.NotificationType or a list of such
@@ -259,18 +241,14 @@ however for this signal, the ``sender`` **may also be None** to allow creating t
notification settings!
"""
register_sales_channels = django.dispatch.Signal(
providing_args=[]
)
register_sales_channels = django.dispatch.Signal()
"""
This signal is sent out to get all known sales channels types. Receivers should return an
instance of a subclass of ``pretix.base.channels.SalesChannel`` or a list of such
instances.
"""
register_data_exporters = EventPluginSignal(
providing_args=[]
)
register_data_exporters = EventPluginSignal()
"""
This signal is sent out to get all known data exporters. Receivers should return a
subclass of pretix.base.exporter.BaseExporter
@@ -278,21 +256,20 @@ subclass of pretix.base.exporter.BaseExporter
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
register_multievent_data_exporters = django.dispatch.Signal(
providing_args=["event"]
)
register_multievent_data_exporters = django.dispatch.Signal()
"""
Arguments: ``event``
This signal is sent out to get all known data exporters, which support exporting data for
multiple events. Receivers should return a subclass of pretix.base.exporter.BaseExporter
The ``sender`` keyword argument will contain an organizer.
"""
validate_order = EventPluginSignal(
providing_args=["payment_provider", "positions", "email", "locale", "invoice_address",
"meta_info"]
)
validate_order = EventPluginSignal()
"""
Arguments: ``payment_provider``, ``positions``, ``email``, ``locale``, ``invoice_address``, ``meta_info``
This signal is sent out when the user tries to confirm the order, before we actually create
the order. It allows you to inspect the cart positions. Your return value will be ignored,
but you can raise an OrderError with an appropriate exception message if you like to block
@@ -301,10 +278,10 @@ the order. We strongly discourage making changes to the order here.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
validate_cart = EventPluginSignal(
providing_args=["positions"]
)
validate_cart = EventPluginSignal()
"""
Arguments: ``positions``
This signal is sent out before the user starts checkout. It includes an iterable
with the current CartPosition objects.
The response of receivers will be ignored, but you can raise a CartError with an
@@ -313,10 +290,10 @@ appropriate exception message.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
validate_cart_addons = EventPluginSignal(
providing_args=["addons", "base_position", "iao"]
)
validate_cart_addons = EventPluginSignal()
"""
Arguments: ``addons``, ``base_position``, ``iao``
This signal is sent when a user tries to select a combination of addons. In contrast to
``validate_cart``, this is executed before the cart is actually modified. You are passed
an argument ``addons`` containing a dict of ``(item, variation or None) → count`` tuples as well
@@ -328,10 +305,10 @@ appropriate exception message.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_placed = EventPluginSignal(
providing_args=["order"]
)
order_placed = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order is placed. The order object is given
as the first argument. This signal is *not* sent out if an order is created through
splitting an existing order, so you can not expect to see all orders by listening
@@ -340,10 +317,10 @@ to this signal.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_paid = EventPluginSignal(
providing_args=["order"]
)
order_paid = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order is paid. The order object is given
as the first argument. This signal is *not* sent out if an order is marked as paid
because an already-paid order has been split.
@@ -351,80 +328,80 @@ because an already-paid order has been split.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_canceled = EventPluginSignal(
providing_args=["order"]
)
order_canceled = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order is canceled. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_reactivated = EventPluginSignal(
providing_args=["order"]
)
order_reactivated = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time a canceled order is reactivated. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_expired = EventPluginSignal(
providing_args=["order"]
)
order_expired = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order is marked as expired. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_modified = EventPluginSignal(
providing_args=["order"]
)
order_modified = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order's information is modified. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_changed = EventPluginSignal(
providing_args=["order"]
)
order_changed = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order's content is changed. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_approved = EventPluginSignal(
providing_args=["order"]
)
order_approved = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order is being approved. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_denied = EventPluginSignal(
providing_args=["order"]
)
order_denied = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time an order is being denied. The order object is given
as the first argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_gracefully_delete = EventPluginSignal(
providing_args=["order"]
)
order_gracefully_delete = EventPluginSignal()
"""
Arguments: ``order``
This signal is sent out every time a test-mode order is being deleted. The order object
is given as the first argument.
@@ -435,10 +412,10 @@ the deletion of the order.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
checkin_created = EventPluginSignal(
providing_args=["checkin"],
)
checkin_created = EventPluginSignal()
"""
Arguments: ``checkin``
This signal is sent out every time a check-in is created (i.e. an order position is marked as
checked in). It is not send if the position was already checked in and is force-checked-in a second time.
The check-in object is given as the first argument
@@ -446,10 +423,10 @@ The check-in object is given as the first argument
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
logentry_display = EventPluginSignal(
providing_args=["logentry"]
)
logentry_display = EventPluginSignal()
"""
Arguments: ``logentry``
To display an instance of the ``LogEntry`` model to a human user,
``pretix.base.signals.logentry_display`` will be sent out with a ``logentry`` argument.
@@ -459,10 +436,10 @@ to the user. The receivers are expected to return plain text.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
logentry_object_link = EventPluginSignal(
providing_args=["logentry"]
)
logentry_object_link = EventPluginSignal()
"""
Arguments: ``logentry``
To display the relationship of an instance of the ``LogEntry`` model to another model
to a human user, ``pretix.base.signals.logentry_object_link`` will be sent out with a
``logentry`` argument.
@@ -487,10 +464,10 @@ Make sure that any user content in the HTML code you return is properly escaped!
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
requiredaction_display = EventPluginSignal(
providing_args=["action", "request"]
)
requiredaction_display = EventPluginSignal()
"""
Arguments: ``action``, ``request``
To display an instance of the ``RequiredAction`` model to a human user,
``pretix.base.signals.requiredaction_display`` will be sent out with a ``action`` argument.
You will also get the current ``request`` in a different argument.
@@ -501,10 +478,10 @@ to the user. The receivers are expected to return HTML code.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
event_copy_data = EventPluginSignal(
providing_args=["other", "tax_map", "category_map", "item_map", "question_map", "variation_map", "checkin_list_map"]
)
event_copy_data = EventPluginSignal()
"""
Arguments: "other", ``tax_map``, ``category_map``, ``item_map``, ``question_map``, ``variation_map``, ``checkin_list_map``
This signal is sent out when a new event is created as a clone of an existing event, i.e.
the settings from the older event are copied to the newer one. You can listen to this
signal to copy data or configuration stored within your plugin's models as well.
@@ -519,10 +496,10 @@ keyword argument will contain the event to **copy from**. The keyword arguments
in the new event of the respective types.
"""
item_copy_data = EventPluginSignal(
providing_args=["source", "target"]
)
item_copy_data = EventPluginSignal()
"""
Arguments: ``source``, ``target``
This signal is sent out when a new product is created as a clone of an existing product, i.e.
the settings from the older product are copied to the newer one. You can listen to this
signal to copy data or configuration stored within your plugin's models as well.
@@ -546,10 +523,10 @@ All plugins that are installed may send fields for the global settings form, as
an OrderedDict of (setting name, form field).
"""
order_fee_calculation = EventPluginSignal(
providing_args=['positions', 'invoice_address', 'meta_info', 'total', 'gift_cards']
)
order_fee_calculation = EventPluginSignal()
"""
Arguments: 'positions', 'invoice_address', 'meta_info', 'total', 'gift_cards'
This signals allows you to add fees to an order while it is being created. You are expected to
return a list of ``OrderFee`` objects that are not yet saved to the database
(because there is no order yet).
@@ -562,10 +539,10 @@ keyword argument will contain the total cart sum without any fees. You should no
the gift cards in use.
"""
order_fee_type_name = EventPluginSignal(
providing_args=['request', 'fee']
)
order_fee_type_name = EventPluginSignal()
"""
Arguments: 'request', 'fee'
This signals allows you to return a human-readable description for a fee type based on the ``fee_type``
and ``internal_type`` attributes of the ``OrderFee`` model that you get as keyword arguments. You are
expected to return a string or None, if you don't know about this fee.
@@ -573,20 +550,20 @@ expected to return a string or None, if you don't know about this fee.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
allow_ticket_download = EventPluginSignal(
providing_args=['order']
)
allow_ticket_download = EventPluginSignal()
"""
Arguments: 'order'
This signal is sent out to check if tickets for an order can be downloaded. If any receiver returns false,
a download will not be offered.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
email_filter = EventPluginSignal(
providing_args=['message', 'order', 'user']
)
email_filter = EventPluginSignal()
"""
Arguments: 'message', 'order', 'user'
This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to
return a (possibly modified) copy of the message object passed to you.
@@ -598,10 +575,10 @@ If the email is associated with a specific user, e.g. a notification email, the
well, otherwise it will be ``None``.
"""
global_email_filter = GlobalSignal(
providing_args=['message', 'order', 'user']
)
global_email_filter = GlobalSignal()
"""
Arguments: 'message', 'order', 'user'
This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to
return a (possibly modified) copy of the message object passed to you.
@@ -666,10 +643,10 @@ a ``subevent`` argument which might be none and you are expected to return a lis
"""
quota_availability = EventPluginSignal(
providing_args=['quota', 'result', 'count_waitinglist']
)
quota_availability = EventPluginSignal()
"""
Arguments: 'quota', 'result', 'count_waitinglist'
This signal allows you to modify the availability of a quota. You are passed the ``quota`` and an
``availability`` result calculated by pretix code or other plugins. ``availability`` is a tuple
with the first entry being one of the ``Quota.AVAILABILITY_*`` constants and the second entry being
@@ -682,25 +659,23 @@ system really bad.** Also, keep in mind that your response is subject to caching
quotas might be used for display (not for actual order processing).
"""
order_split = EventPluginSignal(
providing_args=["original", "split_order"]
)
order_split = EventPluginSignal()
"""
Arguments: ``original``, ``split_order``
This signal is sent out when an order is split into two orders and allows you to copy related models
to the new order. You will be passed the old order as ``original`` and the new order as ``split_order``.
"""
invoice_line_text = EventPluginSignal(
providing_args=["position"]
)
invoice_line_text = EventPluginSignal()
"""
Arguments: ``position``
This signal is sent out when an invoice is built for an order. You can return additional text that
should be shown on the invoice for the given ``position``.
"""
order_import_columns = EventPluginSignal(
providing_args=[]
)
order_import_columns = EventPluginSignal()
"""
This signal is sent out if the user performs an import of orders from an external source. You can use this
to define additional columns that can be read during import. You are expected to return a list of instances of
@@ -709,10 +684,10 @@ to define additional columns that can be read during import. You are expected to
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
validate_event_settings = EventPluginSignal(
providing_args=["settings_dict"]
)
validate_event_settings = EventPluginSignal()
"""
Arguments: ``settings_dict``
This signal is sent out if the user performs an update of event settings through the API or web interface.
You are passed a ``settings_dict`` dictionary with the new state of the event settings object and are expected
to raise a ``django.core.exceptions.ValidationError`` if the new state is not valid.
@@ -723,9 +698,7 @@ serializer field instead.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
api_event_settings_fields = EventPluginSignal(
providing_args=[]
)
api_event_settings_fields = EventPluginSignal()
"""
This signal is sent out to collect serializable settings fields for the API. You are expected to
return a dictionary mapping names of attributes in the settings store to DRF serializer field instances.

View File

@@ -11,7 +11,7 @@ register = template.Library()
@register.filter("money")
def money_filter(value: Decimal, arg='', hide_currency=False):
if isinstance(value, (float, int)):
if isinstance(value, float) or isinstance(value, int):
value = Decimal(value)
if not isinstance(value, Decimal):
if value == '':
@@ -47,7 +47,7 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
@register.filter("money_numberfield")
def money_numberfield_filter(value: Decimal, arg=''):
if isinstance(value, (float, int)):
if isinstance(value, float) or isinstance(value, int):
value = Decimal(value)
if not isinstance(value, Decimal):
raise TypeError("Invalid data type passed to money filter: %r" % type(value))

View File

@@ -153,9 +153,9 @@ def markdown_compile_email(source):
class SnippetExtension(markdown.extensions.Extension):
def extendMarkdown(self, md, *args, **kwargs):
del md.parser.blockprocessors['olist']
del md.parser.blockprocessors['ulist']
del md.parser.blockprocessors['quote']
md.parser.blockprocessors.deregister('olist')
md.parser.blockprocessors.deregister('ulist')
md.parser.blockprocessors.deregister('quote')
def markdown_compile(source, snippet=False):

View File

@@ -48,7 +48,7 @@ def page_not_found(request, exception):
except (AttributeError, IndexError):
pass
else:
if isinstance(message, (str, Promise)):
if isinstance(message, str) or isinstance(message, Promise):
exception_repr = str(message)
context = {
'request_path': request.path,

View File

@@ -4,25 +4,43 @@ import celery.exceptions
from celery.result import AsyncResult
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.test import RequestFactory
from django.utils.translation import gettext as _
from django.views.generic import FormView
from pretix.base.models import User
from pretix.base.services.tasks import ProfiledEventTask
from pretix.celery_app import app
logger = logging.getLogger('pretix.base.tasks')
class AsyncMixin:
class AsyncAction:
task = None
success_url = None
error_url = None
known_errortypes = []
def do(self, *args, **kwargs):
if not isinstance(self.task, app.Task):
raise TypeError('Method has no task attached')
try:
res = self.task.apply_async(args=args, kwargs=kwargs)
except ConnectionError:
# Task very likely not yet sent, due to redis restarting etc. Let's try once agan
res = self.task.apply_async(args=args, kwargs=kwargs)
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
data = self._return_ajax_result(res)
data['check_url'] = self.get_check_url(res.id, True)
return JsonResponse(data)
else:
if res.ready():
if res.successful() and not isinstance(res.info, Exception):
return self.success(res.info)
else:
return self.error(res.info)
return redirect(self.get_check_url(res.id, False))
def get_success_url(self, value):
return self.success_url
@@ -32,6 +50,11 @@ class AsyncMixin:
def get_check_url(self, task_id, ajax):
return self.request.path + '?async_id=%s' % task_id + ('&ajax=1' if ajax else '')
def get(self, request, *args, **kwargs):
if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request)
return self.http_method_not_allowed(request)
def _ajax_response_data(self):
return {}
@@ -63,7 +86,7 @@ class AsyncMixin:
if smes:
messages.success(self.request, smes)
# TODO: Do not store message if the ajax client states that it will not redirect
# but handle the message itself
# but handle the mssage itself
data.update({
'redirect': self.get_success_url(res.info),
'success': True,
@@ -72,7 +95,7 @@ class AsyncMixin:
else:
messages.error(self.request, self.get_error_message(res.info))
# TODO: Do not store message if the ajax client states that it will not redirect
# but handle the message itself
# but handle the mssage itself
data.update({
'redirect': self.get_error_url(),
'success': False,
@@ -136,124 +159,3 @@ class AsyncMixin:
def get_success_message(self, value):
return _('The task has been completed.')
class AsyncAction(AsyncMixin):
task = None
def do(self, *args, **kwargs):
if not isinstance(self.task, app.Task):
raise TypeError('Method has no task attached')
try:
res = self.task.apply_async(args=args, kwargs=kwargs)
except ConnectionError:
# Task very likely not yet sent, due to redis restarting etc. Let's try once again
res = self.task.apply_async(args=args, kwargs=kwargs)
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
data = self._return_ajax_result(res)
data['check_url'] = self.get_check_url(res.id, True)
return JsonResponse(data)
else:
if res.ready():
if res.successful() and not isinstance(res.info, Exception):
return self.success(res.info)
else:
return self.error(res.info)
return redirect(self.get_check_url(res.id, False))
def get(self, request, *args, **kwargs):
if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request)
return self.http_method_not_allowed(request)
class AsyncFormView(AsyncMixin, FormView):
"""
FormView variant in which instead of ``form_valid``, an ``async_form_valid``
is executed in a celery task. Note that this places some severe limitations
on the form and the view, e.g. neither ``get_form*`` nor the form itself
may depend on the request object unless specifically supported by this class.
Also, all form keyword arguments except ``instance`` need to be serializable.
"""
known_errortypes = ['ValidationError']
def __init_subclass__(cls):
def async_execute(self, request_path, form_kwargs, organizer=None, event=None, user=None):
view_instance = cls()
view_instance.request = RequestFactory().post(request_path)
if organizer:
view_instance.request.event = event
if organizer:
view_instance.request.organizer = organizer
if user:
view_instance.request.user = User.objects.get(pk=user)
form_class = view_instance.get_form_class()
if form_kwargs.get('instance'):
cls.model.objects.get(pk=form_kwargs['instance'])
form_kwargs = view_instance.get_async_form_kwargs(form_kwargs, organizer, event)
form = form_class(**form_kwargs)
return view_instance.async_form_valid(self, form)
cls.async_execute = app.task(
base=ProfiledEventTask,
bind=True,
name=cls.__module__ + '.' + cls.__name__ + '.async_execute',
throws=(ValidationError,)
)(async_execute)
def async_form_valid(self, task, form):
pass
def get_async_form_kwargs(self, form_kwargs, organizer=None, event=None):
return form_kwargs
def get(self, request, *args, **kwargs):
if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request)
return super().get(request, *args, **kwargs)
def form_valid(self, form):
if form.files:
raise TypeError('File upload currently not supported in AsyncFormView')
form_kwargs = {
k: v for k, v in self.get_form_kwargs().items()
}
if form_kwargs.get('instance'):
if form_kwargs['instance'].pk:
form_kwargs['instance'] = form_kwargs['instance'].pk
else:
form_kwargs['instance'] = None
form_kwargs.setdefault('data', {})
kwargs = {
'request_path': self.request.path,
'form_kwargs': form_kwargs,
}
if hasattr(self.request, 'organizer'):
kwargs['organizer'] = self.request.organizer.pk
if self.request.user.is_authenticated:
kwargs['user'] = self.request.user.pk
if hasattr(self.request, 'event'):
kwargs['event'] = self.request.event.pk
try:
res = type(self).async_execute.apply_async(kwargs=kwargs)
except ConnectionError:
# Task very likely not yet sent, due to redis restarting etc. Let's try once again
res = type(self).async_execute.apply_async(kwargs=kwargs)
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
data = self._return_ajax_result(res)
data['check_url'] = self.get_check_url(res.id, True)
return JsonResponse(data)
else:
if res.ready():
if res.successful() and not isinstance(res.info, Exception):
return self.success(res.info)
else:
return self.error(res.info)
return redirect(self.get_check_url(res.id, False))

View File

@@ -1439,8 +1439,6 @@ class VoucherFilterForm(FilterForm):
s = fdata.get('tag').strip()
if s == '<>':
qs = qs.filter(Q(tag__isnull=True) | Q(tag=''))
elif s[0] == '"' and s[-1] == '"':
qs = qs.filter(tag__iexact=s[1:-1])
else:
qs = qs.filter(tag__icontains=s)

View File

@@ -337,7 +337,7 @@ class ItemCreateForm(I18nModelForm):
setattr(self.instance, f, getattr(self.cleaned_data['copy_from'], f))
else:
# Add to all sales channels by default
self.instance.sales_channels = list(get_all_sales_channels().keys())
self.instance.sales_channels = [k for k in get_all_sales_channels().keys()]
self.instance.position = (self.event.items.aggregate(p=Max('position'))['p'] or 0) + 1
instance = super().save(*args, **kwargs)

View File

@@ -75,7 +75,7 @@ class ExtendForm(I18nModelForm):
return super().save(commit)
class ForceQuotaConfirmationForm(forms.Form):
class ConfirmPaymentForm(forms.Form):
force = forms.BooleanField(
label=_('Overbook quota and ignore late payment'),
help_text=_('If you check this box, this operation will be performed even if it leads to an overbooked quota '
@@ -101,15 +101,7 @@ class ForceQuotaConfirmationForm(forms.Form):
del self.fields['force']
class ConfirmPaymentForm(ForceQuotaConfirmationForm):
pass
class ReactivateOrderForm(ForceQuotaConfirmationForm):
pass
class CancelForm(ForceQuotaConfirmationForm):
class CancelForm(ConfirmPaymentForm):
send_email = forms.BooleanField(
required=False,
label=_('Notify customer by email'),

View File

@@ -5,7 +5,7 @@ from io import StringIO
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import EmailValidator
from django.db.models.functions import Upper
from django.db.models.functions import Lower
from django.urls import reverse
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes.forms import SafeModelChoiceField
@@ -346,8 +346,8 @@ class VoucherBulkForm(VoucherForm):
data = super().clean()
vouchers = self.instance.event.vouchers.annotate(
code_upper=Upper('code')
).filter(code_upper__in=[c.upper() for c in data['codes']])
code_lower=Lower('code')
).filter(code_lower__in=[c.lower() for c in data['codes']])
if vouchers.exists():
raise ValidationError(_('A voucher with one of these codes already exists.'))
@@ -377,5 +377,26 @@ class VoucherBulkForm(VoucherForm):
return data
def post_bulk_save(self, objs):
pass
def save(self, event, *args, **kwargs):
objs = []
for code in self.cleaned_data['codes']:
obj = modelcopy(self.instance)
obj.event = event
obj.code = code
try:
obj.seat = self.cleaned_data['seats'].pop()
obj.item = obj.seat.product
except IndexError:
pass
data = dict(self.cleaned_data)
data['code'] = code
data['bulk'] = True
del data['codes']
objs.append(obj)
Voucher.objects.bulk_create(objs, batch_size=200)
objs = []
for v in event.vouchers.filter(code__in=self.cleaned_data['codes']):
# We need to query them again as bulk_create does not fill in .pk values on databases
# other than PostgreSQL
objs.append(v)
return objs

View File

@@ -7,7 +7,7 @@ from django.utils.translation import gettext as _
def current_url(request):
if request.GET:
if len(request.GET):
return request.path + '?' + request.GET.urlencode()
else:
return request.path

View File

@@ -2,9 +2,7 @@ from django.dispatch import Signal
from pretix.base.signals import DeprecatedSignal, EventPluginSignal
html_page_start = Signal(
providing_args=[]
)
html_page_start = Signal()
"""
This signal allows you to put code in the beginning of the main page for every
page in the backend. You are expected to return HTML.
@@ -12,10 +10,10 @@ page in the backend. You are expected to return HTML.
The ``sender`` keyword argument will contain the request.
"""
html_head = EventPluginSignal(
providing_args=["request"]
)
html_head = EventPluginSignal()
"""
Arguments: ``request``
This signal allows you to put code inside the HTML ``<head>`` tag
of every page in the backend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.
@@ -23,10 +21,10 @@ of every page in the backend. You will get the request as the keyword argument
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
nav_event = EventPluginSignal(
providing_args=["request"]
)
nav_event = EventPluginSignal()
"""
Arguments: ``request``
This signal allows you to add additional views to the admin panel
navigation. You will get the request as a keyword argument ``request``.
Receivers are expected to return a list of dictionaries. The dictionaries
@@ -48,10 +46,10 @@ in pretix.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
nav_topbar = Signal(
providing_args=["request"]
)
nav_topbar = Signal()
"""
Arguments: ``request``
This signal allows you to add additional views to the top navigation bar.
You will get the request as a keyword argument ``request``.
Receivers are expected to return a list of dictionaries. The dictionaries
@@ -67,10 +65,10 @@ This is no ``EventPluginSignal``, so you do not get the event in the ``sender``
and you may get the signal regardless of whether your plugin is active.
"""
nav_global = Signal(
providing_args=["request"]
)
nav_global = Signal()
"""
Arguments: ``request``
This signal allows you to add additional views to the navigation bar when no event is
selected. You will get the request as a keyword argument ``request``.
Receivers are expected to return a list of dictionaries. The dictionaries
@@ -92,10 +90,10 @@ This is no ``EventPluginSignal``, so you do not get the event in the ``sender``
and you may get the signal regardless of whether your plugin is active.
"""
event_dashboard_top = EventPluginSignal(
providing_args=['request']
)
event_dashboard_top = EventPluginSignal()
"""
Arguments: 'request'
This signal is sent out to include custom HTML in the top part of the the event dashboard.
Receivers should return HTML.
@@ -103,9 +101,7 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
An additional keyword argument ``subevent`` *can* contain a sub-event.
"""
event_dashboard_widgets = EventPluginSignal(
providing_args=[]
)
event_dashboard_widgets = EventPluginSignal()
"""
This signal is sent out to include widgets in the event dashboard. Receivers
should return a list of dictionaries, where each dictionary can have the keys:
@@ -120,10 +116,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
An additional keyword argument ``subevent`` *can* contain a sub-event.
"""
user_dashboard_widgets = Signal(
providing_args=['user']
)
user_dashboard_widgets = Signal()
"""
Arguments: 'user'
This signal is sent out to include widgets in the personal user dashboard. Receivers
should return a list of dictionaries, where each dictionary can have the keys:
@@ -136,36 +132,31 @@ should return a list of dictionaries, where each dictionary can have the keys:
This is a regular django signal (no pretix event signal).
"""
voucher_form_html = EventPluginSignal(
providing_args=['form']
)
voucher_form_html = EventPluginSignal()
"""
Arguments: 'form'
This signal allows you to add additional HTML to the form that is used for modifying vouchers.
You receive the form object in the ``form`` keyword argument.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
voucher_form_class = EventPluginSignal(
providing_args=['cls']
)
voucher_form_class = EventPluginSignal()
"""
Arguments: 'cls'
This signal allows you to replace the form class that is used for modifying vouchers.
You will receive the default form class (or the class set by a previous plugin) in the
``cls`` argument so that you can inherit from it.
Note that this is also called for the voucher bulk creation form, which is executed in
an asynchronous context. For the bulk creation form, ``save()`` is not called. Instead,
you can implement ``post_bulk_save(saved_vouchers)`` which may be called multiple times
for every batch persisted to the database.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
voucher_form_validation = EventPluginSignal(
providing_args=['form']
)
voucher_form_validation = EventPluginSignal()
"""
Arguments: 'form'
This signal allows you to add additional validation to the form that is used for
creating and modifying vouchers. You will receive the form instance in the ``form``
argument and the current data state in the ``data`` argument.
@@ -173,28 +164,28 @@ argument and the current data state in the ``data`` argument.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
quota_detail_html = EventPluginSignal(
providing_args=['quota']
)
quota_detail_html = EventPluginSignal()
"""
Arguments: 'quota'
This signal allows you to append HTML to a Quota's detail view. You receive the
quota as argument in the ``quota`` keyword argument.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
organizer_edit_tabs = DeprecatedSignal(
providing_args=['organizer', 'request']
)
organizer_edit_tabs = DeprecatedSignal()
"""
Arguments: 'organizer', 'request'
Deprecated signal, no longer works. We just keep the definition so old plugins don't
break the installation.
"""
nav_organizer = Signal(
providing_args=['organizer', 'request']
)
nav_organizer = Signal()
"""
Arguments: 'organizer', 'request'
This signal is sent out to include tab links on the detail page of an organizer.
Receivers are expected to return a list of dictionaries. The dictionaries
should contain at least the keys ``label`` and ``url``. You should also return
@@ -215,30 +206,30 @@ This is a regular django signal (no pretix event signal). Receivers will be pass
the keyword arguments ``organizer`` and ``request``.
"""
order_info = EventPluginSignal(
providing_args=["order", "request"]
)
order_info = EventPluginSignal()
"""
Arguments: ``order``, ``request``
This signal is sent out to display additional information on the order detail page
As with all plugin signals, the ``sender`` keyword argument will contain the event.
Additionally, the argument ``order`` and ``request`` are available.
"""
order_position_buttons = EventPluginSignal(
providing_args=["order", "position", "request"]
)
order_position_buttons = EventPluginSignal()
"""
Arguments: ``order``, ``position``, ``request``
This signal is sent out to display additional buttons for a single position of an order.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
Additionally, the argument ``order`` and ``request`` are available.
"""
nav_event_settings = EventPluginSignal(
providing_args=['request']
)
nav_event_settings = EventPluginSignal()
"""
Arguments: 'request'
This signal is sent out to include tab links on the settings page of an event.
Receivers are expected to return a list of dictionaries. The dictionaries
should contain at least the keys ``label`` and ``url``. You should also return
@@ -253,10 +244,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
A second keyword argument ``request`` will contain the request object.
"""
event_settings_widget = EventPluginSignal(
providing_args=['request']
)
event_settings_widget = EventPluginSignal()
"""
Arguments: 'request'
This signal is sent out to include template snippets on the settings page of an event
that allows generating a pretix Widget code.
@@ -264,10 +255,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
A second keyword argument ``request`` will contain the request object.
"""
item_forms = EventPluginSignal(
providing_args=['request', 'item']
)
item_forms = EventPluginSignal()
"""
Arguments: 'request', 'item'
This signal allows you to return additional forms that should be rendered on the product
modification page. You are passed ``request`` and ``item`` arguments and are expected to return
an instance of a form class that you bind yourself when appropriate. Your form will be executed
@@ -277,10 +268,10 @@ styles. It is advisable to set a prefix for your form to avoid clashes with othe
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
item_formsets = EventPluginSignal(
providing_args=['request', 'item']
)
item_formsets = EventPluginSignal()
"""
Arguments: 'request', 'item'
This signal allows you to return additional formsets that should be rendered on the product
modification page. You are passed ``request`` and ``item`` arguments and are expected to return
an instance of a formset class that you bind yourself when appropriate. Your formset will be
@@ -295,10 +286,10 @@ will be passed a ``formset`` variable with your formset.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
subevent_forms = EventPluginSignal(
providing_args=['request', 'subevent', 'copy_from']
)
subevent_forms = EventPluginSignal()
"""
Arguments: 'request', 'subevent', 'copy_from'
This signal allows you to return additional forms that should be rendered on the subevent creation
or modification page. You are passed ``request`` and ``subevent`` arguments and are expected to return
an instance of a form class that you bind yourself when appropriate. Your form will be executed
@@ -312,17 +303,17 @@ creation, ``copy_from`` can be a subevent that is being copied from.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
oauth_application_registered = Signal(
providing_args=["user", "application"]
)
oauth_application_registered = Signal()
"""
Arguments: ``user``, ``application``
This signal will be called whenever a user registers a new OAuth application.
"""
order_search_filter_q = Signal(
providing_args=["query"]
)
order_search_filter_q = Signal()
"""
Arguments: ``query``
This signal will be called whenever a free-text order search is performed. You are expected to return one
Q object that will be OR-ed with existing search queries. As order search exists on a global level as well,
this is not an Event signal and will be called even if your plugin is not active. ``sender`` will contain the
@@ -330,10 +321,10 @@ event if the search is performed within an event, and ``None`` otherwise. The se
``query``.
"""
order_search_forms = EventPluginSignal(
providing_args=['request']
)
order_search_forms = EventPluginSignal()
"""
Arguments: 'request'
This signal allows you to return additional forms that should be rendered in the advanced order search.
You are passed ``request`` argument and are expected to return an instance of a form class that you bind
yourself when appropriate. Your form will be executed as part of the standard validation and rendering

View File

@@ -29,7 +29,7 @@
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
<form class="form-inline helper-display-inline" action="" method="get">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</form>
</form>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% load i18n %}
<p>
<select name="subevent" class="form-control{% if auto_submit %} simple-subevent-choice{% endif %}" data-model-select2="event"
<select name="subevent" class="form-control simple-subevent-choice" data-model-select2="event"
data-select2-url="{% url "control:event.subevents.select2" organizer=request.event.organizer.slug event=request.event.slug %}"
data-placeholder="{% trans "All dates" context "subevent" %}">
{% for se in selected_subevents %}

View File

@@ -98,7 +98,7 @@
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</form>
{% endif %}
{% if not request.event.has_subevents or subevent %}

View File

@@ -15,7 +15,7 @@
</p>
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</form>
{% endif %}
{% if quotas|length == 0 %}

View File

@@ -24,7 +24,6 @@
<form method="post" class="form-horizontal" href="">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' horizontal_label_class='sr-only' horizontal_field_class='col-md-12' %}
<div class="form-group submit-group">
<a class="btn btn-default btn-lg"
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">

View File

@@ -5,7 +5,7 @@
{% block title %}{% trans "Voucher" %}{% endblock %}
{% block inside %}
<h1>{% trans "Create multiple vouchers" %}</h1>
<form action="" method="post" class="form-horizontal" data-asynctask>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_form_errors form %}
<fieldset>

View File

@@ -49,7 +49,7 @@
<td>
<strong>
{% if t.tag %}
<a href="{% url "control:event.vouchers" organizer=request.event.organizer.slug event=request.event.slug %}?tag={{ '"'|add:t.tag|add:'"'|urlencode }}">
<a href="{% url "control:event.vouchers" organizer=request.event.organizer.slug event=request.event.slug %}?tag={{ t.tag|urlencode }}">
{{ t.tag }}
</a>
{% else %}

View File

@@ -48,9 +48,15 @@
</p>
{% endif %}
{% if request.event.has_subevents %}
<div class="col-md-6">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</div>
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
{% endif %}
<button class="btn btn-large btn-primary" type="submit">
{% trans "Send as many vouchers as possible" %}
@@ -74,50 +80,52 @@
</div>
</div>
<form class="row filter-form" action="" method="get">
<div class="col-lg-2 col-md-3 col-xs-6">
<select name="status" class="form-control">
<option value="a"
{% if request.GET.status == "p" %}selected="selected"{% endif %}>{% trans "All entries" %}</option>
<option value="w"
{% if request.GET.status == "w" or not request.GET.status %}selected="selected"{% endif %}>
{% trans "Waiting for a voucher" %}</option>
<option value="s"
{% if request.GET.status == "s" %}selected="selected"{% endif %}>{% trans "Voucher assigned" %}</option>
<option value="v"
{% if request.GET.status == "v" %}selected="selected"{% endif %}>
{% trans "Waiting for redemption" %}</option>
<option value="r"
{% if request.GET.status == "r" %}selected="selected"{% endif %}>
{% trans "Successfully redeemed" %}</option>
<option value="e"
{% if request.GET.status == "e" %}selected="selected"{% endif %}>
{% trans "Voucher expired" %}</option>
</select>
</div>
<div class="col-lg-2 col-md-3 col-xs-6">
<select name="item" class="form-control">
<option value="">{% trans "All products" %}</option>
{% for item in items %}
<option value="{{ item.id }}"
{% if request.GET.item|add:0 == item.id %}selected="selected"{% endif %}>
{{ item }}
<p>
<form class="form-inline helper-display-inline" action="" method="get">
<select name="status" class="form-control">
<option value="a"
{% if request.GET.status == "p" %}selected="selected"{% endif %}>{% trans "All entries" %}</option>
<option value="w"
{% if request.GET.status == "w" or not request.GET.status %}selected="selected"{% endif %}>
{% trans "Waiting for a voucher" %}</option>
<option value="s"
{% if request.GET.status == "s" %}selected="selected"{% endif %}>{% trans "Voucher assigned" %}</option>
<option value="v"
{% if request.GET.status == "v" %}selected="selected"{% endif %}>
{% trans "Waiting for redemption" %}</option>
<option value="r"
{% if request.GET.status == "r" %}selected="selected"{% endif %}>
{% trans "Successfully redeemed" %}</option>
<option value="e"
{% if request.GET.status == "e" %}selected="selected"{% endif %}>
{% trans "Voucher expired" %}</option>
</select>
<select name="item" class="form-control">
<option value="">{% trans "All products" %}</option>
{% for item in items %}
<option value="{{ item.id }}"
{% if request.GET.item|add:0 == item.id %}selected="selected"{% endif %}>
{{ item }}
</option>
{% endfor %}
</select>
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
</div>
{% if request.event.has_subevents %}
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</div>
{% endif %}
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
<button class="btn btn-primary" type="submit"><span class="fa fa-filter"></span> {% trans "Filter" %}</button>
<a href="?{% url_replace request "download" "yes" %}"
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
<a href="?{% url_replace request "download" "yes" %}"
class="btn btn-default"><i class="fa fa-download"></i>
{% trans "Download list" %}</a>
</div>
{% trans "Download list" %}</a>
</form>
</p>
<form method="post" action="?next={{ request.get_full_path|urlencode }}">
{% csrf_token %}
<div class="table-responsive">
@@ -158,7 +166,7 @@
{% endif %}
</td>
{% if request.event.has_subevents %}
<td>{{ e.subevent.name }} {{ e.subevent.get_date_range_display }} {{ e.subevent.date_from|date:"TIME_FORMAT" }}</td>
<td>{{ e.subevent.name }} {{ e.subevent.get_date_range_display }}</td>
{% endif %}
<td>
{{ e.created|date:"SHORT_DATETIME_FORMAT" }}

View File

@@ -1,4 +1,4 @@
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from django.views.generic.base import RedirectView
from pretix.control.views import (
@@ -8,310 +8,327 @@ from pretix.control.views import (
)
urlpatterns = [
url(r'^logout$', auth.logout, name='auth.logout'),
url(r'^login$', auth.login, name='auth.login'),
url(r'^login/2fa$', auth.Login2FAView.as_view(), name='auth.login.2fa'),
url(r'^register$', auth.register, name='auth.register'),
url(r'^invite/(?P<token>[a-zA-Z0-9]+)$', auth.invite, name='auth.invite'),
url(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'),
url(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'),
url(r'^$', dashboards.user_index, name='index'),
url(r'^widgets.json$', dashboards.user_index_widgets_lazy, name='index.widgets'),
url(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global.settings'),
url(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'),
url(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'),
url(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'),
url(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'),
url(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'),
url(r'^geocode/$', geo.GeoCodeView.as_view(), name='global.geocode'),
url(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'),
url(r'^sudo/$', user.StartStaffSession.as_view(), name='user.sudo'),
url(r'^sudo/stop/$', user.StopStaffSession.as_view(), name='user.sudo.stop'),
url(r'^sudo/(?P<id>\d+)/$', user.EditStaffSession.as_view(), name='user.sudo.edit'),
url(r'^sudo/sessions/$', user.StaffSessionList.as_view(), name='user.sudo.list'),
url(r'^users/$', users.UserListView.as_view(), name='users'),
url(r'^users/select2$', typeahead.users_select2, name='users.select2'),
url(r'^users/add$', users.UserCreateView.as_view(), name='users.add'),
url(r'^users/impersonate/stop', users.UserImpersonateStopView.as_view(), name='users.impersonate.stop'),
url(r'^users/(?P<id>\d+)/$', users.UserEditView.as_view(), name='users.edit'),
url(r'^users/(?P<id>\d+)/reset$', users.UserResetView.as_view(), name='users.reset'),
url(r'^users/(?P<id>\d+)/impersonate', users.UserImpersonateView.as_view(), name='users.impersonate'),
url(r'^users/(?P<id>\d+)/anonymize', users.UserAnonymizeView.as_view(), name='users.anonymize'),
url(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'),
url(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'),
url(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
url(r'^settings/notifications/$', user.UserNotificationsEditView.as_view(), name='user.settings.notifications'),
url(r'^settings/notifications/off/(?P<id>\d+)/(?P<token>[^/]+)/$', user.UserNotificationsDisableView.as_view(),
name='user.settings.notifications.off'),
url(r'^settings/oauth/authorized/$', oauth.AuthorizationListView.as_view(),
name='user.settings.oauth.list'),
url(r'^settings/oauth/authorized/(?P<pk>\d+)/revoke$', oauth.AuthorizationRevokeView.as_view(),
name='user.settings.oauth.revoke'),
url(r'^settings/oauth/apps/$', oauth.OAuthApplicationListView.as_view(),
name='user.settings.oauth.apps'),
url(r'^settings/oauth/apps/add$', oauth.OAuthApplicationRegistrationView.as_view(),
name='user.settings.oauth.apps.register'),
url(r'^settings/oauth/apps/(?P<pk>\d+)/$', oauth.OAuthApplicationUpdateView.as_view(),
name='user.settings.oauth.app'),
url(r'^settings/oauth/apps/(?P<pk>\d+)/disable$', oauth.OAuthApplicationDeleteView.as_view(),
name='user.settings.oauth.app.disable'),
url(r'^settings/oauth/apps/(?P<pk>\d+)/roll$', oauth.OAuthApplicationRollView.as_view(),
name='user.settings.oauth.app.roll'),
url(r'^settings/2fa/$', user.User2FAMainView.as_view(), name='user.settings.2fa'),
url(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'),
url(r'^settings/2fa/enable', user.User2FAEnableView.as_view(), name='user.settings.2fa.enable'),
url(r'^settings/2fa/disable', user.User2FADisableView.as_view(), name='user.settings.2fa.disable'),
url(r'^settings/2fa/regenemergency', user.User2FARegenerateEmergencyView.as_view(),
name='user.settings.2fa.regenemergency'),
url(r'^settings/2fa/totp/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(),
name='user.settings.2fa.confirm.totp'),
url(r'^settings/2fa/webauthn/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmWebAuthnView.as_view(),
name='user.settings.2fa.confirm.webauthn'),
url(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),
name='user.settings.2fa.delete'),
url(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
url(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
url(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'),
url(r'^organizer/(?P<organizer>[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'),
url(r'^organizer/(?P<organizer>[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/delete$', organizer.OrganizerDelete.as_view(), name='organizer.delete'),
url(r'^organizer/(?P<organizer>[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(),
name='organizer.display'),
url(r'^organizer/(?P<organizer>[^/]+)/properties$', organizer.EventMetaPropertyListView.as_view(), name='organizer.properties'),
url(r'^organizer/(?P<organizer>[^/]+)/property/add$', organizer.EventMetaPropertyCreateView.as_view(),
name='organizer.property.add'),
url(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/edit$', organizer.EventMetaPropertyUpdateView.as_view(),
name='organizer.property.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/delete$', organizer.EventMetaPropertyDeleteView.as_view(),
name='organizer.property.delete'),
url(r'^organizer/(?P<organizer>[^/]+)/giftcards$', organizer.GiftCardListView.as_view(), name='organizer.giftcards'),
url(r'^organizer/(?P<organizer>[^/]+)/giftcard/add$', organizer.GiftCardCreateView.as_view(), name='organizer.giftcard.add'),
url(r'^organizer/(?P<organizer>[^/]+)/giftcard/(?P<giftcard>[^/]+)/$', organizer.GiftCardDetailView.as_view(), name='organizer.giftcard'),
url(r'^organizer/(?P<organizer>[^/]+)/giftcard/(?P<giftcard>[^/]+)/edit$', organizer.GiftCardUpdateView.as_view(),
name='organizer.giftcard.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/webhooks$', organizer.WebHookListView.as_view(), name='organizer.webhooks'),
url(r'^organizer/(?P<organizer>[^/]+)/webhook/add$', organizer.WebHookCreateView.as_view(),
name='organizer.webhook.add'),
url(r'^organizer/(?P<organizer>[^/]+)/webhook/(?P<webhook>[^/]+)/edit$', organizer.WebHookUpdateView.as_view(),
name='organizer.webhook.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/webhook/(?P<webhook>[^/]+)/logs$', organizer.WebHookLogsView.as_view(),
name='organizer.webhook.logs'),
url(r'^organizer/(?P<organizer>[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'),
url(r'^organizer/(?P<organizer>[^/]+)/device/add$', organizer.DeviceCreateView.as_view(),
name='organizer.device.add'),
url(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/edit$', organizer.DeviceUpdateView.as_view(),
name='organizer.device.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/connect$', organizer.DeviceConnectView.as_view(),
name='organizer.device.connect'),
url(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/revoke$', organizer.DeviceRevokeView.as_view(),
name='organizer.device.revoke'),
url(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/logs$', organizer.DeviceLogView.as_view(),
name='organizer.device.logs'),
url(r'^organizer/(?P<organizer>[^/]+)/gates$', organizer.GateListView.as_view(), name='organizer.gates'),
url(r'^organizer/(?P<organizer>[^/]+)/gate/add$', organizer.GateCreateView.as_view(), name='organizer.gate.add'),
url(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/edit$', organizer.GateUpdateView.as_view(),
name='organizer.gate.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/delete$', organizer.GateDeleteView.as_view(),
name='organizer.gate.delete'),
url(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'),
url(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(),
name='organizer.team'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/edit$', organizer.TeamUpdateView.as_view(),
name='organizer.team.edit'),
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
name='organizer.team.delete'),
url(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
url(r'^organizer/(?P<organizer>[^/]+)/logs', organizer.LogView.as_view(), name='organizer.log'),
url(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
url(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'),
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
url(r'^events/$', main.EventList.as_view(), name='events'),
url(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
url(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'),
url(r'^events/typeahead/meta/$', typeahead.meta_values, name='events.meta.typeahead'),
url(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'),
url(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
url(r'^$', dashboards.event_index, name='event.index'),
url(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),
url(r'^live/$', event.EventLive.as_view(), name='event.live'),
url(r'^logs/$', event.EventLog.as_view(), name='event.log'),
url(r'^delete/$', event.EventDelete.as_view(), name='event.delete'),
url(r'^requiredactions/$', event.EventActions.as_view(), name='event.requiredactions'),
url(r'^requiredactions/(?P<id>\d+)/discard$', event.EventActionDiscard.as_view(),
name='event.requiredaction.discard'),
url(r'^comment/$', event.EventComment.as_view(),
name='event.comment'),
url(r'^quickstart/$', event.QuickSetupView.as_view(), name='event.quick'),
url(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'),
url(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'),
url(r'^settings/payment/(?P<provider>[^/]+)$', event.PaymentProviderSettings.as_view(),
name='event.settings.payment.provider'),
url(r'^settings/payment$', event.PaymentSettings.as_view(), name='event.settings.payment'),
url(r'^settings/tickets$', event.TicketSettings.as_view(), name='event.settings.tickets'),
url(r'^settings/tickets/preview/(?P<output>[^/]+)$', event.TicketSettingsPreview.as_view(),
name='event.settings.tickets.preview'),
url(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'),
url(r'^settings/email/preview$', event.MailSettingsPreview.as_view(), name='event.settings.mail.preview'),
url(r'^settings/email/layoutpreview$', event.MailSettingsRendererPreview.as_view(),
name='event.settings.mail.preview.layout'),
url(r'^settings/cancel', event.CancelSettings.as_view(), name='event.settings.cancel'),
url(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'),
url(r'^settings/invoice/preview$', event.InvoicePreview.as_view(), name='event.settings.invoice.preview'),
url(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'),
url(r'^settings/tax/$', event.TaxList.as_view(), name='event.settings.tax'),
url(r'^settings/tax/(?P<rule>\d+)/$', event.TaxUpdate.as_view(), name='event.settings.tax.edit'),
url(r'^settings/tax/add$', event.TaxCreate.as_view(), name='event.settings.tax.add'),
url(r'^settings/tax/(?P<rule>\d+)/delete$', event.TaxDelete.as_view(), name='event.settings.tax.delete'),
url(r'^settings/widget$', event.WidgetSettings.as_view(), name='event.settings.widget'),
url(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'),
url(r'^pdf/editor/(?P<filename>[^/]+).pdf$', pdf.PdfView.as_view(), name='pdf.background'),
url(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
url(r'^subevents/select2$', typeahead.subevent_select2, name='event.subevents.select2'),
url(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
url(r'^subevents/(?P<subevent>\d+)/delete$', subevents.SubEventDelete.as_view(),
name='event.subevent.delete'),
url(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
url(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
url(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
url(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'),
url(r'^items/$', item.ItemList.as_view(), name='event.items'),
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
url(r'^items/(?P<item>\d+)/up$', item.item_move_up, name='event.items.up'),
url(r'^items/(?P<item>\d+)/down$', item.item_move_down, name='event.items.down'),
url(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
url(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
url(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
url(r'^items/select2/variation$', typeahead.variations_select2, name='event.items.variations.select2'),
url(r'^categories/$', item.CategoryList.as_view(), name='event.items.categories'),
url(r'^categories/select2$', typeahead.category_select2, name='event.items.categories.select2'),
url(r'^categories/(?P<category>\d+)/delete$', item.CategoryDelete.as_view(),
name='event.items.categories.delete'),
url(r'^categories/(?P<category>\d+)/up$', item.category_move_up, name='event.items.categories.up'),
url(r'^categories/(?P<category>\d+)/down$', item.category_move_down,
name='event.items.categories.down'),
url(r'^categories/(?P<category>\d+)/$', item.CategoryUpdate.as_view(),
name='event.items.categories.edit'),
url(r'^categories/add$', item.CategoryCreate.as_view(), name='event.items.categories.add'),
url(r'^questions/$', item.QuestionList.as_view(), name='event.items.questions'),
url(r'^questions/reorder$', item.reorder_questions, name='event.items.questions.reorder'),
url(r'^questions/(?P<question>\d+)/delete$', item.QuestionDelete.as_view(),
name='event.items.questions.delete'),
url(r'^questions/(?P<question>\d+)/$', item.QuestionView.as_view(),
name='event.items.questions.show'),
url(r'^questions/(?P<question>\d+)/change$', item.QuestionUpdate.as_view(),
name='event.items.questions.edit'),
url(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'),
url(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'),
url(r'^quotas/(?P<quota>\d+)/$', item.QuotaView.as_view(), name='event.items.quotas.show'),
url(r'^quotas/select$', typeahead.quotas_select2, name='event.items.quotas.select2'),
url(r'^quotas/(?P<quota>\d+)/change$', item.QuotaUpdate.as_view(), name='event.items.quotas.edit'),
url(r'^quotas/(?P<quota>\d+)/delete$', item.QuotaDelete.as_view(),
name='event.items.quotas.delete'),
url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'),
url(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'),
url(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'),
url(r'^vouchers/rng$', vouchers.VoucherRNG.as_view(), name='event.vouchers.rng'),
url(r'^vouchers/item_select$', typeahead.itemvarquota_select2, name='event.vouchers.itemselect2'),
url(r'^vouchers/(?P<voucher>\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'),
url(r'^vouchers/(?P<voucher>\d+)/delete$', vouchers.VoucherDelete.as_view(),
name='event.voucher.delete'),
url(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'),
url(r'^vouchers/go$', vouchers.VoucherGo.as_view(), name='event.vouchers.go'),
url(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'),
url(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'),
url(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
name='event.order.transition'),
url(r'^orders/(?P<code>[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(),
name='event.order.resendlink'),
url(r'^orders/(?P<code>[0-9A-Z]+)/(?P<position>\d+)/resend$', orders.OrderResendLink.as_view(),
name='event.order.resendlink'),
url(r'^orders/(?P<code>[0-9A-Z]+)/invoice$', orders.OrderInvoiceCreate.as_view(),
name='event.order.geninvoice'),
url(r'^orders/(?P<code>[0-9A-Z]+)/invoices/(?P<id>\d+)/regenerate$', orders.OrderInvoiceRegenerate.as_view(),
name='event.order.regeninvoice'),
url(r'^orders/(?P<code>[0-9A-Z]+)/invoices/(?P<id>\d+)/reissue$', orders.OrderInvoiceReissue.as_view(),
name='event.order.reissueinvoice'),
url(r'^orders/(?P<code>[0-9A-Z]+)/download/(?P<position>\d+)/(?P<output>[^/]+)/$',
orders.OrderDownload.as_view(),
name='event.order.download.ticket'),
url(r'^orders/(?P<code>[0-9A-Z]+)/answer/(?P<answer>[^/]+)/$',
orders.AnswerDownload.as_view(),
name='event.order.download.answer'),
url(r'^orders/(?P<code>[0-9A-Z]+)/checkvatid', orders.OrderCheckVATID.as_view(),
name='event.order.checkvatid'),
url(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
name='event.order.extend'),
url(r'^orders/(?P<code>[0-9A-Z]+)/reactivate$', orders.OrderReactivate.as_view(),
name='event.order.reactivate'),
url(r'^orders/(?P<code>[0-9A-Z]+)/contact$', orders.OrderContactChange.as_view(),
name='event.order.contact'),
url(r'^orders/(?P<code>[0-9A-Z]+)/locale', orders.OrderLocaleChange.as_view(),
name='event.order.locale'),
url(r'^orders/(?P<code>[0-9A-Z]+)/comment$', orders.OrderComment.as_view(),
name='event.order.comment'),
url(r'^orders/(?P<code>[0-9A-Z]+)/change$', orders.OrderChange.as_view(),
name='event.order.change'),
url(r'^orders/(?P<code>[0-9A-Z]+)/approve', orders.OrderApprove.as_view(),
name='event.order.approve'),
url(r'^orders/(?P<code>[0-9A-Z]+)/deny$', orders.OrderDeny.as_view(),
name='event.order.deny'),
url(r'^orders/(?P<code>[0-9A-Z]+)/delete$', orders.OrderDelete.as_view(),
name='event.order.delete'),
url(r'^orders/(?P<code>[0-9A-Z]+)/info', orders.OrderModifyInformation.as_view(),
name='event.order.info'),
url(r'^orders/(?P<code>[0-9A-Z]+)/sendmail$', orders.OrderSendMail.as_view(),
name='event.order.sendmail'),
url(r'^orders/(?P<code>[0-9A-Z]+)/(?P<position>[0-9A-Z]+)/sendmail$', orders.OrderPositionSendMail.as_view(),
name='event.order.position.sendmail'),
url(r'^orders/(?P<code>[0-9A-Z]+)/mail_history$', orders.OrderEmailHistory.as_view(),
name='event.order.mail_history'),
url(r'^orders/(?P<code>[0-9A-Z]+)/payments/(?P<payment>\d+)/cancel$', orders.OrderPaymentCancel.as_view(),
name='event.order.payments.cancel'),
url(r'^orders/(?P<code>[0-9A-Z]+)/payments/(?P<payment>\d+)/confirm$', orders.OrderPaymentConfirm.as_view(),
name='event.order.payments.confirm'),
url(r'^orders/(?P<code>[0-9A-Z]+)/refund$', orders.OrderRefundView.as_view(),
name='event.order.refunds.start'),
url(r'^orders/(?P<code>[0-9A-Z]+)/refunds/(?P<refund>\d+)/cancel$', orders.OrderRefundCancel.as_view(),
name='event.order.refunds.cancel'),
url(r'^orders/(?P<code>[0-9A-Z]+)/refunds/(?P<refund>\d+)/process$', orders.OrderRefundProcess.as_view(),
name='event.order.refunds.process'),
url(r'^orders/(?P<code>[0-9A-Z]+)/refunds/(?P<refund>\d+)/done$', orders.OrderRefundDone.as_view(),
name='event.order.refunds.done'),
url(r'^orders/(?P<code>[0-9A-Z]+)/cancellationrequests/(?P<req>\d+)/delete$',
orders.OrderCancellationRequestDelete.as_view(),
name='event.order.cancellationrequests.delete'),
url(r'^orders/(?P<code>[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'),
url(r'^invoice/(?P<invoice>[^/]+)$', orders.InvoiceDownload.as_view(),
name='event.invoice.download'),
url(r'^orders/overview/$', orders.OverView.as_view(), name='event.orders.overview'),
url(r'^orders/import/$', orderimport.ImportView.as_view(), name='event.orders.import'),
url(r'^orders/import/(?P<file>[^/]+)/$', orderimport.ProcessView.as_view(), name='event.orders.import.process'),
url(r'^orders/export/$', orders.ExportView.as_view(), name='event.orders.export'),
url(r'^orders/export/do$', orders.ExportDoView.as_view(), name='event.orders.export.do'),
url(r'^orders/refunds/$', orders.RefundList.as_view(), name='event.orders.refunds'),
url(r'^orders/go$', orders.OrderGo.as_view(), name='event.orders.go'),
url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'),
url(r'^orders/search$', orders.OrderSearch.as_view(), name='event.orders.search'),
url(r'^dangerzone/$', event.DangerZone.as_view(), name='event.dangerzone'),
url(r'^cancel/$', orders.EventCancel.as_view(), name='event.cancel'),
url(r'^shredder/$', shredder.StartShredView.as_view(), name='event.shredder.start'),
url(r'^shredder/export$', shredder.ShredExportView.as_view(), name='event.shredder.export'),
url(r'^shredder/download/(?P<file>[^/]+)/$', shredder.ShredDownloadView.as_view(), name='event.shredder.download'),
url(r'^shredder/shred', shredder.ShredDoView.as_view(), name='event.shredder.shred'),
url(r'^waitinglist/$', waitinglist.WaitingListView.as_view(), name='event.orders.waitinglist'),
url(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'),
url(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(),
name='event.orders.waitinglist.delete'),
url(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'),
url(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'),
url(r'^checkinlists/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'),
url(r'^checkinlists/(?P<list>\d+)/$', checkin.CheckInListShow.as_view(), name='event.orders.checkinlists.show'),
url(r'^checkinlists/(?P<list>\d+)/change$', checkin.CheckinListUpdate.as_view(),
name='event.orders.checkinlists.edit'),
url(r'^checkinlists/(?P<list>\d+)/delete$', checkin.CheckinListDelete.as_view(),
name='event.orders.checkinlists.delete'),
re_path(r'^logout$', auth.logout, name='auth.logout'),
re_path(r'^login$', auth.login, name='auth.login'),
re_path(r'^login/2fa$', auth.Login2FAView.as_view(), name='auth.login.2fa'),
re_path(r'^register$', auth.register, name='auth.register'),
re_path(r'^invite/(?P<token>[a-zA-Z0-9]+)$', auth.invite, name='auth.invite'),
re_path(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'),
re_path(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'),
re_path(r'^$', dashboards.user_index, name='index'),
re_path(r'^widgets.json$', dashboards.user_index_widgets_lazy, name='index.widgets'),
re_path(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global.settings'),
re_path(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'),
re_path(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'),
re_path(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'),
re_path(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'),
re_path(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'),
re_path(r'^geocode/$', geo.GeoCodeView.as_view(), name='global.geocode'),
re_path(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'),
re_path(r'^sudo/$', user.StartStaffSession.as_view(), name='user.sudo'),
re_path(r'^sudo/stop/$', user.StopStaffSession.as_view(), name='user.sudo.stop'),
re_path(r'^sudo/(?P<id>\d+)/$', user.EditStaffSession.as_view(), name='user.sudo.edit'),
re_path(r'^sudo/sessions/$', user.StaffSessionList.as_view(), name='user.sudo.list'),
re_path(r'^users/$', users.UserListView.as_view(), name='users'),
re_path(r'^users/select2$', typeahead.users_select2, name='users.select2'),
re_path(r'^users/add$', users.UserCreateView.as_view(), name='users.add'),
re_path(r'^users/impersonate/stop', users.UserImpersonateStopView.as_view(), name='users.impersonate.stop'),
re_path(r'^users/(?P<id>\d+)/$', users.UserEditView.as_view(), name='users.edit'),
re_path(r'^users/(?P<id>\d+)/reset$', users.UserResetView.as_view(), name='users.reset'),
re_path(r'^users/(?P<id>\d+)/impersonate', users.UserImpersonateView.as_view(), name='users.impersonate'),
re_path(r'^users/(?P<id>\d+)/anonymize', users.UserAnonymizeView.as_view(), name='users.anonymize'),
re_path(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'),
re_path(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'),
re_path(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
re_path(r'^settings/notifications/$', user.UserNotificationsEditView.as_view(), name='user.settings.notifications'),
re_path(r'^settings/notifications/off/(?P<id>\d+)/(?P<token>[^/]+)/$', user.UserNotificationsDisableView.as_view(),
name='user.settings.notifications.off'),
re_path(r'^settings/oauth/authorized/$', oauth.AuthorizationListView.as_view(),
name='user.settings.oauth.list'),
re_path(r'^settings/oauth/authorized/(?P<pk>\d+)/revoke$', oauth.AuthorizationRevokeView.as_view(),
name='user.settings.oauth.revoke'),
re_path(r'^settings/oauth/apps/$', oauth.OAuthApplicationListView.as_view(),
name='user.settings.oauth.apps'),
re_path(r'^settings/oauth/apps/add$', oauth.OAuthApplicationRegistrationView.as_view(),
name='user.settings.oauth.apps.register'),
re_path(r'^settings/oauth/apps/(?P<pk>\d+)/$', oauth.OAuthApplicationUpdateView.as_view(),
name='user.settings.oauth.app'),
re_path(r'^settings/oauth/apps/(?P<pk>\d+)/disable$', oauth.OAuthApplicationDeleteView.as_view(),
name='user.settings.oauth.app.disable'),
re_path(r'^settings/oauth/apps/(?P<pk>\d+)/roll$', oauth.OAuthApplicationRollView.as_view(),
name='user.settings.oauth.app.roll'),
re_path(r'^settings/2fa/$', user.User2FAMainView.as_view(), name='user.settings.2fa'),
re_path(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'),
re_path(r'^settings/2fa/enable', user.User2FAEnableView.as_view(), name='user.settings.2fa.enable'),
re_path(r'^settings/2fa/disable', user.User2FADisableView.as_view(), name='user.settings.2fa.disable'),
re_path(r'^settings/2fa/regenemergency', user.User2FARegenerateEmergencyView.as_view(),
name='user.settings.2fa.regenemergency'),
re_path(r'^settings/2fa/totp/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmTOTPView.as_view(),
name='user.settings.2fa.confirm.totp'),
re_path(r'^settings/2fa/webauthn/(?P<device>[0-9]+)/confirm', user.User2FADeviceConfirmWebAuthnView.as_view(),
name='user.settings.2fa.confirm.webauthn'),
re_path(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),
name='user.settings.2fa.delete'),
re_path(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
re_path(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
re_path(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'),
re_path(r'^organizer/(?P<organizer>[^/]+)/$', organizer.OrganizerDetail.as_view(), name='organizer'),
re_path(r'^organizer/(?P<organizer>[^/]+)/edit$', organizer.OrganizerUpdate.as_view(), name='organizer.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/delete$', organizer.OrganizerDelete.as_view(), name='organizer.delete'),
re_path(r'^organizer/(?P<organizer>[^/]+)/settings/display$', organizer.OrganizerDisplaySettings.as_view(),
name='organizer.display'),
re_path(r'^organizer/(?P<organizer>[^/]+)/properties$', organizer.EventMetaPropertyListView.as_view(),
name='organizer.properties'),
re_path(r'^organizer/(?P<organizer>[^/]+)/property/add$', organizer.EventMetaPropertyCreateView.as_view(),
name='organizer.property.add'),
re_path(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/edit$',
organizer.EventMetaPropertyUpdateView.as_view(),
name='organizer.property.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/property/(?P<property>[^/]+)/delete$',
organizer.EventMetaPropertyDeleteView.as_view(),
name='organizer.property.delete'),
re_path(r'^organizer/(?P<organizer>[^/]+)/giftcards$', organizer.GiftCardListView.as_view(),
name='organizer.giftcards'),
re_path(r'^organizer/(?P<organizer>[^/]+)/giftcard/add$', organizer.GiftCardCreateView.as_view(),
name='organizer.giftcard.add'),
re_path(r'^organizer/(?P<organizer>[^/]+)/giftcard/(?P<giftcard>[^/]+)/$', organizer.GiftCardDetailView.as_view(),
name='organizer.giftcard'),
re_path(r'^organizer/(?P<organizer>[^/]+)/giftcard/(?P<giftcard>[^/]+)/edit$',
organizer.GiftCardUpdateView.as_view(),
name='organizer.giftcard.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/webhooks$', organizer.WebHookListView.as_view(),
name='organizer.webhooks'),
re_path(r'^organizer/(?P<organizer>[^/]+)/webhook/add$', organizer.WebHookCreateView.as_view(),
name='organizer.webhook.add'),
re_path(r'^organizer/(?P<organizer>[^/]+)/webhook/(?P<webhook>[^/]+)/edit$', organizer.WebHookUpdateView.as_view(),
name='organizer.webhook.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/webhook/(?P<webhook>[^/]+)/logs$', organizer.WebHookLogsView.as_view(),
name='organizer.webhook.logs'),
re_path(r'^organizer/(?P<organizer>[^/]+)/devices$', organizer.DeviceListView.as_view(), name='organizer.devices'),
re_path(r'^organizer/(?P<organizer>[^/]+)/device/add$', organizer.DeviceCreateView.as_view(),
name='organizer.device.add'),
re_path(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/edit$', organizer.DeviceUpdateView.as_view(),
name='organizer.device.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/connect$', organizer.DeviceConnectView.as_view(),
name='organizer.device.connect'),
re_path(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/revoke$', organizer.DeviceRevokeView.as_view(),
name='organizer.device.revoke'),
re_path(r'^organizer/(?P<organizer>[^/]+)/device/(?P<device>[^/]+)/logs$', organizer.DeviceLogView.as_view(),
name='organizer.device.logs'),
re_path(r'^organizer/(?P<organizer>[^/]+)/gates$', organizer.GateListView.as_view(), name='organizer.gates'),
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/add$', organizer.GateCreateView.as_view(),
name='organizer.gate.add'),
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/edit$', organizer.GateUpdateView.as_view(),
name='organizer.gate.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/delete$', organizer.GateDeleteView.as_view(),
name='organizer.gate.delete'),
re_path(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'),
re_path(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(),
name='organizer.team.add'),
re_path(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(),
name='organizer.team'),
re_path(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/edit$', organizer.TeamUpdateView.as_view(),
name='organizer.team.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
name='organizer.team.delete'),
re_path(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
re_path(r'^organizer/(?P<organizer>[^/]+)/logs', organizer.LogView.as_view(), name='organizer.log'),
re_path(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
re_path(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(),
name='organizer.export.do'),
re_path(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
re_path(r'^events/$', main.EventList.as_view(), name='events'),
re_path(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
re_path(r'^events/typeahead/$', typeahead.event_list, name='events.typeahead'),
re_path(r'^events/typeahead/meta/$', typeahead.meta_values, name='events.meta.typeahead'),
re_path(r'^search/orders/$', search.OrderSearch.as_view(), name='search.orders'),
re_path(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
re_path(r'^$', dashboards.event_index, name='event.index'),
re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),
re_path(r'^live/$', event.EventLive.as_view(), name='event.live'),
re_path(r'^logs/$', event.EventLog.as_view(), name='event.log'),
re_path(r'^delete/$', event.EventDelete.as_view(), name='event.delete'),
re_path(r'^requiredactions/$', event.EventActions.as_view(), name='event.requiredactions'),
re_path(r'^requiredactions/(?P<id>\d+)/discard$', event.EventActionDiscard.as_view(),
name='event.requiredaction.discard'),
re_path(r'^comment/$', event.EventComment.as_view(),
name='event.comment'),
re_path(r'^quickstart/$', event.QuickSetupView.as_view(), name='event.quick'),
re_path(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'),
re_path(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'),
re_path(r'^settings/payment/(?P<provider>[^/]+)$', event.PaymentProviderSettings.as_view(),
name='event.settings.payment.provider'),
re_path(r'^settings/payment$', event.PaymentSettings.as_view(), name='event.settings.payment'),
re_path(r'^settings/tickets$', event.TicketSettings.as_view(), name='event.settings.tickets'),
re_path(r'^settings/tickets/preview/(?P<output>[^/]+)$', event.TicketSettingsPreview.as_view(),
name='event.settings.tickets.preview'),
re_path(r'^settings/email$', event.MailSettings.as_view(), name='event.settings.mail'),
re_path(r'^settings/email/preview$', event.MailSettingsPreview.as_view(), name='event.settings.mail.preview'),
re_path(r'^settings/email/layoutpreview$', event.MailSettingsRendererPreview.as_view(),
name='event.settings.mail.preview.layout'),
re_path(r'^settings/cancel', event.CancelSettings.as_view(), name='event.settings.cancel'),
re_path(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'),
re_path(r'^settings/invoice/preview$', event.InvoicePreview.as_view(), name='event.settings.invoice.preview'),
re_path(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'),
re_path(r'^settings/tax/$', event.TaxList.as_view(), name='event.settings.tax'),
re_path(r'^settings/tax/(?P<rule>\d+)/$', event.TaxUpdate.as_view(), name='event.settings.tax.edit'),
re_path(r'^settings/tax/add$', event.TaxCreate.as_view(), name='event.settings.tax.add'),
re_path(r'^settings/tax/(?P<rule>\d+)/delete$', event.TaxDelete.as_view(), name='event.settings.tax.delete'),
re_path(r'^settings/widget$', event.WidgetSettings.as_view(), name='event.settings.widget'),
re_path(r'^pdf/editor/webfonts.css', pdf.FontsCSSView.as_view(), name='pdf.css'),
re_path(r'^pdf/editor/(?P<filename>[^/]+).pdf$', pdf.PdfView.as_view(), name='pdf.background'),
re_path(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
re_path(r'^subevents/select2$', typeahead.subevent_select2, name='event.subevents.select2'),
re_path(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
re_path(r'^subevents/(?P<subevent>\d+)/delete$', subevents.SubEventDelete.as_view(),
name='event.subevent.delete'),
re_path(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
re_path(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
re_path(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
re_path(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'),
re_path(r'^items/$', item.ItemList.as_view(), name='event.items'),
re_path(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
re_path(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
re_path(r'^items/(?P<item>\d+)/up$', item.item_move_up, name='event.items.up'),
re_path(r'^items/(?P<item>\d+)/down$', item.item_move_down, name='event.items.down'),
re_path(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
re_path(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
re_path(r'^items/select2/variation$', typeahead.variations_select2, name='event.items.variations.select2'),
re_path(r'^categories/$', item.CategoryList.as_view(), name='event.items.categories'),
re_path(r'^categories/select2$', typeahead.category_select2, name='event.items.categories.select2'),
re_path(r'^categories/(?P<category>\d+)/delete$', item.CategoryDelete.as_view(),
name='event.items.categories.delete'),
re_path(r'^categories/(?P<category>\d+)/up$', item.category_move_up, name='event.items.categories.up'),
re_path(r'^categories/(?P<category>\d+)/down$', item.category_move_down,
name='event.items.categories.down'),
re_path(r'^categories/(?P<category>\d+)/$', item.CategoryUpdate.as_view(),
name='event.items.categories.edit'),
re_path(r'^categories/add$', item.CategoryCreate.as_view(), name='event.items.categories.add'),
re_path(r'^questions/$', item.QuestionList.as_view(), name='event.items.questions'),
re_path(r'^questions/reorder$', item.reorder_questions, name='event.items.questions.reorder'),
re_path(r'^questions/(?P<question>\d+)/delete$', item.QuestionDelete.as_view(),
name='event.items.questions.delete'),
re_path(r'^questions/(?P<question>\d+)/$', item.QuestionView.as_view(),
name='event.items.questions.show'),
re_path(r'^questions/(?P<question>\d+)/change$', item.QuestionUpdate.as_view(),
name='event.items.questions.edit'),
re_path(r'^questions/add$', item.QuestionCreate.as_view(), name='event.items.questions.add'),
re_path(r'^quotas/$', item.QuotaList.as_view(), name='event.items.quotas'),
re_path(r'^quotas/(?P<quota>\d+)/$', item.QuotaView.as_view(), name='event.items.quotas.show'),
re_path(r'^quotas/select$', typeahead.quotas_select2, name='event.items.quotas.select2'),
re_path(r'^quotas/(?P<quota>\d+)/change$', item.QuotaUpdate.as_view(), name='event.items.quotas.edit'),
re_path(r'^quotas/(?P<quota>\d+)/delete$', item.QuotaDelete.as_view(),
name='event.items.quotas.delete'),
re_path(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'),
re_path(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'),
re_path(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'),
re_path(r'^vouchers/rng$', vouchers.VoucherRNG.as_view(), name='event.vouchers.rng'),
re_path(r'^vouchers/item_select$', typeahead.itemvarquota_select2, name='event.vouchers.itemselect2'),
re_path(r'^vouchers/(?P<voucher>\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'),
re_path(r'^vouchers/(?P<voucher>\d+)/delete$', vouchers.VoucherDelete.as_view(),
name='event.voucher.delete'),
re_path(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'),
re_path(r'^vouchers/go$', vouchers.VoucherGo.as_view(), name='event.vouchers.go'),
re_path(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'),
re_path(r'^vouchers/bulk_action$', vouchers.VoucherBulkAction.as_view(), name='event.vouchers.bulkaction'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
name='event.order.transition'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(),
name='event.order.resendlink'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/(?P<position>\d+)/resend$', orders.OrderResendLink.as_view(),
name='event.order.resendlink'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/invoice$', orders.OrderInvoiceCreate.as_view(),
name='event.order.geninvoice'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/invoices/(?P<id>\d+)/regenerate$',
orders.OrderInvoiceRegenerate.as_view(),
name='event.order.regeninvoice'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/invoices/(?P<id>\d+)/reissue$', orders.OrderInvoiceReissue.as_view(),
name='event.order.reissueinvoice'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/download/(?P<position>\d+)/(?P<output>[^/]+)/$',
orders.OrderDownload.as_view(),
name='event.order.download.ticket'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/answer/(?P<answer>[^/]+)/$',
orders.AnswerDownload.as_view(),
name='event.order.download.answer'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/checkvatid', orders.OrderCheckVATID.as_view(),
name='event.order.checkvatid'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
name='event.order.extend'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/reactivate$', orders.OrderReactivate.as_view(),
name='event.order.reactivate'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/contact$', orders.OrderContactChange.as_view(),
name='event.order.contact'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/locale', orders.OrderLocaleChange.as_view(),
name='event.order.locale'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/comment$', orders.OrderComment.as_view(),
name='event.order.comment'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/change$', orders.OrderChange.as_view(),
name='event.order.change'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/approve', orders.OrderApprove.as_view(),
name='event.order.approve'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/deny$', orders.OrderDeny.as_view(),
name='event.order.deny'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/delete$', orders.OrderDelete.as_view(),
name='event.order.delete'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/info', orders.OrderModifyInformation.as_view(),
name='event.order.info'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/sendmail$', orders.OrderSendMail.as_view(),
name='event.order.sendmail'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/(?P<position>[0-9A-Z]+)/sendmail$',
orders.OrderPositionSendMail.as_view(),
name='event.order.position.sendmail'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/mail_history$', orders.OrderEmailHistory.as_view(),
name='event.order.mail_history'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/payments/(?P<payment>\d+)/cancel$', orders.OrderPaymentCancel.as_view(),
name='event.order.payments.cancel'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/payments/(?P<payment>\d+)/confirm$', orders.OrderPaymentConfirm.as_view(),
name='event.order.payments.confirm'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/refund$', orders.OrderRefundView.as_view(),
name='event.order.refunds.start'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/refunds/(?P<refund>\d+)/cancel$', orders.OrderRefundCancel.as_view(),
name='event.order.refunds.cancel'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/refunds/(?P<refund>\d+)/process$', orders.OrderRefundProcess.as_view(),
name='event.order.refunds.process'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/refunds/(?P<refund>\d+)/done$', orders.OrderRefundDone.as_view(),
name='event.order.refunds.done'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/cancellationrequests/(?P<req>\d+)/delete$',
orders.OrderCancellationRequestDelete.as_view(),
name='event.order.cancellationrequests.delete'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'),
re_path(r'^invoice/(?P<invoice>[^/]+)$', orders.InvoiceDownload.as_view(),
name='event.invoice.download'),
re_path(r'^orders/overview/$', orders.OverView.as_view(), name='event.orders.overview'),
re_path(r'^orders/import/$', orderimport.ImportView.as_view(), name='event.orders.import'),
re_path(r'^orders/import/(?P<file>[^/]+)/$', orderimport.ProcessView.as_view(),
name='event.orders.import.process'),
re_path(r'^orders/export/$', orders.ExportView.as_view(), name='event.orders.export'),
re_path(r'^orders/export/do$', orders.ExportDoView.as_view(), name='event.orders.export.do'),
re_path(r'^orders/refunds/$', orders.RefundList.as_view(), name='event.orders.refunds'),
re_path(r'^orders/go$', orders.OrderGo.as_view(), name='event.orders.go'),
re_path(r'^orders/$', orders.OrderList.as_view(), name='event.orders'),
re_path(r'^orders/search$', orders.OrderSearch.as_view(), name='event.orders.search'),
re_path(r'^dangerzone/$', event.DangerZone.as_view(), name='event.dangerzone'),
re_path(r'^cancel/$', orders.EventCancel.as_view(), name='event.cancel'),
re_path(r'^shredder/$', shredder.StartShredView.as_view(), name='event.shredder.start'),
re_path(r'^shredder/export$', shredder.ShredExportView.as_view(), name='event.shredder.export'),
re_path(r'^shredder/download/(?P<file>[^/]+)/$', shredder.ShredDownloadView.as_view(),
name='event.shredder.download'),
re_path(r'^shredder/shred', shredder.ShredDoView.as_view(), name='event.shredder.shred'),
re_path(r'^waitinglist/$', waitinglist.WaitingListView.as_view(), name='event.orders.waitinglist'),
re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'),
re_path(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(),
name='event.orders.waitinglist.delete'),
re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'),
re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'),
re_path(r'^checkinlists/select2$', typeahead.checkinlist_select2, name='event.orders.checkinlists.select2'),
re_path(r'^checkinlists/(?P<list>\d+)/$', checkin.CheckInListShow.as_view(),
name='event.orders.checkinlists.show'),
re_path(r'^checkinlists/(?P<list>\d+)/change$', checkin.CheckinListUpdate.as_view(),
name='event.orders.checkinlists.edit'),
re_path(r'^checkinlists/(?P<list>\d+)/delete$', checkin.CheckinListDelete.as_view(),
name='event.orders.checkinlists.delete'),
])),
url(r'^event/(?P<organizer>[^/]+)/$', RedirectView.as_view(pattern_name='control:organizer'), name='event.organizerredirect'),
re_path(r'^event/(?P<organizer>[^/]+)/$', RedirectView.as_view(pattern_name='control:organizer'),
name='event.organizerredirect'),
]

View File

@@ -1,4 +1,4 @@
import collections
import collections.abc
import warnings
from django.core.paginator import (
@@ -65,7 +65,7 @@ class PaginationMixin:
return ctx
class LargeResultSetPage(collections.Sequence):
class LargeResultSetPage(collections.abc.Sequence):
def __init__(self, object_list, number, paginator):
self.object_list = object_list

View File

@@ -955,10 +955,11 @@ class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixi
return reverse('control:index')
class EventLog(EventPermissionRequiredMixin, PaginationMixin, ListView):
class EventLog(EventPermissionRequiredMixin, ListView):
template_name = 'pretixcontrol/event/logs.html'
model = LogEntry
context_object_name = 'logs'
paginate_by = 20
def get_queryset(self):
qs = self.request.event.logentry_set.all().select_related(
@@ -1362,7 +1363,7 @@ class QuickSetupView(FormView):
tax_rule=tax_rule,
admission=True,
position=i,
sales_channels=list(get_all_sales_channels().keys())
sales_channels=[k for k in get_all_sales_channels().keys()]
)
item.log_action('pretix.event.item.added', user=self.request.user, data=dict(f.cleaned_data))
if f.cleaned_data['quota'] or not form.cleaned_data['total_quota']:

View File

@@ -83,7 +83,7 @@ from pretix.control.forms.orders import (
ExtendForm, MarkPaidForm, OrderContactForm, OrderFeeChangeForm,
OrderLocaleForm, OrderMailForm, OrderPositionAddForm,
OrderPositionAddFormset, OrderPositionChangeForm, OrderPositionMailForm,
OrderRefundForm, OtherOperationsForm, ReactivateOrderForm,
OrderRefundForm, OtherOperationsForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.signals import order_search_forms
@@ -1424,24 +1424,11 @@ class OrderExtend(OrderView):
class OrderReactivate(OrderView):
permission = 'can_change_orders'
@cached_property
def reactivate_form(self):
return ReactivateOrderForm(
instance=self.order,
data=self.request.POST if self.request.method == "POST" else None,
)
def post(self, *args, **kwargs):
if not self.reactivate_form.is_valid():
return render(self.request, 'pretixcontrol/order/reactivate.html', {
'form': self.reactivate_form,
'order': self.order,
})
try:
reactivate_order(
self.order,
user=self.request.user,
force=self.reactivate_form.cleaned_data.get('force', False)
user=self.request.user
)
messages.success(self.request, _('The order has been reactivated.'))
except OrderError as e:
@@ -1466,7 +1453,6 @@ class OrderReactivate(OrderView):
def get(self, *args, **kwargs):
return render(self.request, 'pretixcontrol/order/reactivate.html', {
'form': self.reactivate_form,
'order': self.order,
})

View File

@@ -1438,11 +1438,12 @@ class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionR
return redirect(success_url)
class LogView(OrganizerPermissionRequiredMixin, PaginationMixin, ListView):
class LogView(OrganizerPermissionRequiredMixin, ListView):
template_name = 'pretixcontrol/organizers/logs.html'
permission = 'can_change_organizer_settings'
model = LogEntry
context_object_name = 'logs'
paginate_by = 20
def get_queryset(self):
qs = self.request.organizer.all_logentries().select_related(

View File

@@ -3,8 +3,7 @@ import io
from defusedcsv import csv
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.db import connection, transaction
from django.db import transaction
from django.db.models import Sum
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
@@ -22,9 +21,7 @@ from django.views.generic import (
from pretix.base.models import CartPosition, LogEntry, OrderPosition, Voucher
from pretix.base.models.vouchers import _generate_random_code
from pretix.base.services.locking import NoLockManager
from pretix.base.services.vouchers import vouchers_send
from pretix.base.views.tasks import AsyncFormView
from pretix.control.forms.filter import VoucherFilterForm, VoucherTagFilterForm
from pretix.control.forms.vouchers import VoucherBulkForm, VoucherForm
from pretix.control.permissions import EventPermissionRequiredMixin
@@ -290,19 +287,13 @@ class VoucherGo(EventPermissionRequiredMixin, View):
return redirect('control:event.vouchers', event=request.event.slug, organizer=request.event.organizer.slug)
class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
model = Voucher
template_name = 'pretixcontrol/vouchers/bulk.html'
permission = 'can_change_vouchers'
context_object_name = 'voucher'
def get_success_url(self, value) -> str:
return reverse('control:event.vouchers', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
def get_error_url(self):
def get_success_url(self) -> str:
return reverse('control:event.vouchers', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
@@ -325,84 +316,34 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
i.redeemed = 0
kwargs['instance'] = i
else:
kwargs['instance'] = Voucher(event=self.request.event, code=None)
kwargs['instance'] = Voucher(event=self.request.event)
return kwargs
def get_async_form_kwargs(self, form_kwargs, organizer=None, event=None):
if not form_kwargs.get('instance'):
form_kwargs['instance'] = Voucher(event=self.request.event, code=None)
return form_kwargs
def async_form_valid(self, task, form):
lockfn = NoLockManager
if form.data.get('block_quota'):
lockfn = self.request.event.lock
batch_size = 500
total_num = 1 # will be set later
def set_progress(percent):
if not task.request.called_directly:
task.update_state(
state='PROGRESS',
meta={'value': percent}
)
def process_batch(batch_vouchers, voucherids):
Voucher.objects.bulk_create(batch_vouchers)
if not connection.features.can_return_rows_from_bulk_insert:
batch_vouchers = list(self.request.event.vouchers.filter(code__in=[v.code for v in batch_vouchers]))
log_entries = []
for v in batch_vouchers:
voucherids.append(v.pk)
data = dict(form.cleaned_data)
data['code'] = code
data['bulk'] = True
del data['codes']
log_entries.append(
v.log_action('pretix.voucher.added', data=data, user=self.request.user, save=False)
)
LogEntry.objects.bulk_create(log_entries)
form.post_bulk_save(batch_vouchers)
batch_vouchers.clear()
set_progress(len(voucherids) / total_num * (50. if form.cleaned_data['send'] else 100.))
@transaction.atomic
def form_valid(self, form):
log_entries = []
objs = form.save(self.request.event)
voucherids = []
with lockfn(), transaction.atomic():
if not form.is_valid():
raise ValidationError(form.errors)
total_num = len(form.cleaned_data['codes'])
batch_vouchers = []
for code in form.cleaned_data['codes']:
if len(batch_vouchers) > batch_size:
process_batch(batch_vouchers, voucherids)
obj = modelcopy(form.instance, code=None)
obj.event = self.request.event
obj.code = code
try:
obj.seat = form.cleaned_data['seats'].pop()
obj.item = obj.seat.product
except IndexError:
pass
batch_vouchers.append(obj)
process_batch(batch_vouchers, voucherids)
for v in objs:
log_entries.append(
v.log_action('pretix.voucher.added', data=form.cleaned_data, user=self.request.user, save=False)
)
voucherids.append(v.pk)
LogEntry.objects.bulk_create(log_entries, batch_size=200)
if form.cleaned_data['send']:
vouchers_send(
event=self.request.event,
vouchers=voucherids,
subject=form.cleaned_data['send_subject'],
message=form.cleaned_data['send_message'],
recipients=[r._asdict() for r in form.cleaned_data['send_recipients']],
user=self.request.user.pk,
progress=lambda p: set_progress(50. + p * 50.)
)
def get_success_message(self, value):
return _('The new vouchers have been created.')
vouchers_send.apply_async(kwargs={
'event': self.request.event.pk,
'vouchers': voucherids,
'subject': form.cleaned_data['send_subject'],
'message': form.cleaned_data['send_message'],
'recipients': [r._asdict() for r in form.cleaned_data['send_recipients']],
'user': self.request.user.pk,
})
messages.success(self.request, _('The new vouchers have been created and will be sent out shortly.'))
else:
messages.success(self.request, _('The new vouchers have been created.'))
return HttpResponseRedirect(self.get_success_url())
def get_form_class(self):
form_class = VoucherBulkForm
@@ -416,6 +357,11 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
ctx['code_length'] = settings.ENTROPY['voucher_code']
return ctx
def post(self, request, *args, **kwargs):
# TODO: Transform this into an asynchronous call?
with request.event.lock():
return super().post(request, *args, **kwargs)
class VoucherRNG(EventPermissionRequiredMixin, View):
permission = 'can_change_vouchers'

View File

@@ -94,7 +94,7 @@ def merge(*args):
"""Implements the 'merge' operator for merging lists."""
ret = []
for arg in args:
if isinstance(arg, (list, tuple)):
if isinstance(arg, list) or isinstance(arg, tuple):
ret += list(arg)
else:
ret.append(arg)

View File

@@ -1,5 +1,6 @@
import copy
from django.core.files import File
from django.db import models
@@ -12,11 +13,12 @@ class Thumbnail(models.Model):
unique_together = (('source', 'size'),)
def modelcopy(obj: models.Model, **kwargs):
n = obj.__class__(**kwargs)
def modelcopy(obj: models.Model):
n = obj.__class__()
for f in obj._meta.fields:
val = getattr(obj, f.name)
if isinstance(val, models.Model):
print(f, f.name, val)
if isinstance(val, (models.Model, File)):
setattr(n, f.name, copy.copy(val))
else:
setattr(n, f.name, copy.deepcopy(val))

View File

@@ -7,16 +7,16 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-08 16:39+0000\n"
"PO-Revision-Date: 2021-03-14 17:33+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\n"
"PO-Revision-Date: 2021-01-29 11:23+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
">\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.4.2\n"
"X-Generator: Weblate 3.10.3\n"
#: htmlcov/pretix_control_views_dashboards_py.html:898
#: pretix/control/templates/pretixcontrol/events/index.html:144
@@ -154,10 +154,10 @@ msgid "Meta data property '{name}' does not exist."
msgstr "Metadataeigenschap '{name}' bestaat niet."
#: pretix/api/serializers/event.py:182 pretix/api/serializers/event.py:466
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Meta data property '{name}' does not exist."
msgid "Meta data property '{name}' does not allow value '{value}'."
msgstr ""
"De waarde '{value}' is niet toegestaan voor de metadataeigenschap '{name}'."
msgstr "Metadataeigenschap '{name}' bestaat niet."
#: pretix/api/serializers/event.py:225
#, python-brace-format
@@ -2246,7 +2246,7 @@ msgstr "U moet ten minste één quotum instellen om iets te verkopen."
#: pretix/base/models/event.py:960
#, python-brace-format
msgid "You need to fill the meta parameter \"{property}\"."
msgstr "U moet de meta-eigenschap \"{property}\" invullen."
msgstr ""
#: pretix/base/models/event.py:1065
msgid ""
@@ -2324,49 +2324,50 @@ msgstr ""
"onderstrepingstekens bevatten."
#: pretix/base/models/event.py:1379
#, fuzzy
#| msgid "Default language"
msgid "Default value"
msgstr "Standaardwaarde"
msgstr "Standaardtaal"
#: pretix/base/models/event.py:1381
#, fuzzy
#| msgid "Can change organizer settings"
msgid "Can only be changed by organizer-level administrators"
msgstr "Kan alleen worden gewijzigd door beheerders van deze organisator"
msgstr "Kan organisatorinstellingen wijzigen"
#: pretix/base/models/event.py:1383
#, fuzzy
#| msgid "Search for events"
msgid "Required for events"
msgstr "Verplicht voor evenementen"
msgstr "Zoek naar evenementen"
#: pretix/base/models/event.py:1384
msgid ""
"If checked, an event can only be taken live if the property is set. In event "
"series, its always optional to set a value for individual dates"
msgstr ""
"Als deze optie is ingeschakeld kan een evenement alleen live worden gezet "
"als deze eigenschap een waarde heeft. In evenementenreeksen is het altijd "
"optioneel om een waarde voor individuele datums in te stellen."
#: pretix/base/models/event.py:1389
#, fuzzy
#| msgid "Total value"
msgid "Valid values"
msgstr "Toegestane waarden"
msgstr "Totaalwaarde"
#: pretix/base/models/event.py:1390
msgid ""
"If you keep this empty, any value is allowed. Otherwise, enter one possible "
"value per line."
msgstr ""
"Voer hier een toegestane waarde per regel in. Als u dit veld leeg laat wordt "
"iedere waarde toegestaan."
#: pretix/base/models/event.py:1396
msgid "A property can either be required or have a default value, not both."
msgstr ""
"Een eigenschap kan niet verplicht zijn en tegelijkertijd een standaardwaarde "
"hebben."
#: pretix/base/models/event.py:1398
#, fuzzy
#| msgid "You cannot select a quota that belongs to a different event."
msgid "You cannot set a default value that is not a valid value."
msgstr ""
"U kunt geen standaardwaarde instellen die niet in de lijst met toegestane "
"waarden staat."
msgstr "U kunt geen quotum selecteren dat bij een ander evenement hoort."
#: pretix/base/models/fields.py:12
msgid "No value can contain the delimiter character."
@@ -4420,7 +4421,12 @@ msgid "Payment process description in order confirmation emails"
msgstr "Beschrijving van betalingsproces in bevestigingsmails"
#: pretix/base/payment.py:971
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid ""
#| "This text will be included for the {payment_info} placeholder in order "
#| "confirmation mails. It should instruct the user on how to proceed with "
#| "the payment. You can use the placeholders {order}, {total}, {currency} "
#| "and {total_with_currency}."
msgid ""
"This text will be included for the {payment_info} placeholder in order "
"confirmation mails. It should instruct the user on how to proceed with the "
@@ -4430,7 +4436,7 @@ msgstr ""
"Deze tekst zal worden ingevoegd op de plaats van de {payment_info}-"
"plaatsaanduiding in bevestigingsmails voor een bestelling. De tekst moet de "
"gebruiker informeren hoe verder te gaan met de betaling. U kunt hier de "
"plaatsaanduidingen {order}, {amount}, {currency} en {amount_with_currency} "
"plaatsaanduidingen {order}, {total}, {currency} en {total_with_currency} "
"gebruiken."
#: pretix/base/payment.py:978
@@ -4438,7 +4444,12 @@ msgid "Payment process description for pending orders"
msgstr "Beschrijving van betalingsproces voor openstaande bestellingen"
#: pretix/base/payment.py:979
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid ""
#| "This text will be shown on the order confirmation page for pending "
#| "orders. It should instruct the user on how to proceed with the payment. "
#| "You can use the placeholders {order}, {total}, {currency} and "
#| "{total_with_currency}."
msgid ""
"This text will be shown on the order confirmation page for pending orders. "
"It should instruct the user on how to proceed with the payment. You can use "
@@ -4446,8 +4457,8 @@ msgid ""
msgstr ""
"Deze tekst zal worden getoond op de bevestigingspagina van openstaande "
"bestellingen. De tekst moet de gebruiker informeren hoe verder te gaan met "
"de betaling. U kunt hier de plaatsaanduidingen {order}, {amount}, {currency} "
"en {amount_with_currency} gebruiken."
"de betaling. U kunt hier de plaatsaanduidingen {order}, {total}, {currency} "
"en {total_with_currency} gebruiken."
#: pretix/base/payment.py:1028
msgid "Offsetting"
@@ -4793,11 +4804,13 @@ msgstr "Informatietekst organisator"
#: pretix/base/pdf.py:298
msgid "Event organizer info text"
msgstr "Informatie over de organisator van het evenement"
msgstr "Informatietekst van de organisator van het evenement"
#: pretix/base/pdf.py:302 pretix/base/pdf.py:303
#, fuzzy
#| msgid "Event organizer info text"
msgid "Event info text"
msgstr "Informatie over het evenement"
msgstr "Informatietekst van de organisator van het evenement"
#: pretix/base/pdf.py:307
msgid "Printing date"
@@ -6441,52 +6454,64 @@ msgstr ""
"aan de volgende persoon op de lijst."
#: pretix/base/settings.py:984
#, fuzzy
#| msgid "Ask for attendee names"
msgid "Ask for a name"
msgstr "Vraag om namen"
msgstr "Vraag om namen van gasten"
#: pretix/base/settings.py:985
#, fuzzy
#| msgid "An entry has been changed on the waiting list."
msgid "Ask for a name when signing up to the waiting list."
msgstr "Vraag om een naam bij het aanmelden voor de wachtlijst."
msgstr "Een inschrijving op de wachtlijst is aangepast."
#: pretix/base/settings.py:994
#, fuzzy
#| msgid "Require customer name"
msgid "Require name"
msgstr "Verplicht namen"
msgstr "Verplicht klantnaam"
#: pretix/base/settings.py:995
#, fuzzy
#| msgid "An entry has been changed on the waiting list."
msgid "Require a name when signing up to the waiting list.."
msgstr ""
"Maakt het opgeven van een naam verplicht om in te schrijven voor de "
"wachtlijst."
msgstr "Een inschrijving op de wachtlijst is aangepast."
#: pretix/base/settings.py:1005
#, fuzzy
#| msgid "Ask for a phone number per order"
msgid "Ask for a phone number"
msgstr "Vraag om een telefoonnummer"
msgstr "Vraag om een telefoonnummer bij bestelling"
#: pretix/base/settings.py:1006
#, fuzzy
#| msgid "An entry has been changed on the waiting list."
msgid "Ask for a phone number when signing up to the waiting list."
msgstr "Vraag om een telefoonnummer bij het inschrijven voor de wachtlijst."
msgstr "Een inschrijving op de wachtlijst is aangepast."
#: pretix/base/settings.py:1015
#, fuzzy
#| msgid "Require a phone number per order"
msgid "Require phone number"
msgstr "Verplicht telefoonnummer"
msgstr "Verplicht het opgeven van een telefoonnummer"
#: pretix/base/settings.py:1016
#, fuzzy
#| msgid "An entry has been changed on the waiting list."
msgid "Require a phone number when signing up to the waiting list.."
msgstr ""
"Maakt het opgeven van een telefoonnummer verplicht bij het inschrijven voor "
"de wachtlijst."
msgstr "Een inschrijving op de wachtlijst is aangepast."
#: pretix/base/settings.py:1026
#, fuzzy
#| msgid "Voucher explanation"
msgid "Phone number explanation"
msgstr "Uitleg voor telefoonnummer"
msgstr "Voucher-uitleg"
#: pretix/base/settings.py:1029
msgid ""
"If you ask for a phone number, explain why you do so and what you will use "
"the phone number for."
msgstr ""
"Als u om een telefoonnummer vraagt kunt u in dit veld uitleggen waarom en "
"hoe u de verzamelde telefoonnummers zult gebruiken."
#: pretix/base/settings.py:1039
msgid "Allow users to download tickets"
@@ -6616,7 +6641,6 @@ msgstr ""
#: pretix/base/settings.py:1147
msgid "Allow customers to modify their information after they checked in."
msgstr ""
"Sta klanten toe om hun informatie aan te passen nadat ze ingecheckt zijn."
#: pretix/base/settings.py:1156
msgid "Last date of modifications"
@@ -6812,8 +6836,10 @@ msgstr ""
"contactinformatie en eventuele wettelijk verplichte informatie bevat."
#: pretix/base/settings.py:1387
#, fuzzy
#| msgid "Cached ticket files"
msgid "Attach ticket files"
msgstr "Ticketbestanden bijvoegen bij e-mails"
msgstr "Gecachete ticketbestanden"
#: pretix/base/settings.py:1389
#, python-brace-format
@@ -6821,8 +6847,6 @@ msgid ""
"Tickets will never be attached if they're larger than {size} to avoid email "
"delivery problems."
msgstr ""
"Tickets worden nooit bijgevoegd bij een e-mail als ze groter zijn dan {size}"
", om problemen met het versturen van de e-mail te voorkomen."
#: pretix/base/settings.py:1400
msgid "Attach calendar files"
@@ -7559,16 +7583,22 @@ msgstr ""
"informatie vraagt."
#: pretix/base/settings.py:2073
#, fuzzy
#| msgid "Additional fee"
msgid "Additional success message"
msgstr "Extra succesbericht na het plaatsen van een bestelling"
msgstr "Extra kosten"
#: pretix/base/settings.py:2074
#, fuzzy
#| msgid ""
#| "This text will be shown on the order confirmation page for pending orders "
#| "in addition to the standard text."
msgid ""
"This message will be shown after an order has been created successfully. It "
"will be shown in additional to the default text."
msgstr ""
"Deze tekst zal worden getoond nadat een klant een bestelling heeft "
"geplaatst. Deze tekst wordt samen met de standaardtekst getoond."
"Deze tekst zal naast de standaardtekst worden getoond op de "
"bevestigingspagina voor openstaande bestellingen."
#: pretix/base/settings.py:2086
msgid "Help text of the phone number field"
@@ -7853,12 +7883,12 @@ msgstr ""
"met opgeslagen emailinhoud."
#: pretix/base/shredder.py:206
#, fuzzy
#| msgid "This will remove all email addresses from the waiting list."
msgid ""
"This will remove all names, email addresses, and phone numbers from the "
"waiting list."
msgstr ""
"Dit zal alle namen, e-mailadressen en telefoonnummers van de wachtlijst "
"verwijderen."
msgstr "Dit zal alle e-mailadressen van de wachtlijst verwijderen."
#: pretix/base/shredder.py:239
msgid "Attendee info"
@@ -8063,18 +8093,25 @@ msgid "Order code:"
msgstr "Bestelcode:"
#: pretix/base/templates/pretixbase/email/order_details.html:30
#, fuzzy
#| msgctxt "payment_state"
#| msgid "created"
msgid "created by"
msgstr "aangemaakt door"
msgstr "aangemaakt"
#: pretix/base/templates/pretixbase/email/order_details.html:36
#: pretix/base/templates/pretixbase/email/order_details.html:92
#, fuzzy
#| msgid "Order status"
msgid "Order status:"
msgstr "Bestelstatus:"
msgstr "Bestelstatus"
#: pretix/base/templates/pretixbase/email/order_details.html:44
#: pretix/base/templates/pretixbase/email/order_details.html:140
#, fuzzy
#| msgid "Organizer"
msgid "Organizer:"
msgstr "Organisator:"
msgstr "Organisator"
#: pretix/base/templates/pretixbase/email/order_details.html:59
msgid "View registration details"
@@ -8089,8 +8126,10 @@ msgstr ""
"geplaatst:"
#: pretix/base/templates/pretixbase/email/order_details.html:101
#, fuzzy
#| msgid "Details"
msgid "Details:"
msgstr "Details:"
msgstr "Details"
#: pretix/base/templates/pretixbase/forms/widgets/reldate.html:15
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:19
@@ -8340,9 +8379,10 @@ msgid "Do not copy"
msgstr "Niet kopiëren"
#: pretix/control/forms/event.py:274 pretix/control/forms/subevents.py:309
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Default language"
msgid "Default ({value})"
msgstr "Standaard ({value})"
msgstr "Standaardtaal"
#: pretix/control/forms/event.py:326 pretix/control/forms/organizer.py:84
msgid "Custom domain"
@@ -8862,7 +8902,7 @@ msgstr "Annulering aangevraagd"
#: pretix/control/forms/filter.py:158
msgid "Fully canceled but invoice not canceled"
msgstr "Compleet geannuleerd, maar factuur niet geannuleerd"
msgstr ""
#: pretix/control/forms/filter.py:160
msgid "Payment process"
@@ -9492,8 +9532,10 @@ msgstr ""
"betalingen worden uitgevoerd."
#: pretix/control/forms/orders.py:107 pretix/control/forms/orders.py:151
#, fuzzy
#| msgid "Notify user by e-mail"
msgid "Notify customer by email"
msgstr "Stel de klant per e-mail op de hoogte"
msgstr "Stel de gebruiker per e-mail op de hoogte"
#: pretix/control/forms/orders.py:114
msgid "Keep a cancellation fee of"
@@ -9514,8 +9556,10 @@ msgstr ""
"betalen. Voer een bruto bedrag in, belasting zal automatisch worden berekend."
#: pretix/control/forms/orders.py:121
#, fuzzy
#| msgid "Generate cancellation"
msgid "Generate cancellation for invoice"
msgstr "Genereer annulering voor factuur"
msgstr "Genereer annulering"
#: pretix/control/forms/orders.py:158
msgid "Payment amount"
@@ -9890,17 +9934,21 @@ msgstr "Optioneel"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:48
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:181
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:266
#, fuzzy
#| msgid "Change"
msgctxt "form_bulk"
msgid "change"
msgstr "veranderen"
msgstr "Veranderen"
#: pretix/control/forms/subevents.py:96
#, fuzzy
#| msgid "Current value"
msgid "Keep the current values"
msgstr "Houd huidige waarden"
msgstr "Huidige waarde"
#: pretix/control/forms/subevents.py:113 pretix/control/forms/subevents.py:121
msgid "Selection contains various values"
msgstr "Selectie bevat verschillende waarden"
msgstr ""
#: pretix/control/forms/subevents.py:369
msgid "Exclude these dates instead of adding them."
@@ -10311,28 +10359,40 @@ msgid "This object has been created by cloning."
msgstr "Dit object is aangemaakt via kopiëren."
#: pretix/control/logdisplay.py:276
#, fuzzy
#| msgid "The order has been changed."
msgid "The organizer has been changed."
msgstr "De organisator is veranderd."
msgstr "De bestelling is aangepast."
#: pretix/control/logdisplay.py:277
#, fuzzy
#| msgid "The team settings have been changed."
msgid "The organizer settings have been changed."
msgstr "De instellingen van de organisator zijn aangepast."
msgstr "De teaminstellingen zijn aangepast."
#: pretix/control/logdisplay.py:278
#, fuzzy
#| msgid "The new organizer has been created."
msgid "Gift card acceptance for another organizer has been added."
msgstr "Cadeaubonacceptatie voor een andere organisator is toegevoegd."
msgstr "De nieuwe organisator is aangemaakt."
#: pretix/control/logdisplay.py:279
#, fuzzy
#| msgid "The new organizer has been created."
msgid "Gift card acceptance for another organizer has been removed."
msgstr "Cadeaubonacceptatie voor een andere organisator is verwijderd."
msgstr "De nieuwe organisator is aangemaakt."
#: pretix/control/logdisplay.py:280
#, fuzzy
#| msgid "The user has been created."
msgid "The webhook has been created."
msgstr "De webhook is aangemaakt."
msgstr "De gebruiker is aangemaakt."
#: pretix/control/logdisplay.py:281
#, fuzzy
#| msgid "The gate has been changed."
msgid "The webhook has been changed."
msgstr "De webhook is aangepast."
msgstr "De toegangslocatie is aangepast."
#: pretix/control/logdisplay.py:282
msgid "The event's internal comment has been updated."
@@ -10343,8 +10403,10 @@ msgid "The event has been canceled."
msgstr "Het evenement is geannuleerd."
#: pretix/control/logdisplay.py:284
#, fuzzy
#| msgid "The event has been deleted."
msgid "An event has been deleted."
msgstr "Een webhook is verwijderd."
msgstr "Dit evenement is verwijderd."
#: pretix/control/logdisplay.py:285
msgid "The order details have been changed."
@@ -12375,19 +12437,25 @@ msgstr "Geocoding-data © OpenStreetMap"
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:4
msgid "Failed to retrieve geo coordinates"
msgstr "Geo-coördinaten ophalen mislukt"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:5
#, fuzzy
#| msgid "Geo coordinates"
msgid "Retrieving geo coordinates …"
msgstr "Geo-coördinaten ophalen…"
msgstr "Geo-coördinaten"
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:6
#, fuzzy
#| msgid "Geo coordinates"
msgid "Geo coordinates updated"
msgstr "Geo-coördinaten bijgewerkt"
msgstr "Geo-coördinaten"
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:7
#, fuzzy
#| msgid "Update comment"
msgid "Update map?"
msgstr "Kaart bijwerken?"
msgstr "Commentaar bijwerken"
#: pretix/control/templates/pretixcontrol/event/fragment_timeline.html:5
msgid "Your timeline"
@@ -15496,7 +15564,7 @@ msgstr "VOLLEDIG BETAALD"
#: pretix/control/templates/pretixcontrol/orders/index.html:175
msgid "INVOICE NOT CANCELED"
msgstr "FACTUUR NIET GEANNULEERD"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/index.html:186
msgid "Sum over all pages"
@@ -15882,8 +15950,10 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/logs.html:4
#: pretix/control/templates/pretixcontrol/organizers/logs.html:6
#, fuzzy
#| msgid "Organizers"
msgid "Organizer logs"
msgstr "Organisatorlogs"
msgstr "Organisatoren"
#: pretix/control/templates/pretixcontrol/organizers/properties.html:7
msgid ""
@@ -15897,24 +15967,34 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/properties.html:15
#: pretix/control/templates/pretixcontrol/organizers/property_edit.html:8
#, fuzzy
#| msgid "Create a new product"
msgid "Create a new property"
msgstr "Nieuwe eigenschap aanmaken"
msgstr "Nieuw product aanmaken"
#: pretix/control/templates/pretixcontrol/organizers/properties.html:20
#, fuzzy
#| msgid "Add property"
msgid "Property"
msgstr "Eigenschap"
msgstr "Eigenschap toevoegen"
#: pretix/control/templates/pretixcontrol/organizers/property_delete.html:5
#, fuzzy
#| msgid "Delete product"
msgid "Delete property:"
msgstr "Verwijder eigenschap:"
msgstr "Product verwijderen"
#: pretix/control/templates/pretixcontrol/organizers/property_delete.html:8
#, fuzzy
#| msgid "Are you sure you want to delete the gate?"
msgid "Are you sure you want to delete the property?"
msgstr "Weet u zeker dat u de eigenschap wilt verwijderen?"
msgstr "Weet u zeker dat u de toegangslocatie wilt verwijderen?"
#: pretix/control/templates/pretixcontrol/organizers/property_edit.html:6
#, fuzzy
#| msgid "Add property"
msgid "Property:"
msgstr "Eigenschap:"
msgstr "Eigenschap toevoegen"
#: pretix/control/templates/pretixcontrol/organizers/team_delete.html:5
msgid "Delete team:"
@@ -16294,8 +16374,10 @@ msgid "Light"
msgstr "Licht"
#: pretix/control/templates/pretixcontrol/pdf/index.html:349
#, fuzzy
#| msgid "E-mail content"
msgid "Image content"
msgstr "Afbeeldingsinhoud"
msgstr "E-mailinhoud"
#: pretix/control/templates/pretixcontrol/pdf/index.html:360
msgid "Text content"
@@ -16331,7 +16413,7 @@ msgstr "pretix-logo"
#: pretix/control/templates/pretixcontrol/pdf/index.html:410
msgid "Dynamic image"
msgstr "Dynamische afbeelding"
msgstr ""
#: pretix/control/templates/pretixcontrol/search/orders.html:104
msgid ""
@@ -16584,14 +16666,18 @@ msgstr "Voeg een nieuwe inchecklijst toe"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:8
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:11
#, fuzzy
#| msgctxt "subevent"
#| msgid "Create multiple dates"
msgctxt "subevent"
msgid "Change multiple dates"
msgstr "Meerdere datums veranderen"
msgstr "Maak meerdere datums aan"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:13
#, python-format
#, fuzzy, python-format
#| msgid "Enable selected"
msgid "%(number)s selected"
msgstr "%(number)s geselecteerd"
msgstr "Schakel geselecteerde in"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:171
msgid ""
@@ -16600,18 +16686,12 @@ msgid ""
"new set of quotas to <strong>replace</strong> the quota setup of all "
"selected dates."
msgstr ""
"De quotuminstellingen van de gekozen datums verschillen en kunnen hierom "
"niet in één keer aangepast worden. Als u dit wilt kunt u wel nieuwe quota "
"definiëren om de quotuminstellingen van de geselecteerde datums "
"<strong>vervangen</strong>."
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:257
msgid ""
"You selected a set of dates that currently have different check-in list "
"setups. You can therefore not change their check-in lists in bulk."
msgstr ""
"De inchecklijstinstellingen van de gekozen datums verschillen en kunnen "
"hierom niet in één keer aangepast worden."
#: pretix/control/templates/pretixcontrol/subevents/delete.html:4
#: pretix/control/templates/pretixcontrol/subevents/delete.html:6
@@ -16671,7 +16751,7 @@ msgstr "Maak meerdere nieuwe datums"
#: pretix/control/templates/pretixcontrol/subevents/index.html:70
msgid "select all rows for batch-operation"
msgstr "Selecteer alle rijen voor batch-handeling"
msgstr ""
#: pretix/control/templates/pretixcontrol/subevents/index.html:77
msgid "Begin"
@@ -16679,11 +16759,11 @@ msgstr "Begin"
#: pretix/control/templates/pretixcontrol/subevents/index.html:100
msgid "Select all results on other pages as well"
msgstr "Selecteer ook alle resultaten op andere pagina's"
msgstr ""
#: pretix/control/templates/pretixcontrol/subevents/index.html:111
msgid "select row for batch-operation"
msgstr "Selecteer rij voor batch-handeling"
msgstr ""
#: pretix/control/templates/pretixcontrol/subevents/index.html:152
msgctxt "subevent"
@@ -16701,16 +16781,23 @@ msgid "Delete selected"
msgstr "Verwijder geselecteerde"
#: pretix/control/templates/pretixcontrol/subevents/index.html:176
#, fuzzy
#| msgctxt "subevent"
#| msgid "No date selected."
msgid "Edit selected"
msgstr "Bewerk geselecteerde"
msgstr "Geen datum geselecteerd."
#: pretix/control/templates/pretixcontrol/subevents/index.html:179
#, fuzzy
#| msgid "Disable selected"
msgid "Activate selected"
msgstr "Activeer geselecteerde"
msgstr "Schakel geselecteerde uit"
#: pretix/control/templates/pretixcontrol/subevents/index.html:182
#, fuzzy
#| msgid "Delete selected"
msgid "Deactivate selected"
msgstr "Deactiveer geselecteerde"
msgstr "Verwijder geselecteerde"
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:4
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:6
@@ -18608,12 +18695,16 @@ msgid "The selected gate has been deleted."
msgstr "De geselecteerde toegangslocatie is verwijderd."
#: pretix/control/views/organizer.py:1375
#, fuzzy
#| msgid "The product has been created."
msgid "The property has been created."
msgstr "De eigenschap is aangemaakt."
msgstr "Het product is aangemaakt."
#: pretix/control/views/organizer.py:1437
#, fuzzy
#| msgid "The selected product has been deleted."
msgid "The selected property has been deleted."
msgstr "De eigenschap is verwijderd."
msgstr "Het gekozen product is verwijderd."
#: pretix/control/views/pdf.py:53
msgid "The uploaded PDF file is too large."
@@ -20366,14 +20457,19 @@ msgid "Only send to customers of dates starting before"
msgstr "Stuur alleen naar klanten van subevenementen die beginnen voor"
#: pretix/plugins/sendmail/forms.py:70
#, fuzzy
#| msgid "Send to customers with order status"
msgctxt "subevent"
msgid "Only send to customers with orders created after"
msgstr "Stuur alleen naar klanten met bestellingen geplaatst na"
msgstr "Stuur naar klanten met bestelstatus"
#: pretix/plugins/sendmail/forms.py:75
#, fuzzy
#| msgctxt "subevent"
#| msgid "Only send to customers of dates starting before"
msgctxt "subevent"
msgid "Only send to customers with orders created before"
msgstr "Stuur alleen naar klanten met bestellingen geplaatst voor"
msgstr "Stuur alleen naar klanten van subevenementen die beginnen voor"
#: pretix/plugins/sendmail/forms.py:108
msgid "Everyone who created a ticket order"
@@ -20457,13 +20553,16 @@ msgid "There are no orders matching this selection."
msgstr "Er zijn geen bestellingen die overeenkomen met deze selectie."
#: pretix/plugins/sendmail/views.py:180
#, python-format
#, fuzzy, python-format
#| msgid ""
#| "Your message has been queued and will be sent to the contact addresses of "
#| "%d orders in the next minutes."
msgid ""
"Your message has been queued and will be sent to the contact addresses of %d "
"orders in the next few minutes."
msgstr ""
"Uw bericht is in de wachtrij gezet en zal binnenkort naar de e-mailadressen "
"van %d bestellingen worden verstuurd."
"Uw bericht is in de wachtrij gezet, en zal in de komende minuten naar de e-"
"mailadressen van %d bestellingen worden verstuurd."
#: pretix/plugins/statistics/__init__.py:9
#: pretix/plugins/statistics/__init__.py:12
@@ -21454,9 +21553,12 @@ msgid "No other variations of this product exist."
msgstr "Er bestaan geen andere varianten van dit product."
#: pretix/presale/forms/renderers.py:32
#, fuzzy
#| msgctxt "attendee_data"
#| msgid "Required"
msgctxt "form"
msgid "required"
msgstr "verplicht"
msgstr "Verplicht"
#: pretix/presale/ical.py:54
#, python-brace-format
@@ -21474,8 +21576,10 @@ msgid "Organizer: {organizer}"
msgstr "Organisator: {organizer}"
#: pretix/presale/templates/pretixpresale/base.html:59
#, fuzzy
#| msgid "Toggle navigation"
msgid "Footer Navigation"
msgstr "Footer-navigatie"
msgstr "Navigatie schakelen"
#: pretix/presale/templates/pretixpresale/base_footer.html:36
#, python-format
@@ -21492,8 +21596,10 @@ msgstr "Zet uw winkel nu live"
#: pretix/presale/templates/pretixpresale/event/base.html:40
#: pretix/presale/templates/pretixpresale/event/base.html:86
#, fuzzy
#| msgid "Use languages"
msgid "select language"
msgstr "taal kiezen"
msgstr "Gebruik talen"
#: pretix/presale/templates/pretixpresale/event/base.html:52
#, python-format
@@ -21652,7 +21758,7 @@ msgstr "Ga terug"
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:7
#, python-format
msgid "Step %(current)s of %(total)s: %(label)s"
msgstr "Stap %(current)s van %(total)s: %(label)s"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:12
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:57
@@ -21924,12 +22030,16 @@ msgid "Redeem voucher"
msgstr "Voucher inwisselen"
#: pretix/presale/templates/pretixpresale/event/fragment_checkoutflow.html:13
#, fuzzy
#| msgid "Completion date"
msgid "Completed:"
msgstr "Voltooid:"
msgstr "Voltooiingsdatum"
#: pretix/presale/templates/pretixpresale/event/fragment_checkoutflow.html:15
#, fuzzy
#| msgid "Currency"
msgid "Current:"
msgstr "Huidig:"
msgstr "Munteenheid"
#: pretix/presale/templates/pretixpresale/event/fragment_checkoutflow.html:29
msgctxt "checkoutflow"
@@ -21943,27 +22053,39 @@ msgstr ""
"tickets te ontvangen."
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:16
#, fuzzy
#| msgid "Please check your email account, we've sent you your tickets."
msgid "Please check your email account, we've sent you an email."
msgstr "We hebben een e-mail naar u verzonden."
msgstr ""
"We hebben uw tickets per e-mail naar u verzonden. Kijk in uw inbox om uw "
"tickets te ontvangen."
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:21
#, fuzzy
#| msgid ""
#| "You can also download them right here as soon as the person who placed "
#| "the order clicked the link in the email they received to confirm the "
#| "email address is valid."
msgid ""
"You can download your tickets right here as soon as the person who placed "
"the order clicked the link in the email they received to confirm the email "
"address is valid."
msgstr ""
"De persoon die de bestelling heeft geplaatst heeft een e-mail ontvangen met "
"een link om het opgegeven e-mailadres te controleren. U kunt uw tickets op "
"deze pagina downloaden zodra de persoon die de bestelling heeft geplaatst op "
"deze link heeft geklikt."
"U kunt uw tickets ook op deze pagina downloaden zodra het opgegeven e-"
"mailadres is bevestigd. Dit kan de persoon die de bestelling heeft geplaatst "
"doen door te klikken op de link in de toegezonden email."
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:26
#, fuzzy
#| msgid ""
#| "If the email has no attachment, click the link in our email and you will "
#| "be able to download them from here."
msgid ""
"If you click the link in our email, you will be able to download your "
"tickets here."
msgstr ""
"U kunt uw tickets op deze pagina downloaden zodra u op de link in onze e-"
"mail klikt."
"Als de email geen bijlage heeft kunt u op de link in de mail klikken om de "
"tickets te downloaden."
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:30
msgid ""
@@ -22017,14 +22139,17 @@ msgid "Confirmed"
msgstr "Bevestigd"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:9
#, fuzzy
#| msgid "Uncategorized"
msgid "Uncategorized products"
msgstr "Ongecategoriseerde producten"
msgstr "Ongecategoriseerd"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:28
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:187
#, python-format
#, fuzzy, python-format
#| msgid "Show all events of %(name)s"
msgid "Show full-size image of %(item)s"
msgstr "Toon volledige afbeelding van %(item)s"
msgstr "Toon alle evenementen van %(name)s"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:63
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:120
@@ -22092,12 +22217,10 @@ msgstr "Nog niet in de verkoop"
#: pretix/presale/templates/pretixpresale/event/index.html:27
msgid "Your cart, general information, add products to your cart"
msgstr ""
"Uw winkelwagen, algemene informatie, nieuwe producten aan winkelwagen "
"toevoegen"
#: pretix/presale/templates/pretixpresale/event/index.html:27
msgid "General information, add products to your cart"
msgstr "Algemene informatie, nieuwe producten aan winkelwagen toevoegen"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/index.html:65
#: pretix/presale/templates/pretixpresale/event/index.html:82

View File

@@ -1,15 +1,15 @@
import importlib.util
from django.apps import apps
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from pretix.multidomain.plugin_handler import plugin_event_urls
from pretix.presale.urls import event_patterns, locale_patterns
from pretix.urls import common_patterns
presale_patterns = [
url(r'', include((locale_patterns + [
url(r'', include(event_patterns)),
re_path(r'', include((locale_patterns + [
re_path(r'', include(event_patterns)),
], 'presale')))
]
@@ -21,11 +21,11 @@ for app in apps.get_app_configs():
if hasattr(urlmod, 'event_patterns'):
patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name)
raw_plugin_patterns.append(
url(r'', include((patterns, app.label)))
re_path(r'', include((patterns, app.label)))
)
plugin_patterns = [
url(r'', include((raw_plugin_patterns, 'plugins')))
re_path(r'', include((raw_plugin_patterns, 'plugins')))
]
# The presale namespace comes last, because it contains a wildcard catch

View File

@@ -1,7 +1,7 @@
import importlib.util
from django.apps import apps
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from django.views.generic import TemplateView
from pretix.multidomain.plugin_handler import plugin_event_urls
@@ -11,10 +11,10 @@ from pretix.presale.urls import (
from pretix.urls import common_patterns
presale_patterns_main = [
url(r'', include((locale_patterns + [
url(r'^(?P<organizer>[^/]+)/', include(organizer_patterns)),
url(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(event_patterns)),
url(r'^$', TemplateView.as_view(template_name='pretixpresale/index.html'), name="index")
re_path(r'', include((locale_patterns + [
re_path(r'^(?P<organizer>[^/]+)/', include(organizer_patterns)),
re_path(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(event_patterns)),
re_path(r'^$', TemplateView.as_view(template_name='pretixpresale/index.html'), name="index")
], 'presale')))
]
@@ -28,18 +28,18 @@ for app in apps.get_app_configs():
single_plugin_patterns += urlmod.urlpatterns
if hasattr(urlmod, 'event_patterns'):
patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name)
single_plugin_patterns.append(url(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/',
include(patterns)))
single_plugin_patterns.append(re_path(r'^(?P<organizer>[^/]+)/(?P<event>[^/]+)/',
include(patterns)))
if hasattr(urlmod, 'organizer_patterns'):
patterns = urlmod.organizer_patterns
single_plugin_patterns.append(url(r'^(?P<organizer>[^/]+)/',
include(patterns)))
single_plugin_patterns.append(re_path(r'^(?P<organizer>[^/]+)/',
include(patterns)))
raw_plugin_patterns.append(
url(r'', include((single_plugin_patterns, app.label)))
re_path(r'', include((single_plugin_patterns, app.label)))
)
plugin_patterns = [
url(r'', include((raw_plugin_patterns, 'plugins')))
re_path(r'', include((raw_plugin_patterns, 'plugins')))
]
# The presale namespace comes last, because it contains a wildcard catch

View File

@@ -1,7 +1,7 @@
import importlib.util
from django.apps import apps
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from pretix.multidomain.plugin_handler import plugin_event_urls
from pretix.presale.urls import (
@@ -10,9 +10,9 @@ from pretix.presale.urls import (
from pretix.urls import common_patterns
presale_patterns = [
url(r'', include((locale_patterns + [
url(r'', include(organizer_patterns)),
url(r'^(?P<event>[^/]+)/', include(event_patterns)),
re_path(r'', include((locale_patterns + [
re_path(r'', include(organizer_patterns)),
re_path(r'^(?P<event>[^/]+)/', include(event_patterns)),
], 'presale')))
]
@@ -24,16 +24,16 @@ for app in apps.get_app_configs():
if hasattr(urlmod, 'event_patterns'):
patterns = plugin_event_urls(urlmod.event_patterns, plugin=app.name)
raw_plugin_patterns.append(
url(r'^(?P<event>[^/]+)/', include((patterns, app.label)))
re_path(r'^(?P<event>[^/]+)/', include((patterns, app.label)))
)
if hasattr(urlmod, 'organizer_patterns'):
patterns = urlmod.organizer_patterns
raw_plugin_patterns.append(
url(r'', include((patterns, app.label)))
re_path(r'', include((patterns, app.label)))
)
plugin_patterns = [
url(r'', include((raw_plugin_patterns, 'plugins')))
re_path(r'', include((raw_plugin_patterns, 'plugins')))
]
# The presale namespace comes last, because it contains a wildcard catch

View File

@@ -63,7 +63,7 @@ def eventurl(parser, token, absolute=False):
asvar = bits[-1]
bits = bits[:-2]
if bits:
if len(bits):
for bit in bits:
match = kwarg_re.match(bit)
if not match:

View File

@@ -5,14 +5,12 @@ from io import BytesIO
from typing import Tuple
from django import forms
from django.conf import settings
from django.contrib.staticfiles import finders
from django.core.files import File
from django.core.files.storage import default_storage
from django.db.models import Exists, OuterRef
from django.db.models.functions import Coalesce
from django.utils.translation import gettext as _, gettext_lazy
from jsonfallback.functions import JSONExtract
from reportlab.lib import pagesizes
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
@@ -242,10 +240,10 @@ class BadgeExporter(BaseExporter):
choices=[
('name', _('Attendee name')),
('code', _('Order code')),
] + ([
] + [
('name:{}'.format(k), _('Attendee name: {part}').format(part=label))
for k, label, w in name_scheme['fields']
] if settings.JSON_FIELD_AVAILABLE and len(name_scheme['fields']) > 1 else []),
],
)),
]
)
@@ -275,10 +273,8 @@ class BadgeExporter(BaseExporter):
qs = qs.annotate(
resolved_name=Coalesce('attendee_name_parts', 'addon_to__attendee_name_parts',
'order__invoice_address__name_parts')
).annotate(
resolved_name_part=JSONExtract('resolved_name', part)
).order_by(
'resolved_name_part'
f'resolved_name__{part}'
)
outbuffer = render_pdf(self.event, qs, OPTIONS[form_data.get('rendering', 'one')])

View File

@@ -1,4 +1,4 @@
from django.conf.urls import url
from django.conf.urls import re_path
from pretix.api.urls import event_router
from pretix.plugins.badges.api import BadgeItemViewSet, BadgeLayoutViewSet
@@ -9,18 +9,18 @@ from .views import (
)
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/$',
LayoutListView.as_view(), name='index'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/print$',
OrderPrintDo.as_view(), name='print'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/add$',
LayoutCreate.as_view(), name='add'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/default$',
LayoutSetDefault.as_view(), name='default'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/delete$',
LayoutDelete.as_view(), name='delete'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/editor',
LayoutEditorView.as_view(), name='edit'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/$',
LayoutListView.as_view(), name='index'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/print$',
OrderPrintDo.as_view(), name='print'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/add$',
LayoutCreate.as_view(), name='add'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/default$',
LayoutSetDefault.as_view(), name='default'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/delete$',
LayoutDelete.as_view(), name='delete'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/editor',
LayoutEditorView.as_view(), name='edit'),
]
event_router.register('badgelayouts', BadgeLayoutViewSet)
event_router.register('badgeitems', BadgeItemViewSet)

View File

@@ -1,4 +1,4 @@
from django.conf.urls import url
from django.conf.urls import re_path
from pretix.api.urls import orga_router
from pretix.plugins.banktransfer.api import BankImportJobViewSet
@@ -6,38 +6,38 @@ from pretix.plugins.banktransfer.api import BankImportJobViewSet
from . import views
urlpatterns = [
url(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/import/',
views.OrganizerImportView.as_view(),
name='import'),
url(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/job/(?P<job>\d+)/',
views.OrganizerJobDetailView.as_view(), name='import.job'),
url(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/action/',
views.OrganizerActionView.as_view(), name='import.action'),
url(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/refunds/',
views.OrganizerRefundExportListView.as_view(), name='refunds.list'),
url(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/export/(?P<id>\d+)/$',
views.OrganizerDownloadRefundExportView.as_view(),
name='refunds.download'),
url(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/sepa-export/(?P<id>\d+)/$',
views.OrganizerSepaXMLExportView.as_view(),
name='refunds.sepa'),
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/import/',
views.OrganizerImportView.as_view(),
name='import'),
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/job/(?P<job>\d+)/',
views.OrganizerJobDetailView.as_view(), name='import.job'),
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/action/',
views.OrganizerActionView.as_view(), name='import.action'),
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/refunds/',
views.OrganizerRefundExportListView.as_view(), name='refunds.list'),
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/export/(?P<id>\d+)/$',
views.OrganizerDownloadRefundExportView.as_view(),
name='refunds.download'),
re_path(r'^control/organizer/(?P<organizer>[^/]+)/banktransfer/sepa-export/(?P<id>\d+)/$',
views.OrganizerSepaXMLExportView.as_view(),
name='refunds.sepa'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/import/',
views.EventImportView.as_view(),
name='import'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/job/(?P<job>\d+)/',
views.EventJobDetailView.as_view(), name='import.job'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/action/',
views.EventActionView.as_view(), name='import.action'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/refunds/',
views.EventRefundExportListView.as_view(),
name='refunds.list'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/export/(?P<id>\d+)/$',
views.EventDownloadRefundExportView.as_view(),
name='refunds.download'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/sepa-export/(?P<id>\d+)/$',
views.EventSepaXMLExportView.as_view(),
name='refunds.sepa'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/import/',
views.EventImportView.as_view(),
name='import'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/job/(?P<job>\d+)/',
views.EventJobDetailView.as_view(), name='import.job'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/action/',
views.EventActionView.as_view(), name='import.action'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/refunds/',
views.EventRefundExportListView.as_view(),
name='refunds.list'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/export/(?P<id>\d+)/$',
views.EventDownloadRefundExportView.as_view(),
name='refunds.download'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/banktransfer/sepa-export/(?P<id>\d+)/$',
views.EventSepaXMLExportView.as_view(),
name='refunds.sepa'),
]
orga_router.register('bankimportjobs', BankImportJobViewSet)

View File

@@ -3,16 +3,14 @@ from datetime import datetime, time, timedelta
import dateutil.parser
from django import forms
from django.conf import settings
from django.db.models import (
Case, Exists, Max, OuterRef, Q, Subquery, Value, When,
Case, Exists, Max, OuterRef, Q, Subquery, Value, When, F,
)
from django.db.models.functions import Coalesce, NullIf
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.timezone import is_aware, make_aware
from django.utils.translation import gettext as _, gettext_lazy, pgettext
from jsonfallback.functions import JSONExtract
from pytz import UTC
from reportlab.lib.units import mm
from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle
@@ -73,10 +71,10 @@ class CheckInListMixin(BaseExporter):
choices=[
('name', _('Attendee name')),
('code', _('Order code')),
] + ([
] + [
('name:{}'.format(k), _('Attendee name: {part}').format(part=label))
for k, label, w in name_scheme['fields']
] if settings.JSON_FIELD_AVAILABLE and len(name_scheme['fields']) > 1 else []),
],
widget=forms.RadioSelect,
required=False
)),
@@ -159,7 +157,7 @@ class CheckInListMixin(BaseExporter):
), self.event.timezone)
qs = qs.filter(subevent__date_from__lt=dt)
o = ()
o = tuple()
if self.event.has_subevents and not cl.subevent:
o = ('subevent__date_from', 'subevent__name')
@@ -180,15 +178,17 @@ class CheckInListMixin(BaseExporter):
part = sort[5:]
qs = qs.annotate(
resolved_name=Case(
When(attendee_name_cached__ne='', then='attendee_name_parts'),
When(addon_to__attendee_name_cached__isnull=False, addon_to__attendee_name_cached__ne='', then='addon_to__attendee_name_parts'),
default='order__invoice_address__name_parts',
)
When(attendee_name_cached__isnull=False, attendee_name_cached__ne='',
then='attendee_name_parts'),
When(addon_to__attendee_name_cached__isnull=False, addon_to__attendee_name_cached__ne='',
then='addon_to__attendee_name_parts'),
default='order__invoice_address__name_parts',
),
).annotate(
resolved_name_part=JSONExtract('resolved_name', part)
resolved_name_part=F(f'resolved_name__{part}')
).order_by(
*o,
'resolved_name_part'
f'resolved_name__{part}'
)
if form_data.get('attention_only'):

View File

@@ -1,4 +1,4 @@
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from pretix.multidomain import event_url
@@ -7,22 +7,21 @@ from .views import (
)
event_patterns = [
url(r'^paypal/', include([
url(r'^abort/$', abort, name='abort'),
url(r'^return/$', success, name='return'),
url(r'^redirect/$', redirect_view, name='redirect'),
re_path(r'^paypal/', include([
re_path(r'^abort/$', abort, name='abort'),
re_path(r'^return/$', success, name='return'),
re_path(r'^redirect/$', redirect_view, name='redirect'),
url(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/abort/', abort, name='abort'),
url(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/return/', success, name='return'),
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/abort/', abort, name='abort'),
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/return/', success, name='return'),
event_url(r'^webhook/$', webhook, name='webhook', require_live=False),
])),
]
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/paypal/disconnect/',
oauth_disconnect, name='oauth.disconnect'),
url(r'^_paypal/webhook/$', webhook, name='webhook'),
url(r'^_paypal/oauth_return/$', oauth_return, name='oauth.return'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/paypal/disconnect/',
oauth_disconnect, name='oauth.disconnect'),
re_path(r'^_paypal/webhook/$', webhook, name='webhook'),
re_path(r'^_paypal/oauth_return/$', oauth_return, name='oauth.return'),
]

View File

@@ -616,7 +616,7 @@ class OrderTaxListReport(MultiSheetListExporter):
elif sheet == 'companies':
yield from self.iterate_companies(form_data)
def _combine(self, *qs, keys=()):
def _combine(self, *qs, keys=tuple()):
cache = {}
def kf(r):

View File

@@ -1,8 +1,8 @@
from django.conf.urls import url
from django.conf.urls import re_path
from .views import ReturnSettings
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/returnurl/settings$',
ReturnSettings.as_view(), name='settings'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/returnurl/settings$',
ReturnSettings.as_view(), name='settings'),
]

View File

@@ -1,9 +1,10 @@
from django.conf.urls import url
from django.conf.urls import re_path
from . import views
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/$', views.SenderView.as_view(),
name='send'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/history/', views.EmailHistoryView.as_view(), name='history')
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/$', views.SenderView.as_view(),
name='send'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/sendmail/history/', views.EmailHistoryView.as_view(),
name='history')
]

View File

@@ -10,7 +10,7 @@
<h1>{% trans "Statistics" %}</h1>
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</form>
{% endif %}
{% if has_orders %}

View File

@@ -1,8 +1,8 @@
from django.conf.urls import url
from django.conf.urls import re_path
from . import views
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/statistics/', views.IndexView.as_view(),
name='index'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/statistics/', views.IndexView.as_view(),
name='index'),
]

View File

@@ -1,4 +1,4 @@
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from pretix.multidomain import event_url
@@ -9,30 +9,30 @@ from .views import (
)
event_patterns = [
url(r'^stripe/', include([
re_path(r'^stripe/', include([
event_url(r'^webhook/$', webhook, name='webhook', require_live=False),
url(r'^redirect/$', redirect_view, name='redirect'),
url(r'^return/(?P<order>[^/]+)/(?P<hash>[^/]+)/(?P<payment>[0-9]+)/$', ReturnView.as_view(), name='return'),
url(r'^sca/(?P<order>[^/]+)/(?P<hash>[^/]+)/(?P<payment>[0-9]+)/$', ScaView.as_view(), name='sca'),
url(r'^sca/(?P<order>[^/]+)/(?P<hash>[^/]+)/(?P<payment>[0-9]+)/return/$',
ScaReturnView.as_view(), name='sca.return'),
re_path(r'^redirect/$', redirect_view, name='redirect'),
re_path(r'^return/(?P<order>[^/]+)/(?P<hash>[^/]+)/(?P<payment>[0-9]+)/$', ReturnView.as_view(), name='return'),
re_path(r'^sca/(?P<order>[^/]+)/(?P<hash>[^/]+)/(?P<payment>[0-9]+)/$', ScaView.as_view(), name='sca'),
re_path(r'^sca/(?P<order>[^/]+)/(?P<hash>[^/]+)/(?P<payment>[0-9]+)/return/$',
ScaReturnView.as_view(), name='sca.return'),
])),
url(r'^.well-known/apple-developer-merchantid-domain-association$',
applepay_association, name='applepay.association'),
re_path(r'^.well-known/apple-developer-merchantid-domain-association$',
applepay_association, name='applepay.association'),
]
organizer_patterns = [
url(r'^.well-known/apple-developer-merchantid-domain-association$',
applepay_association, name='applepay.association'),
re_path(r'^.well-known/apple-developer-merchantid-domain-association$',
applepay_association, name='applepay.association'),
]
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/stripe/disconnect/',
oauth_disconnect, name='oauth.disconnect'),
url(r'^control/organizer/(?P<organizer>[^/]+)/stripeconnect/',
OrganizerSettingsFormView.as_view(), name='settings.connect'),
url(r'^_stripe/webhook/$', webhook, name='webhook'),
url(r'^_stripe/oauth_return/$', oauth_return, name='oauth.return'),
url(r'^.well-known/apple-developer-merchantid-domain-association$',
applepay_association, name='applepay.association'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/stripe/disconnect/',
oauth_disconnect, name='oauth.disconnect'),
re_path(r'^control/organizer/(?P<organizer>[^/]+)/stripeconnect/',
OrganizerSettingsFormView.as_view(), name='settings.connect'),
re_path(r'^_stripe/webhook/$', webhook, name='webhook'),
re_path(r'^_stripe/oauth_return/$', oauth_return, name='oauth.return'),
re_path(r'^.well-known/apple-developer-merchantid-domain-association$',
applepay_association, name='applepay.association'),
]

View File

@@ -2,11 +2,9 @@ from collections import OrderedDict
from io import BytesIO
from django import forms
from django.conf import settings
from django.core.files.base import ContentFile
from django.db.models.functions import Coalesce
from django.utils.translation import gettext as _, gettext_lazy
from jsonfallback.functions import JSONExtract
from PyPDF2.merger import PdfFileMerger
from pretix.base.exporter import BaseExporter
@@ -41,7 +39,7 @@ class AllTicketsPDF(BaseExporter):
] + ([
('name:{}'.format(k), _('Attendee name: {part}').format(part=label))
for k, label, w in name_scheme['fields']
] if settings.JSON_FIELD_AVAILABLE and name_scheme and len(name_scheme['fields']) > 1 else []),
] if name_scheme else [])
)),
]
)
@@ -68,10 +66,8 @@ class AllTicketsPDF(BaseExporter):
part = form_data['order_by'][5:]
qs = qs.annotate(
resolved_name=Coalesce('attendee_name_parts', 'addon_to__attendee_name_parts', 'order__invoice_address__name_parts')
).annotate(
resolved_name_part=JSONExtract('resolved_name', part)
).order_by(
'resolved_name_part'
f'resolved_name__{part}'
)
o = PdfTicketOutput(Event.objects.none())

View File

@@ -131,10 +131,10 @@ def pdf_logentry_object_link(sender, logentry, **kwargs):
return a_text.format_map(a_map)
override_layout = EventPluginSignal(
providing_args=["layout", "orderposition"]
)
override_layout = EventPluginSignal()
"""
Arguments: ``layout``, ``orderposition``
This signal allows you to forcefully override the ticket layout that is being used to create the ticket PDF. Use with
care, as this will render any specifically by the organizer selected templates useless.

View File

@@ -1,4 +1,4 @@
from django.conf.urls import url
from django.conf.urls import re_path
from pretix.api.urls import event_router
from pretix.plugins.ticketoutputpdf.api import (
@@ -10,18 +10,18 @@ from pretix.plugins.ticketoutputpdf.views import (
)
urlpatterns = [
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/$',
LayoutListView.as_view(), name='index'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/add$',
LayoutCreate.as_view(), name='add'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/(?P<layout>\d+)/default$',
LayoutSetDefault.as_view(), name='default'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/default$',
LayoutGetDefault.as_view(), name='getdefault'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/(?P<layout>\d+)/delete$',
LayoutDelete.as_view(), name='delete'),
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/(?P<layout>\d+)/editor',
LayoutEditorView.as_view(), name='edit'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/$',
LayoutListView.as_view(), name='index'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/add$',
LayoutCreate.as_view(), name='add'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/(?P<layout>\d+)/default$',
LayoutSetDefault.as_view(), name='default'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/default$',
LayoutGetDefault.as_view(), name='getdefault'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/(?P<layout>\d+)/delete$',
LayoutDelete.as_view(), name='delete'),
re_path(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pdfoutput/(?P<layout>\d+)/editor',
LayoutEditorView.as_view(), name='edit'),
]
event_router.register('ticketlayouts', TicketLayoutViewSet)
event_router.register('ticketlayoutitems', TicketLayoutItemViewSet)

View File

@@ -1,14 +1,14 @@
from bootstrap3.renderers import FieldRenderer
from bootstrap3.text import text_value
from bootstrap3.utils import add_css_class
from django.forms import CheckboxInput, CheckboxSelectMultiple, RadioSelect
from django.forms import CheckboxInput
from django.forms.utils import flatatt
from django.utils.html import escape, format_html, strip_tags
from django.utils.safestring import mark_safe
from django.utils.translation import pgettext
def render_label(content, label_for=None, label_class=None, label_title='', label_id='', optional=False, is_valid=None):
def render_label(content, label_for=None, label_class=None, label_title='', optional=False):
"""
Render a label with content
"""
@@ -19,17 +19,6 @@ def render_label(content, label_for=None, label_class=None, label_title='', labe
attrs['class'] = label_class
if label_title:
attrs['title'] = label_title
if label_id:
attrs['id'] = label_id
opt = ""
if is_valid is not None:
if is_valid:
validation_text = pgettext('form', 'is valid')
else:
validation_text = pgettext('form', 'has errors')
opt += '<strong class="sr-only"> {}</strong>'.format(validation_text)
if text_value(content) == '&#160;':
# Empty label, e.g. checkbox
@@ -37,17 +26,17 @@ def render_label(content, label_for=None, label_class=None, label_title='', labe
attrs['class'] += ' label-empty'
# usually checkboxes have overall empty labels and special labels per checkbox
# => remove for-attribute as well as "required"-text appended to label
if 'for' in attrs:
del(attrs['for'])
del(attrs['for'])
opt = ""
else:
opt += '<i class="sr-only label-required">, {}</i>'.format(pgettext('form', 'required')) if not optional else ''
opt = mark_safe('<i class="sr-only"> {}</i>'.format(pgettext('form', 'required'))) if not optional else ''
builder = '<{tag}{attrs}>{content}{opt}</{tag}>'
return format_html(
builder,
tag='label',
attrs=mark_safe(flatatt(attrs)) if attrs else '',
opt=mark_safe(opt),
opt=opt,
content=text_value(content),
)
@@ -56,7 +45,6 @@ class CheckoutFieldRenderer(FieldRenderer):
def __init__(self, *args, **kwargs):
kwargs['layout'] = 'horizontal'
super().__init__(*args, **kwargs)
self.is_group_widget = isinstance(self.widget, (CheckboxSelectMultiple, RadioSelect, )) or (self.is_multi_widget and len(self.widget.widgets) > 1)
def get_form_group_class(self):
form_group_class = self.form_group_class
@@ -76,26 +64,6 @@ class CheckoutFieldRenderer(FieldRenderer):
)
return form_group_class
def append_to_field(self, html):
help_text_and_errors = []
help_text_and_errors += self.field_errors
if self.field_help:
help_text_and_errors.append(self.field_help)
for idx, text in enumerate(help_text_and_errors):
html += '<div class="help-block" id="help-for-{id}-{idx}">{text}</div>'.format(id=self.field.id_for_label, text=text, idx=idx)
return html
def add_help_attrs(self, widget=None):
super().add_help_attrs(widget)
if widget is None:
widget = self.widget
help_cnt = len(self.field_errors)
if self.field_help:
help_cnt += 1
if help_cnt > 0:
help_ids = ["help-for-{id}-{idx}".format(id=self.field.id_for_label, idx=idx) for idx in range(help_cnt)]
widget.attrs["aria-describedby"] = " ".join(help_ids)
def add_label(self, html):
label = self.get_label()
@@ -105,25 +73,11 @@ class CheckoutFieldRenderer(FieldRenderer):
else:
required = self.field.field.required
if self.field.form.is_bound:
is_valid = len(self.field.errors) == 0
else:
is_valid = None
if self.is_group_widget:
label_for = ""
label_id = "legend-{}".format(self.field.html_name)
else:
label_for = self.field.id_for_label
label_id = ""
html = render_label(
label,
label_for=label_for,
label_for=self.field.id_for_label,
label_class=self.get_label_class(),
label_id=label_id,
optional=not required and not isinstance(self.widget, CheckboxInput),
is_valid=is_valid
optional=not required and not isinstance(self.widget, CheckboxInput)
) + html
return html
@@ -134,10 +88,3 @@ class CheckoutFieldRenderer(FieldRenderer):
label_for=self.field.id_for_label,
label_title=escape(strip_tags(self.field_help)),
)
def wrap_label_and_field(self, html):
if self.is_group_widget:
attrs = ' role="group" aria-labelledby="legend-{}"'.format(self.field.html_name)
else:
attrs = ''
return '<div class="{klass}"{attrs}>{html}</div>'.format(klass=self.get_form_group_class(), html=html, attrs=attrs)

View File

@@ -2,10 +2,10 @@ from django.dispatch import Signal
from pretix.base.signals import EventPluginSignal
global_html_head = Signal(
providing_args=["request"]
)
global_html_head = Signal()
"""
Arguments: ``request``
This signal allows you to put code inside the HTML ``<head>`` tag
of every page in the frontend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.
@@ -13,10 +13,10 @@ of every page in the frontend. You will get the request as the keyword argument
This signal is called regardless of whether your plugin is active for all pages of the system.
"""
global_html_page_header = Signal(
providing_args=["request"]
)
global_html_page_header = Signal()
"""
Arguments: ``request``
This signal allows you to put code right in the beginning of the HTML ``<body>`` tag
of every page in the frontend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.
@@ -24,10 +24,10 @@ of every page in the frontend. You will get the request as the keyword argument
This signal is called regardless of whether your plugin is active for all pages of the system.
"""
global_html_footer = Signal(
providing_args=["request"]
)
global_html_footer = Signal()
"""
Arguments: ``request``
This signal allows you to put code before the end of the HTML ``<body>`` tag
of every page in the frontend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.
@@ -35,10 +35,10 @@ of every page in the frontend. You will get the request as the keyword argument
This signal is called regardless of whether your plugin is active for all pages of the system.
"""
html_head = EventPluginSignal(
providing_args=["request"]
)
html_head = EventPluginSignal()
"""
Arguments: ``request``
This signal allows you to put code inside the HTML ``<head>`` tag
of every page in the frontend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.
@@ -46,10 +46,10 @@ of every page in the frontend. You will get the request as the keyword argument
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
html_page_header = EventPluginSignal(
providing_args=["request"]
)
html_page_header = EventPluginSignal()
"""
Arguments: ``request``
This signal allows you to put code right in the beginning of the HTML ``<body>`` tag
of every page in the frontend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.
@@ -57,10 +57,10 @@ of every page in the frontend. You will get the request as the keyword argument
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
html_footer = EventPluginSignal(
providing_args=["request"]
)
html_footer = EventPluginSignal()
"""
Arguments: ``request``
This signal allows you to put code before the end of the HTML ``<body>`` tag
of every page in the frontend. You will get the request as the keyword argument
``request`` and are expected to return plain HTML.
@@ -68,10 +68,10 @@ of every page in the frontend. You will get the request as the keyword argument
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
sass_preamble = EventPluginSignal(
providing_args=["filename"]
)
sass_preamble = EventPluginSignal()
"""
Arguments: ``filename``
This signal allows you to put SASS code at the beginning of the event-specific
stylesheet. Keep in mind that this will only be called/rebuilt when the user changes
display settings or pretix gets updated. You will get the filename that is being
@@ -82,10 +82,10 @@ code.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
sass_postamble = EventPluginSignal(
providing_args=["filename"]
)
sass_postamble = EventPluginSignal()
"""
Arguments: ``filename``
This signal allows you to put SASS code at the end of the event-specific
stylesheet. Keep in mind that this will only be called/rebuilt when the user changes
display settings or pretix gets updated. You will get the filename that is being
@@ -95,20 +95,20 @@ all of pretix' SASS code.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
footer_link = EventPluginSignal(
providing_args=["request"]
)
footer_link = EventPluginSignal()
"""
Arguments: ``request``
The signal ``pretix.presale.signals.footer_link`` allows you to add links to the footer of an event page. You
are expected to return a dictionary containing the keys ``label`` and ``url``.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
global_footer_link = Signal(
providing_args=["request"]
)
global_footer_link = Signal()
"""
Arguments: ``request``
The signal ``pretix.presale.signals.global_footer_link`` allows you to add links to the footer of any page. You
are expected to return a dictionary containing the keys ``label`` and ``url``.
"""
@@ -131,29 +131,29 @@ a subclass of ``pretix.presale.checkoutflow.BaseCheckoutFlowStep``.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
voucher_redeem_info = EventPluginSignal(
providing_args=["voucher"]
)
voucher_redeem_info = EventPluginSignal()
"""
Arguments: ``voucher``
This signal is sent out to display additional information on the "redeem a voucher" page
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_meta_from_request = EventPluginSignal(
providing_args=["request"]
)
order_meta_from_request = EventPluginSignal()
"""
Arguments: ``request``
This signal is sent before an order is created through the pretixpresale frontend. It allows you
to return a dictionary that will be merged in the meta_info attribute of the order.
You will receive the request triggering the order creation as the ``request`` keyword argument.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
"""
checkout_confirm_page_content = EventPluginSignal(
providing_args=['request']
)
checkout_confirm_page_content = EventPluginSignal()
"""
Arguments: 'request'
This signals allows you to add HTML content to the confirmation page that is presented at the
end of the checkout process, just before the order is being created.
@@ -161,10 +161,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
argument will contain the request object.
"""
fee_calculation_for_cart = EventPluginSignal(
providing_args=['request', 'invoice_address', 'total', 'positions']
)
fee_calculation_for_cart = EventPluginSignal()
"""
Arguments: 'request', 'invoice_address', 'total', 'positions'
This signals allows you to add fees to a cart. You are expected to return a list of ``OrderFee``
objects that are not yet saved to the database (because there is no order yet).
@@ -175,9 +175,7 @@ You should not rely on this ``total`` value for fee calculations as other fees m
The ``positions`` argument will contain a list or queryset of ``CartPosition`` objects.
"""
contact_form_fields = EventPluginSignal(
providing_args=[]
)
contact_form_fields = EventPluginSignal()
"""
This signals allows you to add form fields to the contact form that is presented during checkout
and by default only asks for the email address. You are supposed to return a dictionary of
@@ -188,10 +186,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
argument will contain the request object.
"""
contact_form_fields_overrides = EventPluginSignal(
providing_args=["request", "order"]
)
contact_form_fields_overrides = EventPluginSignal()
"""
Arguments: ``request``, ``order``
This signal allows you to override fields of the contact form that is presented during checkout
and by default only asks for the email address. It is also being used for the invoice address
form. You are supposed to return a dictionary of dictionaries with globally unique keys. The
@@ -203,10 +201,10 @@ argument will contain the request object. The ``order`` argument is ``None`` dur
process and contains an order if the customer is trying to change an existing order.
"""
question_form_fields = EventPluginSignal(
providing_args=["position"]
)
question_form_fields = EventPluginSignal()
"""
Arguments: ``position``
This signals allows you to add form fields to the questions form that is presented during checkout
and by default asks for the questions configured in the backend. You are supposed to return a dictionary
of form fields with globally unique keys. The validated form results will be saved into the
@@ -219,10 +217,10 @@ later.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
question_form_fields_overrides = EventPluginSignal(
providing_args=["position", "request"]
)
question_form_fields_overrides = EventPluginSignal()
"""
Arguments: ``position``, ``request``
This signal allows you to override fields of the questions form that is presented during checkout
and by default only asks for the questions configured in the backend. You are supposed to return a
dictionary of dictionaries with globally unique keys. The value-dictionary should contain one or
@@ -235,46 +233,46 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
argument will contain the request object.
"""
order_info = EventPluginSignal(
providing_args=["order", "request"]
)
order_info = EventPluginSignal()
"""
Arguments: ``order``, ``request``
This signal is sent out to display additional information on the order detail page
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
position_info = EventPluginSignal(
providing_args=["order", "position", "request"]
)
position_info = EventPluginSignal()
"""
Arguments: ``order``, ``position``, ``request``
This signal is sent out to display additional information on the position detail page
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
order_info_top = EventPluginSignal(
providing_args=["order", "request"]
)
order_info_top = EventPluginSignal()
"""
Arguments: ``order``, ``request``
This signal is sent out to display additional information on top of the order detail page
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
position_info_top = EventPluginSignal(
providing_args=["order", "position", "request"]
)
position_info_top = EventPluginSignal()
"""
Arguments: ``order``, ``position``, ``request``
This signal is sent out to display additional information on top of the position detail page
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
process_request = EventPluginSignal(
providing_args=["request"]
)
process_request = EventPluginSignal()
"""
Arguments: ``request``
This signal is sent out whenever a request is made to a event presale page. Most of the
time, this will be called from the middleware layer (except on plugin-provided pages
this will be called by the @event_view decorator). Similarly to Django's process_request
@@ -287,10 +285,10 @@ easy to cause serious performance problems.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
process_response = EventPluginSignal(
providing_args=["request", "response"]
)
process_response = EventPluginSignal()
"""
Arguments: ``request``, ``response``
This signal is sent out whenever a response is sent from a event presale page. Most of
the time, this will be called from the middleware layer (except on plugin-provided pages
this will be called by the @event_view decorator). Similarly to Django's process_response
@@ -304,10 +302,10 @@ easy to cause serious performance problems.
As with all plugin signals, the ``sender`` keyword argument will contain the event.
"""
front_page_top = EventPluginSignal(
providing_args=["request", "subevent"]
)
front_page_top = EventPluginSignal()
"""
Arguments: ``request``, ``subevent``
This signal is sent out to display additional information on the frontpage above the list
of products and but below a custom frontpage text.
@@ -315,10 +313,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
receivers are expected to return HTML.
"""
render_seating_plan = EventPluginSignal(
providing_args=["request", "subevent", "voucher"]
)
render_seating_plan = EventPluginSignal()
"""
Arguments: ``request``, ``subevent``, ``voucher``
This signal is sent out to render a seating plan, if one is configured for the specific event.
You will be passed the ``request`` as a keyword argument. If applicable, a ``subevent`` or
``voucher`` argument might be given.
@@ -327,10 +325,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
receivers are expected to return HTML.
"""
front_page_bottom = EventPluginSignal(
providing_args=["request", "subevent"]
)
front_page_bottom = EventPluginSignal()
"""
Arguments: ``request``, ``subevent``
This signal is sent out to display additional information on the frontpage below the list
of products.
@@ -338,10 +336,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
receivers are expected to return HTML.
"""
front_page_bottom_widget = EventPluginSignal(
providing_args=["request", "subevent"]
)
front_page_bottom_widget = EventPluginSignal()
"""
Arguments: ``request``, ``subevent``
This signal is sent out to display additional information on the frontpage below the list
of products if the front page is shown in the widget.
@@ -349,10 +347,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
receivers are expected to return HTML.
"""
checkout_all_optional = EventPluginSignal(
providing_args=['request']
)
checkout_all_optional = EventPluginSignal()
"""
Arguments: 'request'
If any receiver of this signal returns ``True``, all input fields during checkout (contact data,
invoice address, confirmations) will be optional, except for questions. Use with care!
@@ -360,10 +358,10 @@ As with all plugin signals, the ``sender`` keyword argument will contain the eve
argument will contain the request object.
"""
item_description = EventPluginSignal(
providing_args=["item", "variation"]
)
item_description = EventPluginSignal()
"""
Arguments: ``item``, ``variation``
This signal is sent out when the description of an item or variation is rendered and allows you to append
additional text to the description. You are passed the ``item`` and ``variation`` and expected to return
HTML.

View File

@@ -57,7 +57,7 @@
<div class="panel-heading">
{% if payment_provider.identifier != "free" %}
<div class="pull-right flip">
<a href="{% eventurl request.event "presale:event.checkout" step="payment" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans "Modify payment" %}">
<a href="{% eventurl request.event "presale:event.checkout" step="payment" cart_namespace=cart_namespace|default_if_none:"" %}">
<span class="fa fa-edit" aria-hidden="true"></span>
{% trans "Modify" %}
</a>
@@ -79,7 +79,7 @@
<div class="panel panel-primary panel-contact">
<div class="panel-heading">
<div class="pull-right flip">
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}?invoice=1" aria-label="{% trans "Modify invoice information" %}">
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}?invoice=1">
<span class="fa fa-edit" aria-hidden="true"></span>
{% trans "Modify" %}
</a>
@@ -129,7 +129,7 @@
<div class="panel panel-primary panel-contact">
<div class="panel-heading">
<div class="pull-right flip">
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans "Modify contact information" %}">
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}">
<span class="fa fa-edit" aria-hidden="true"></span>
{% trans "Modify" %}
</a>

View File

@@ -21,9 +21,9 @@
</summary>
<div id="contact">
<div class="panel-body">
{% bootstrap_form contact_form layout="checkout" %}
{% bootstrap_form contact_form layout="horizontal" %}
{% if not invoice_address_asked and event.settings.invoice_name_required %}
{% bootstrap_form invoice_form layout="checkout" %}
{% bootstrap_form invoice_form layout="horizontal" %}
{% endif %}
</div>
</div>
@@ -46,7 +46,7 @@
{{ event.settings.invoice_address_explanation_text|rich_text }}
</div>
{% endif %}
{% bootstrap_form invoice_form layout="checkout" %}
{% bootstrap_form invoice_form layout="horizontal" %}
</div>
</div>
</details>

View File

@@ -51,47 +51,49 @@
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee name" %}">
{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "No attendee name provided" %}</em>{% endif %}
{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
</span>
</dd>
{% endif %}
{% if line.item.admission and event.settings.attendee_emails_asked and line.attendee_email %}
{% if line.item.admission and event.settings.attendee_emails_asked %}
<dt class="sr-only">
{% trans "Attendee email" %}
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee email" %}">
{{ line.attendee_email }}
{% if line.attendee_email %}{{ line.attendee_email }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
</span>
</dd>
{% endif %}
{% if line.item.admission and event.settings.attendee_addresses_asked %}
<br>
{% endif %}
{% if line.item.admission and event.settings.attendee_company_asked and line.company %}
{% if line.item.admission and event.settings.attendee_company_asked %}
<dt class="sr-only">
{% trans "Attendee company" %}
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee company" %}">
{{ line.company }}
{% if line.company %}{{ line.company }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
</span>
</dd>
{% endif %}
{% if line.item.admission and event.settings.attendee_addresses_asked %}
{% if line.street or line.zipcode or line.city %}
<dt class="sr-only">
{% trans "Attendee address" %}
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee address" %}">
<dt class="sr-only">
{% trans "Attendee address" %}
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee address" %}">
{% if line.street or line.zipcode or line.city or line.country %}
{{ line.street|default_if_none:""|linebreaksbr }}<br>
{{ line.zipcode|default_if_none:"" }} {{ line.city|default_if_none:"" }}<br>
{{ line.country.name|default_if_none:"" }}
{% if line.state %}<br>{{ line.state }}{% endif %}
</span>
</dd>
{% endif %}
{% else %}
<em>{% trans "not answered" %}</em>
{% endif %}
</span>
</dd>
{% endif %}
{% for q in line.questions %}
<dt>{{ q.question }}</dt>
@@ -138,7 +140,6 @@
{% elif line.addon_to %}
<div class="count">&nbsp;</div>
<div class="singleprice price">
<span class="sr-only">{% trans "price per item" %}</span>
{% if event.settings.display_net_prices %}
{{ line.net_price|money:event.currency }}
{% else %}
@@ -162,7 +163,6 @@
</button>
</form>
{% endif %}
<span class="sr-only">{% trans "quantity" %}</span>
{{ line.count }}
{% if editable %}
<form action="{% eventurl event "presale:event.cart.add" cart_namespace=cart_namespace|default_if_none:"" %}"
@@ -189,7 +189,6 @@
{% endif %}
</div>
<div class="singleprice price">
<span class="sr-only">{% trans "price per item" %}</span>
{% if event.settings.display_net_prices %}
{{ line.net_price|money:event.currency }}
{% else %}
@@ -198,7 +197,6 @@
</div>
{% endif %}
<div class="totalprice price">
<span class="sr-only">{% trans "price" %}</span>
{% if event.settings.display_net_prices %}
<strong>{{ line.net_total|money:event.currency }}</strong>
{% if line.tax_rate and line.total %}
@@ -308,12 +306,11 @@
data-asynctask-headline="{% trans "We're applying this voucher to your cart..." %}"
method="post" data-asynctask class="apply-voucher">
{% csrf_token %}
<label for="voucher_code" class="sr-only">{% trans "Voucher code" %}</label>
<div class="input-group">
<input type="text" class="form-control" name="voucher" id="voucher_code" placeholder="{% trans "Voucher code" %}" aria-label="{% trans "Voucher code" %}">
<input type="text" class="form-control" name="voucher" placeholder="{% trans "Voucher code" %}" aria-label="{% trans "Voucher code" %}">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">
<span class="fa fa-check" aria-hidden="true"></span><span class="sr-only"> {% trans "Redeem voucher" %}</span>
<button class="btn btn-primary" type="submit" aria-label="{% trans "Redeem voucher" %}">
<span class="fa fa-check" aria-hidden="true"></span>
</button>
</span>
</div>

View File

@@ -6,14 +6,12 @@
{% load eventsignal %}
{% load rich_text %}
{% for tup in items_by_category %}
<section aria-labelledby="category-{% if tup.0 %}{{ tup.0.id }}{% else %}none{% endif %}"{% if tup.0.description %} aria-describedby="category-info-{{ tup.0.id }}"{% endif %}>
<section{% if tup.0 %} aria-labelledby="category-{{ tup.0.id }}"{% else %} aria-label="{% trans "Uncategorized products" %}"{% endif %}{% if tup.0.description %} aria-describedby="category-info-{{ tup.0.id }}"{% endif %}>
{% if tup.0 %}
<h3 id="category-{{ tup.0.id }}">{{ tup.0.name }}</h3>
{% if tup.0.description %}
<div id="category-info-{{ tup.0.id }}">{{ tup.0.description|localize|rich_text }}</div>
{% endif %}
{% else %}
<h3 id="category-none" class="sr-only">{% trans "Uncategorized products" %}</h3>
{% endif %}
{% for item in tup.1 %}
{% if item.has_variations %}

View File

@@ -105,19 +105,19 @@
{% if subevent and "year" not in request.GET %}
{% if show_cart %}
<a class="subevent-toggle btn btn-primary btn-block btn-lg" href="#subevent-list">
<a class="subevent-toggle btn btn-primary btn-block btn-lg">
<span class="fa fa-reply" aria-hidden="true"></span>
{% trans "Add tickets for a different date" %}
</a>
{% else %}
<a class="subevent-toggle btn btn-default btn-block" href="#subevent-list">
<a class="subevent-toggle btn btn-default btn-block">
{% trans "View other date" %}
</a>
{% endif %}
{% else %}
<h3>{% trans "Choose date to book a ticket" %}</h3>
{% endif %}
<div class="panel panel-default subevent-list" id="subevent-list">
<div class="panel panel-default subevent-list">
<div class="panel-heading">
{% if subevent %}
{% trans "Other dates" context "subevent" %}
@@ -298,11 +298,10 @@
<form method="get" action="{% eventurl event "presale:event.redeem" cart_namespace=cart_namespace %}">
<div class="row-voucher">
<div class="col-md-8 col-sm-6 col-xs-12">
<label for="voucher" class="sr-only">{% trans "Voucher code" %}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-ticket fa-fw" aria-hidden="true"></i></span>
<input type="text" class="form-control" name="voucher" id="voucher"
placeholder="{% trans "Voucher code" %}">
placeholder="{% trans "Voucher code" %}" aria-label="{% trans "Voucher code" %}">
</div>
</div>
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}" />

View File

@@ -186,7 +186,7 @@
<div class="panel-heading">
{% if order.can_modify_answers %}
<div class="pull-right flip">
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}" aria-label="{% trans "Change ordered items" %}">
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}">
<span class="fa fa-edit" aria-hidden="true"></span>
{% trans "Change details" %}
</a>
@@ -280,7 +280,7 @@
{% if invoice_address_asked or request.event.settings.invoice_name_required %}
{% if order.can_modify_answers %}
<div class="pull-right flip">
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}" aria-label="{% trans "Change your information" %}">
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}">
<span class="fa fa-edit" aria-hidden="true"></span>
{% trans "Change details" %}
</a>

View File

@@ -1,4 +1,4 @@
from django.conf.urls import include, url
from django.conf.urls import include, re_path
from django.views.decorators.csrf import csrf_exempt
import pretix.presale.views.cart
@@ -17,126 +17,129 @@ import pretix.presale.views.widget
# configuration is done by the pretix.multidomain package.
frame_wrapped_urls = [
url(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'),
url(r'^cart/voucher$', pretix.presale.views.cart.CartApplyVoucher.as_view(), name='event.cart.voucher'),
url(r'^cart/clear$', pretix.presale.views.cart.CartClear.as_view(), name='event.cart.clear'),
url(r'^cart/answer/(?P<answer>[^/]+)/$',
pretix.presale.views.cart.AnswerDownload.as_view(),
name='event.cart.download.answer'),
url(r'^checkout/start$', pretix.presale.views.checkout.CheckoutView.as_view(), name='event.checkout.start'),
url(r'^checkout/(?P<step>[^/]+)/$', pretix.presale.views.checkout.CheckoutView.as_view(),
name='event.checkout'),
url(r'^redeem/?$', pretix.presale.views.cart.RedeemView.as_view(),
name='event.redeem'),
url(r'^seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(),
name='event.seatingplan'),
url(r'^(?P<subevent>[0-9]+)/seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(),
name='event.seatingplan'),
url(r'^(?P<subevent>[0-9]+)/$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
url(r'^waitinglist', pretix.presale.views.waiting.WaitingView.as_view(), name='event.waitinglist'),
url(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
re_path(r'^cart/remove$', pretix.presale.views.cart.CartRemove.as_view(), name='event.cart.remove'),
re_path(r'^cart/voucher$', pretix.presale.views.cart.CartApplyVoucher.as_view(), name='event.cart.voucher'),
re_path(r'^cart/clear$', pretix.presale.views.cart.CartClear.as_view(), name='event.cart.clear'),
re_path(r'^cart/answer/(?P<answer>[^/]+)/$',
pretix.presale.views.cart.AnswerDownload.as_view(),
name='event.cart.download.answer'),
re_path(r'^checkout/start$', pretix.presale.views.checkout.CheckoutView.as_view(), name='event.checkout.start'),
re_path(r'^checkout/(?P<step>[^/]+)/$', pretix.presale.views.checkout.CheckoutView.as_view(),
name='event.checkout'),
re_path(r'^redeem/?$', pretix.presale.views.cart.RedeemView.as_view(),
name='event.redeem'),
re_path(r'^seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(),
name='event.seatingplan'),
re_path(r'^(?P<subevent>[0-9]+)/seatingframe/$', pretix.presale.views.event.SeatingPlanView.as_view(),
name='event.seatingplan'),
re_path(r'^(?P<subevent>[0-9]+)/$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
re_path(r'^waitinglist', pretix.presale.views.waiting.WaitingView.as_view(), name='event.waitinglist'),
re_path(r'^$', pretix.presale.views.event.EventIndex.as_view(), name='event.index'),
]
event_patterns = [
# Cart/checkout patterns are a bit more complicated, as they should have simple URLs like cart/clear in normal
# cases, but need to have versions with unguessable URLs like w/8l4Y83XNonjLxoBb/cart/clear to be used in widget
# mode. This is required to prevent all clickjacking and CSRF attacks that would otherwise be possible.
# First, we define the normal version. The docstring of get_or_create_cart_id() has more information on this.
url(r'', include(frame_wrapped_urls)),
re_path(r'', include(frame_wrapped_urls)),
# Second, the widget version
url(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/', include(frame_wrapped_urls)),
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/', include(frame_wrapped_urls)),
# Third, a fake version that is defined like the first (and never gets called), but makes reversing URLs easier
url(r'(?P<cart_namespace>[_]{0})', include(frame_wrapped_urls)),
re_path(r'(?P<cart_namespace>[_]{0})', include(frame_wrapped_urls)),
# CartAdd goes extra since it also gets a csrf_exempt decorator in one of the cases
url(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'),
url(r'^(?P<cart_namespace>[_]{0})cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'),
url(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/cart/add',
csrf_exempt(pretix.presale.views.cart.CartAdd.as_view()),
name='event.cart.add'),
re_path(r'^cart/add$', pretix.presale.views.cart.CartAdd.as_view(), name='event.cart.add'),
re_path(r'^(?P<cart_namespace>[_]{0})cart/add$', pretix.presale.views.cart.CartAdd.as_view(),
name='event.cart.add'),
re_path(r'w/(?P<cart_namespace>[a-zA-Z0-9]{16})/cart/add',
csrf_exempt(pretix.presale.views.cart.CartAdd.as_view()),
name='event.cart.add'),
url(r'unlock/(?P<hash>[a-z0-9]{64})/$', pretix.presale.views.user.UnlockHashView.as_view(),
name='event.payment.unlock'),
url(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'),
re_path(r'unlock/(?P<hash>[a-z0-9]{64})/$', pretix.presale.views.user.UnlockHashView.as_view(),
name='event.payment.unlock'),
re_path(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/open/(?P<hash>[a-z0-9]+)/$', pretix.presale.views.order.OrderOpen.as_view(),
name='event.order.open'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
name='event.order'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice$',
pretix.presale.views.order.OrderInvoiceCreate.as_view(),
name='event.order.geninvoice'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/change$',
pretix.presale.views.order.OrderChange.as_view(),
name='event.order.change'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/cancel$',
pretix.presale.views.order.OrderCancel.as_view(),
name='event.order.cancel'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/cancel/do$',
pretix.presale.views.order.OrderCancelDo.as_view(),
name='event.order.cancel.do'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/modify$',
pretix.presale.views.order.OrderModify.as_view(),
name='event.order.modify'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/(?P<payment>[0-9]+)/$',
pretix.presale.views.order.OrderPaymentStart.as_view(),
name='event.order.pay'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/(?P<payment>[0-9]+)/confirm$',
pretix.presale.views.order.OrderPaymentConfirm.as_view(),
name='event.order.pay.confirm'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/(?P<payment>[0-9]+)/complete$',
pretix.presale.views.order.OrderPaymentComplete.as_view(),
name='event.order.pay.complete'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/change',
pretix.presale.views.order.OrderPayChangeMethod.as_view(),
name='event.order.pay.change'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/answer/(?P<answer>[^/]+)/$',
pretix.presale.views.order.AnswerDownload.as_view(),
name='event.order.download.answer'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<output>[^/]+)$',
pretix.presale.views.order.OrderDownload.as_view(),
name='event.order.download.combined'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<position>[0-9]+)/(?P<output>[^/]+)$',
pretix.presale.views.order.OrderDownload.as_view(),
name='event.order.download'),
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice/(?P<invoice>[0-9]+)$',
pretix.presale.views.order.InvoiceDownload.as_view(),
name='event.invoice.download'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/open/(?P<hash>[a-z0-9]+)/$',
pretix.presale.views.order.OrderOpen.as_view(),
name='event.order.open'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
name='event.order'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice$',
pretix.presale.views.order.OrderInvoiceCreate.as_view(),
name='event.order.geninvoice'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/change$',
pretix.presale.views.order.OrderChange.as_view(),
name='event.order.change'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/cancel$',
pretix.presale.views.order.OrderCancel.as_view(),
name='event.order.cancel'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/cancel/do$',
pretix.presale.views.order.OrderCancelDo.as_view(),
name='event.order.cancel.do'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/modify$',
pretix.presale.views.order.OrderModify.as_view(),
name='event.order.modify'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/(?P<payment>[0-9]+)/$',
pretix.presale.views.order.OrderPaymentStart.as_view(),
name='event.order.pay'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/(?P<payment>[0-9]+)/confirm$',
pretix.presale.views.order.OrderPaymentConfirm.as_view(),
name='event.order.pay.confirm'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/(?P<payment>[0-9]+)/complete$',
pretix.presale.views.order.OrderPaymentComplete.as_view(),
name='event.order.pay.complete'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/pay/change',
pretix.presale.views.order.OrderPayChangeMethod.as_view(),
name='event.order.pay.change'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/answer/(?P<answer>[^/]+)/$',
pretix.presale.views.order.AnswerDownload.as_view(),
name='event.order.download.answer'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<output>[^/]+)$',
pretix.presale.views.order.OrderDownload.as_view(),
name='event.order.download.combined'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<position>[0-9]+)/(?P<output>[^/]+)$',
pretix.presale.views.order.OrderDownload.as_view(),
name='event.order.download'),
re_path(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice/(?P<invoice>[0-9]+)$',
pretix.presale.views.order.InvoiceDownload.as_view(),
name='event.invoice.download'),
url(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/$',
pretix.presale.views.order.OrderPositionDetails.as_view(),
name='event.order.position'),
url(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<pid>[0-9]+)/(?P<output>[^/]+)$',
re_path(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/$',
pretix.presale.views.order.OrderPositionDetails.as_view(),
name='event.order.position'),
re_path(
r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<pid>[0-9]+)/(?P<output>[^/]+)$',
pretix.presale.views.order.OrderPositionDownload.as_view(),
name='event.order.position.download'),
url(r'^ical/?$',
pretix.presale.views.event.EventIcalDownload.as_view(),
name='event.ical.download'),
url(r'^ical/(?P<subevent>[0-9]+)/$',
pretix.presale.views.event.EventIcalDownload.as_view(),
name='event.ical.download'),
url(r'^auth/$', pretix.presale.views.event.EventAuth.as_view(), name='event.auth'),
re_path(r'^ical/?$',
pretix.presale.views.event.EventIcalDownload.as_view(),
name='event.ical.download'),
re_path(r'^ical/(?P<subevent>[0-9]+)/$',
pretix.presale.views.event.EventIcalDownload.as_view(),
name='event.ical.download'),
re_path(r'^auth/$', pretix.presale.views.event.EventAuth.as_view(), name='event.auth'),
url(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='event.widget.productlist'),
url(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='event.widget.css'),
url(r'^(?P<subevent>\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='event.widget.productlist'),
re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='event.widget.productlist'),
re_path(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='event.widget.css'),
re_path(r'^(?P<subevent>\d+)/widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='event.widget.productlist'),
]
organizer_patterns = [
url(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'),
url(r'^events/ical/$',
pretix.presale.views.organizer.OrganizerIcalDownload.as_view(),
name='organizer.ical'),
url(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='organizer.widget.productlist'),
url(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='organizer.widget.css'),
re_path(r'^$', pretix.presale.views.organizer.OrganizerIndex.as_view(), name='organizer.index'),
re_path(r'^events/ical/$',
pretix.presale.views.organizer.OrganizerIcalDownload.as_view(),
name='organizer.ical'),
re_path(r'^widget/product_list$', pretix.presale.views.widget.WidgetAPIProductList.as_view(),
name='organizer.widget.productlist'),
re_path(r'^widget/v1.css$', pretix.presale.views.widget.widget_css, name='organizer.widget.css'),
]
locale_patterns = [
url(r'^locale/set$', pretix.presale.views.locale.LocaleSet.as_view(), name='locale.set'),
url(r'^robots.txt$', pretix.presale.views.robots.robots_txt, name='robots.txt'),
url(r'^browserconfig.xml$', pretix.presale.views.theme.browserconfig_xml, name='browserconfig.xml'),
url(r'^site.webmanifest$', pretix.presale.views.theme.webmanifest, name='site.webmanifest'),
url(r'^widget/v1\.(?P<lang>[a-zA-Z0-9_\-]+)\.js$', pretix.presale.views.widget.widget_js, name='widget.js'),
re_path(r'^locale/set$', pretix.presale.views.locale.LocaleSet.as_view(), name='locale.set'),
re_path(r'^robots.txt$', pretix.presale.views.robots.robots_txt, name='robots.txt'),
re_path(r'^browserconfig.xml$', pretix.presale.views.theme.browserconfig_xml, name='browserconfig.xml'),
re_path(r'^site.webmanifest$', pretix.presale.views.theme.webmanifest, name='site.webmanifest'),
re_path(r'^widget/v1\.(?P<lang>[a-zA-Z0-9_\-]+)\.js$', pretix.presale.views.widget.widget_js, name='widget.js'),
]

View File

@@ -100,7 +100,7 @@ def _detect_event(request, require_live=True, require_plugin=None):
if hasattr(request, 'event'):
# Restrict locales to the ones available for this event
LocaleMiddleware().process_request(request)
LocaleMiddleware(NotImplementedError).process_request(request)
if require_live and not request.event.live:
can_access = (

View File

@@ -81,7 +81,6 @@ else:
if 'mysql' in db_backend:
db_options['charset'] = 'utf8mb4'
JSON_FIELD_AVAILABLE = db_backend in ('mysql', 'postgresql')
DATABASES = {
'default': {
@@ -653,7 +652,7 @@ LOGGING = {
'django.db.backends': {
'handlers': ['file', 'console'],
'level': 'INFO', # Do not output all the queries
'propagate': False,
'propagate': True,
},
'asyncio': {
'handlers': ['file', 'console'],

View File

@@ -208,7 +208,7 @@ $input-border-radius-small: $border-radius-small !default;
$input-border-focus: #66afe9 !default;
//** Placeholder text color
$input-color-placeholder: #767676 !default;
$input-color-placeholder: #999 !default;
//** Default `.form-control` height
$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default;

View File

@@ -9,19 +9,19 @@
$gray-darker: lighten(#000, 13.5%);
$gray-dark: lighten(#000, 20%);
$gray: lighten(#000, 33.5%);
$gray-light: lighten(#000, 55%);
$gray-light: lighten(#000, 60%);
$gray-lighter: lighten(#000, 93.5%);
$gray-lightest: lighten(#000, 97.25%);
$font-family-sans-serif: "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif !default;
$text-color: #222222 !default;
$text-muted: #767676 !default;
$text-muted: #999999 !default;
$brand-primary: #7f5a91 !default;
$brand-success: #50a167 !default;
$brand-info: #5f9cd4 !default;
$brand-warning: #ffb419 !default;
$brand-danger: #c44f4f !default;
$brand-danger: #d36060 !default;
$btn-default-border: #CCCCCC;

View File

@@ -45,9 +45,9 @@ $(function () {
if (data_type === 'B') {
var colors;
if (data[0].answer_bool) {
colors = ['#50A167', '#C44F4F'];
colors = ['#50A167', '#D36060'];
} else {
colors = ['#C44F4F', '#50A167'];
colors = ['#D36060', '#50A167'];
}
new Morris.Donut({
element: 'question_chart',
@@ -65,7 +65,7 @@ $(function () {
'#50A167',
'#FFB419',
'#5F9CD4',
'#C44F4F',
'#D36060',
'#83FFFA',
'#FF6C38',
'#1f5b8e',

View File

@@ -1,7 +1,3 @@
.help-block {
color: $text-muted;
}
td > .form-group {
margin-bottom: 0;
}

View File

@@ -75,7 +75,7 @@ $(function () {
$(".apply-voucher-toggle").click(function (e) {
$(".apply-voucher-toggle").hide();
$(".apply-voucher").show();
$(".apply-voucher input[type=text]").first().focus();
$(".apply-voucher input[ŧype=text]").first().focus();
e.preventDefault();
return true;
});

View File

@@ -292,7 +292,6 @@ $(function () {
$("input[data-required-if], select[data-required-if], textarea[data-required-if]").each(function () {
var dependent = $(this),
dependentLabel = $("label[for="+this.id+"]"),
dependency = $($(this).attr("data-required-if")),
update = function (ev) {
var enabled = (dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val();
@@ -300,12 +299,6 @@ $(function () {
dependent.prop('required', enabled);
}
dependent.closest('.form-group').toggleClass('required', enabled);
if (enabled) {
dependentLabel.append('<i class="sr-only label-required">, ' + gettext('required') + '</i>');
}
else {
dependentLabel.find(".label-required").remove();
}
};
update();
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("change", update);

View File

@@ -229,9 +229,7 @@ Vue.component('availbox', {
},
amount_selected: {
get: function () {
var selected = this.item.has_variations ? this.variation.amount_selected : this.item.amount_selected
if (selected === 0) return undefined;
return selected
return this.item.has_variations ? this.variation.amount_selected : this.item.amount_selected
},
set: function (value) {
// Unary operator to force boolean to integer conversion, as the HTML form submission
@@ -614,12 +612,7 @@ var shared_methods = {
},
resume: function () {
var redirect_url;
redirect_url = this.$root.target_url + 'w/' + widget_id + '/';
if (this.$root.subevent && !this.$root.cart_id) {
// button with subevent but no items
redirect_url += this.$root.subevent + '/';
}
redirect_url += '?iframe=1&locale=' + lang;
redirect_url = this.$root.target_url + 'w/' + widget_id + '/?iframe=1&locale=' + lang;
if (this.$root.cart_id) {
redirect_url += '&take_cart_id=' + this.$root.cart_id;
}
@@ -666,9 +659,7 @@ var shared_iframe_fragment = (
+ ' :name="$root.parent.widget_id" src="about:blank" v-once>'
+ 'Please enable frames in your browser!'
+ '</iframe>'
+ '<div class="pretix-widget-frame-close"><a href="#" @click.prevent="close">'
+ '<svg height="16px" viewBox="0 0 512 512" width="16px" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
+ '</a></div>'
+ '<div class="pretix-widget-frame-close"><a href="#" @click.prevent="close">X</a></div>'
+ '</div>'
+ '</div>'
);
@@ -1347,7 +1338,7 @@ var shared_root_methods = {
url += '&voucher=' + encodeURIComponent(this.$root.voucher_code);
}
if (cart_id) {
url += "&cart_id=" + encodeURIComponent(cart_id);
url += "&cart_id=" + cart_id;
}
if (this.$root.date !== null) {
url += "&year=" + this.$root.date.substr(0, 4) + "&month=" + this.$root.date.substr(5, 2);
@@ -1355,21 +1346,18 @@ var shared_root_methods = {
url += "&year=" + this.$root.week[0] + "&week=" + this.$root.week[1];
}
if (this.$root.style !== null) {
url = url + '&style=' + encodeURIComponent(this.$root.style);
url = url + '&style=' + this.$root.style;
}
var root = this.$root;
api._getJSON(url, function (data, xhr) {
if (typeof xhr.responseURL !== "undefined") {
if (typeof xhr.responseURL !== "undefined" && xhr.responseURL !== url) {
var new_url = xhr.responseURL.substr(0, xhr.responseURL.indexOf("/widget/product_list?") + 1);
var old_url = url.substr(0, url.indexOf("/widget/product_list?") + 1);
if (new_url !== old_url) {
if (root.subevent) {
new_url = new_url.substr(0, new_url.lastIndexOf("/", new_url.length - 1) + 1);
}
root.target_url = new_url;
root.reload();
return;
if (root.subevent) {
new_url = new_url.substr(0, new_url.lastIndexOf("/", new_url.length - 1) + 1);
}
root.target_url = new_url;
root.reload();
return;
}
if (data.weeks !== undefined) {
root.weeks = data.weeks;
@@ -1515,7 +1503,7 @@ var shared_root_computed = {
return form_target
},
useIframe: function () {
return !this.disable_iframe && (this.skip_ssl || site_is_secure());
return !this.disable_iframe && Math.min(screen.width, window.innerWidth) >= 800 && (this.skip_ssl || site_is_secure());
},
showPrices: function () {
var has_priced = false;

View File

@@ -1,7 +1,3 @@
.help-block {
color: $text-muted;
}
a.btn, button.btn {
max-width: 100%;
white-space: normal;

Some files were not shown because too many files have changed in this diff Show More