Compare commits

...

30 Commits

Author SHA1 Message Date
Richard Schreiber
2f8e6a2a4b only auto-submit subevent-select when context var "auto_submit" is True 2021-03-30 08:52:44 +02:00
Richard Schreiber
c084b91ab3 added columns to filter-form 2021-03-24 21:05:45 +01:00
Martin Gross
8baaa0a8c6 Add select2-subevent picker; display subevent start time 2021-03-24 13:50:27 +01:00
Richard Schreiber
53070f5d4b Cart: fix call to del if attribute is unknown when rendering a form label 2021-03-22 17:48:29 +01:00
Richard Schreiber
5685a349ea fix code style issues - missing whitespace around = operator 2021-03-22 17:05:13 +01:00
Richard Schreiber
1af69d5c76 Cart: Hide attendee information if not provided 2021-03-22 16:38:10 +01:00
Richard Schreiber
adddc7a71e A11y: add role=group and labels to multi-widgets (#2006)
* add role=group aria-labelledby to multiwidgets

* remove for-attribute from parent-label for grouped inputs

* add aria-labels to PhoneNumber-fields

* add aria-label to name multi-inputs
2021-03-22 15:19:29 +01:00
Richard Schreiber
11f23c3fd2 [a11y] Improved form error messages, descriptive labels, focusable toggle-link (#2002) 2021-03-19 16:13:25 +01:00
Raphael Michel
954fece6cf Log view: Page size selector 2021-03-19 10:49:03 +01:00
Richard Schreiber
8ef6adc3d5 A11y: make toggle-link for "view other date" focusable 2021-03-19 08:00:41 +01:00
Aksh Gupta
88ba7ab53a Refactor code quality issues (#2001) 2021-03-16 19:13:02 +01:00
Raphael Michel
eae55e4b5a Widget: Support button with subevent but without items 2021-03-16 19:01:43 +01:00
Raphael Michel
5ae839f62e Security Profile: Allow badge layouts for POS 2021-03-16 19:01:43 +01:00
Raphael Michel
7314d32422 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (4019 of 4019 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2021-03-16 17:30:23 +01:00
Maarten van den Berg
97d6ae8e55 Translated on translate.pretix.eu (Dutch)
Currently translated at 100.0% (4019 of 4019 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2021-03-16 17:30:23 +01:00
Maarten van den Berg
13063cb9d2 Translated on translate.pretix.eu (Dutch)
Currently translated at 99.3% (3993 of 4019 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2021-03-16 17:30:23 +01:00
Raphael Michel
2792813d95 Widget: Fix possible redirect loop 2021-03-16 17:26:20 +01:00
Martin Gross
d6aeefdf09 Add force-reactivate checkbox to order (#1997) 2021-03-16 16:49:37 +01:00
Raphael Michel
13056ef477 Widget: Do not prefill field with 0 2021-03-16 16:46:39 +01:00
Raphael Michel
6e2b5eae9a Widget: Open iframe even on mobile (to prevent breakage in WkWebView) 2021-03-16 16:16:59 +01:00
Raphael Michel
4cfb10b254 Widget: Make close icon independent of system font 2021-03-16 12:50:19 +01:00
Raphael Michel
ebd336e8cb Use new red color everywhere 2021-03-16 12:17:54 +01:00
Richard Schreiber
1357b010de [a11y] add missing labels on voucher-input and fix input.focus when revealing voucher-input via JS (#1998) 2021-03-16 12:17:47 +01:00
Richard Schreiber
09b2e69178 [a11y] Increase contrast on some colors for WCAG conformance (#1996)
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
2021-03-16 12:10:37 +01:00
Raphael Michel
5e34032821 Fix #256 -- Allow exact filtering of voucher tags 2021-03-15 16:16:49 +01:00
Richard Schreiber
46cee890f0 QuestionAnswer: Add UNIQUE keys on (orderposition, question) and (cartposition, question) (#1994) 2021-03-15 15:34:33 +01:00
Raphael Michel
4a2ac110b3 Voucher bulk creation: More efficient implementation and async task 2021-03-14 18:19:49 +01:00
Raphael Michel
7eefd3dc59 Recommend upper-case index on pretixbase_voucher.code 2021-03-14 18:04:19 +01:00
Raphael Michel
fdca62685c Revert "Update Django to 3.1 as well as other dependencies"
This reverts commit b3c9dca024.
2021-03-12 10:52:02 +01:00
Raphael Michel
7ae38b5e97 Fix TypeError during invoice creation 2021-03-12 10:51:50 +01:00
63 changed files with 779 additions and 571 deletions

View File

@@ -60,7 +60,10 @@ 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_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));
Also, if you use our ``pretix-shipping`` plugin::

View File

@@ -10,7 +10,7 @@ class FullAccessSecurityProfile:
class AllowListSecurityProfile:
allowlist = tuple()
allowlist = ()
def is_allowed(self, request):
key = (request.method, f"{request.resolver_match.namespace}:{request.resolver_match.url_name}")
@@ -95,6 +95,8 @@ 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 = [imp for imp in event.item_meta_properties.all()]
current = list(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, tuple()),
(forms.TimeField, serializers.TimeField, tuple()),
(forms.SplitDateTimeField, serializers.DateTimeField, tuple()),
(forms.DateTimeField, serializers.DateTimeField, tuple()),
(forms.DateField, serializers.DateField, ()),
(forms.TimeField, serializers.TimeField, ()),
(forms.SplitDateTimeField, serializers.DateTimeField, ()),
(forms.DateTimeField, serializers.DateTimeField, ()),
(forms.DecimalField, serializers.DecimalField, ('max_digits', 'decimal_places', 'min_value', 'max_value')),
(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()),
(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, ()),
)

View File

@@ -53,8 +53,8 @@ class DeviceSerializer(serializers.ModelSerializer):
class InitializeView(APIView):
authentication_classes = tuple()
permission_classes = tuple()
authentication_classes = ()
permission_classes = ()
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 dict())
final_attrs = self.build_attrs(attrs or {})
if 'required' in final_attrs:
del final_attrs['required']
id_ = final_attrs.get('id', None)
@@ -122,6 +122,8 @@ 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))
@@ -220,7 +222,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]))
super().__init__(choices=sorted(choices, key=lambda item: item[1]), attrs={'aria-label': pgettext_lazy('phonenumber', 'International area code')})
def render(self, name, value, *args, **kwargs):
return super().render(name, value or self.initial, *args, **kwargs)
@@ -243,7 +245,10 @@ class WrappedPhonePrefixSelect(Select):
class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
def __init__(self, attrs=None, initial=None):
widgets = (WrappedPhonePrefixSelect(initial), forms.TextInput())
attrs = {
'aria-label': pgettext_lazy('phonenumber', 'Phone number (without international area code)')
}
widgets = (WrappedPhonePrefixSelect(initial), forms.TextInput(attrs=attrs))
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(self.invoice.event.settings.invoice_address_custom_field, tags=[]).strip().replace('\n', '<br />\n'),
bleach.clean(str(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

@@ -0,0 +1,49 @@
# 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

@@ -983,6 +983,9 @@ 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:

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) or isinstance(op, self.ExtendOperation):
if isinstance(op, (self.AddOperation, 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) or isinstance(op, self.ExtendOperation):
elif isinstance(op, (self.AddOperation, 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,22 +3,21 @@ 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, User, Voucher
from pretix.base.models import Event, LogEntry, User, Voucher
from pretix.base.services.mail import mail
from pretix.base.services.tasks import TransactionAwareProfiledEventTask
from pretix.celery_app import app
@app.task(base=TransactionAwareProfiledEventTask, acks_late=True)
def vouchers_send(event: Event, vouchers: list, subject: str, message: str, recipients: list, user: int) -> None:
def vouchers_send(event: Event, vouchers: list, subject: str, message: str, recipients: list, user: int,
progress=None) -> None:
vouchers = list(Voucher.objects.filter(id__in=vouchers).order_by('id'))
user = User.objects.get(pk=user)
for r in recipients:
for ir, r in enumerate(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,
@@ -27,14 +26,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'])
v.save(update_fields=['tag', 'comment'])
v.log_action(
logs.append(v.log_action(
'pretix.voucher.sent',
user=user,
data={
@@ -42,5 +41,11 @@ 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': '#D36060',
'default': '#C44F4F',
'type': str,
'form_class': forms.CharField,
'serializer_class': serializers.CharField,

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) or isinstance(value, int):
if isinstance(value, (float, 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) or isinstance(value, int):
if isinstance(value, (float, int)):
value = Decimal(value)
if not isinstance(value, Decimal):
raise TypeError("Invalid data type passed to money filter: %r" % type(value))

View File

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

View File

@@ -4,43 +4,25 @@ 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 AsyncAction:
task = None
class AsyncMixin:
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
@@ -50,11 +32,6 @@ class AsyncAction:
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 {}
@@ -86,7 +63,7 @@ class AsyncAction:
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 mssage itself
# but handle the message itself
data.update({
'redirect': self.get_success_url(res.info),
'success': True,
@@ -95,7 +72,7 @@ class AsyncAction:
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 mssage itself
# but handle the message itself
data.update({
'redirect': self.get_error_url(),
'success': False,
@@ -159,3 +136,124 @@ class AsyncAction:
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,6 +1439,8 @@ 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 = [k for k in get_all_sales_channels().keys()]
self.instance.sales_channels = list(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 ConfirmPaymentForm(forms.Form):
class ForceQuotaConfirmationForm(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,7 +101,15 @@ class ConfirmPaymentForm(forms.Form):
del self.fields['force']
class CancelForm(ConfirmPaymentForm):
class ConfirmPaymentForm(ForceQuotaConfirmationForm):
pass
class ReactivateOrderForm(ForceQuotaConfirmationForm):
pass
class CancelForm(ForceQuotaConfirmationForm):
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 Lower
from django.db.models.functions import Upper
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_lower=Lower('code')
).filter(code_lower__in=[c.lower() for c in data['codes']])
code_upper=Upper('code')
).filter(code_upper__in=[c.upper() for c in data['codes']])
if vouchers.exists():
raise ValidationError(_('A voucher with one of these codes already exists.'))
@@ -377,26 +377,5 @@ class VoucherBulkForm(VoucherForm):
return data
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
def post_bulk_save(self, objs):
pass

View File

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

View File

@@ -154,6 +154,11 @@ This signal allows you to replace the form class that is used for modifying vouc
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.
"""

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" %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
</form>
</form>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% load i18n %}
<p>
<select name="subevent" class="form-control simple-subevent-choice" data-model-select2="event"
<select name="subevent" class="form-control{% if auto_submit %} simple-subevent-choice{% endif %}" 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" %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
</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" %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
</form>
{% endif %}
{% if quotas|length == 0 %}

View File

@@ -24,6 +24,7 @@
<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">
<form action="" method="post" class="form-horizontal" data-asynctask>
{% 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={{ t.tag|urlencode }}">
<a href="{% url "control:event.vouchers" organizer=request.event.organizer.slug event=request.event.slug %}?tag={{ '"'|add:t.tag|add:'"'|urlencode }}">
{{ t.tag }}
</a>
{% else %}

View File

@@ -48,15 +48,9 @@
</p>
{% endif %}
{% 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 class="col-md-6">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</div>
{% endif %}
<button class="btn btn-large btn-primary" type="submit">
{% trans "Send as many vouchers as possible" %}
@@ -80,52 +74,50 @@
</div>
</div>
<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 }}
<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 }}
</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 %}
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
<a href="?{% url_replace request "download" "yes" %}"
<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" %}"
class="btn btn-default"><i class="fa fa-download"></i>
{% trans "Download list" %}</a>
{% trans "Download list" %}</a>
</div>
</form>
</p>
<form method="post" action="?next={{ request.get_full_path|urlencode }}">
{% csrf_token %}
<div class="table-responsive">
@@ -166,7 +158,7 @@
{% endif %}
</td>
{% if request.event.has_subevents %}
<td>{{ e.subevent.name }} {{ e.subevent.get_date_range_display }}</td>
<td>{{ e.subevent.name }} {{ e.subevent.get_date_range_display }} {{ e.subevent.date_from|date:"TIME_FORMAT" }}</td>
{% endif %}
<td>
{{ e.created|date:"SHORT_DATETIME_FORMAT" }}

View File

@@ -955,11 +955,10 @@ class EventDelete(RecentAuthenticationRequiredMixin, EventPermissionRequiredMixi
return reverse('control:index')
class EventLog(EventPermissionRequiredMixin, ListView):
class EventLog(EventPermissionRequiredMixin, PaginationMixin, 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(
@@ -1363,7 +1362,7 @@ class QuickSetupView(FormView):
tax_rule=tax_rule,
admission=True,
position=i,
sales_channels=[k for k in get_all_sales_channels().keys()]
sales_channels=list(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,
OrderRefundForm, OtherOperationsForm, ReactivateOrderForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.signals import order_search_forms
@@ -1424,11 +1424,24 @@ 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
user=self.request.user,
force=self.reactivate_form.cleaned_data.get('force', False)
)
messages.success(self.request, _('The order has been reactivated.'))
except OrderError as e:
@@ -1453,6 +1466,7 @@ 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,12 +1438,11 @@ class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionR
return redirect(success_url)
class LogView(OrganizerPermissionRequiredMixin, ListView):
class LogView(OrganizerPermissionRequiredMixin, PaginationMixin, 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,7 +3,8 @@ import io
from defusedcsv import csv
from django.conf import settings
from django.contrib import messages
from django.db import transaction
from django.core.exceptions import ValidationError
from django.db import connection, transaction
from django.db.models import Sum
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
@@ -21,7 +22,9 @@ 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
@@ -287,13 +290,19 @@ class VoucherGo(EventPermissionRequiredMixin, View):
return redirect('control:event.vouchers', event=request.event.slug, organizer=request.event.organizer.slug)
class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
model = Voucher
template_name = 'pretixcontrol/vouchers/bulk.html'
permission = 'can_change_vouchers'
context_object_name = 'voucher'
def get_success_url(self) -> str:
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):
return reverse('control:event.vouchers', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
@@ -316,34 +325,84 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
i.redeemed = 0
kwargs['instance'] = i
else:
kwargs['instance'] = Voucher(event=self.request.event)
kwargs['instance'] = Voucher(event=self.request.event, code=None)
return kwargs
@transaction.atomic
def form_valid(self, form):
log_entries = []
objs = form.save(self.request.event)
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.))
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)
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)
if form.cleaned_data['send']:
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())
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.')
def get_form_class(self):
form_class = VoucherBulkForm
@@ -357,11 +416,6 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
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) or isinstance(arg, tuple):
if isinstance(arg, (list, tuple)):
ret += list(arg)
else:
ret.append(arg)

View File

@@ -12,8 +12,8 @@ class Thumbnail(models.Model):
unique_together = (('source', 'size'),)
def modelcopy(obj: models.Model):
n = obj.__class__()
def modelcopy(obj: models.Model, **kwargs):
n = obj.__class__(**kwargs)
for f in obj._meta.fields:
val = getattr(obj, f.name)
if isinstance(val, models.Model):

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-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"
"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"
"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 3.10.3\n"
"X-Generator: Weblate 4.4.2\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
#, fuzzy, python-brace-format
#| msgid "Meta data property '{name}' does not exist."
#, python-brace-format
msgid "Meta data property '{name}' does not allow value '{value}'."
msgstr "Metadataeigenschap '{name}' bestaat niet."
msgstr ""
"De waarde '{value}' is niet toegestaan voor de metadataeigenschap '{name}'."
#: 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 ""
msgstr "U moet de meta-eigenschap \"{property}\" invullen."
#: pretix/base/models/event.py:1065
msgid ""
@@ -2324,50 +2324,49 @@ msgstr ""
"onderstrepingstekens bevatten."
#: pretix/base/models/event.py:1379
#, fuzzy
#| msgid "Default language"
msgid "Default value"
msgstr "Standaardtaal"
msgstr "Standaardwaarde"
#: pretix/base/models/event.py:1381
#, fuzzy
#| msgid "Can change organizer settings"
msgid "Can only be changed by organizer-level administrators"
msgstr "Kan organisatorinstellingen wijzigen"
msgstr "Kan alleen worden gewijzigd door beheerders van deze organisator"
#: pretix/base/models/event.py:1383
#, fuzzy
#| msgid "Search for events"
msgid "Required for events"
msgstr "Zoek naar evenementen"
msgstr "Verplicht voor 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 "Totaalwaarde"
msgstr "Toegestane waarden"
#: 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 quotum selecteren dat bij een ander evenement hoort."
msgstr ""
"U kunt geen standaardwaarde instellen die niet in de lijst met toegestane "
"waarden staat."
#: pretix/base/models/fields.py:12
msgid "No value can contain the delimiter character."
@@ -4421,12 +4420,7 @@ msgid "Payment process description in order confirmation emails"
msgstr "Beschrijving van betalingsproces in bevestigingsmails"
#: pretix/base/payment.py:971
#, 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}."
#, 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 "
@@ -4436,7 +4430,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}, {total}, {currency} en {total_with_currency} "
"plaatsaanduidingen {order}, {amount}, {currency} en {amount_with_currency} "
"gebruiken."
#: pretix/base/payment.py:978
@@ -4444,12 +4438,7 @@ msgid "Payment process description for pending orders"
msgstr "Beschrijving van betalingsproces voor openstaande bestellingen"
#: pretix/base/payment.py:979
#, 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}."
#, 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 "
@@ -4457,8 +4446,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}, {total}, {currency} "
"en {total_with_currency} gebruiken."
"de betaling. U kunt hier de plaatsaanduidingen {order}, {amount}, {currency} "
"en {amount_with_currency} gebruiken."
#: pretix/base/payment.py:1028
msgid "Offsetting"
@@ -4804,13 +4793,11 @@ msgstr "Informatietekst organisator"
#: pretix/base/pdf.py:298
msgid "Event organizer info text"
msgstr "Informatietekst van de organisator van het evenement"
msgstr "Informatie over 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 "Informatietekst van de organisator van het evenement"
msgstr "Informatie over het evenement"
#: pretix/base/pdf.py:307
msgid "Printing date"
@@ -6454,64 +6441,52 @@ 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 van gasten"
msgstr "Vraag om namen"
#: 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 "Een inschrijving op de wachtlijst is aangepast."
msgstr "Vraag om een naam bij het aanmelden voor de wachtlijst."
#: pretix/base/settings.py:994
#, fuzzy
#| msgid "Require customer name"
msgid "Require name"
msgstr "Verplicht klantnaam"
msgstr "Verplicht namen"
#: 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 "Een inschrijving op de wachtlijst is aangepast."
msgstr ""
"Maakt het opgeven van een naam verplicht om in te schrijven voor de "
"wachtlijst."
#: 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 bij bestelling"
msgstr "Vraag om een telefoonnummer"
#: 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 "Een inschrijving op de wachtlijst is aangepast."
msgstr "Vraag om een telefoonnummer bij het inschrijven voor de wachtlijst."
#: pretix/base/settings.py:1015
#, fuzzy
#| msgid "Require a phone number per order"
msgid "Require phone number"
msgstr "Verplicht het opgeven van een telefoonnummer"
msgstr "Verplicht 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 "Een inschrijving op de wachtlijst is aangepast."
msgstr ""
"Maakt het opgeven van een telefoonnummer verplicht bij het inschrijven voor "
"de wachtlijst."
#: pretix/base/settings.py:1026
#, fuzzy
#| msgid "Voucher explanation"
msgid "Phone number explanation"
msgstr "Voucher-uitleg"
msgstr "Uitleg voor telefoonnummer"
#: 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"
@@ -6641,6 +6616,7 @@ 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"
@@ -6836,10 +6812,8 @@ msgstr ""
"contactinformatie en eventuele wettelijk verplichte informatie bevat."
#: pretix/base/settings.py:1387
#, fuzzy
#| msgid "Cached ticket files"
msgid "Attach ticket files"
msgstr "Gecachete ticketbestanden"
msgstr "Ticketbestanden bijvoegen bij e-mails"
#: pretix/base/settings.py:1389
#, python-brace-format
@@ -6847,6 +6821,8 @@ 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"
@@ -7583,22 +7559,16 @@ msgstr ""
"informatie vraagt."
#: pretix/base/settings.py:2073
#, fuzzy
#| msgid "Additional fee"
msgid "Additional success message"
msgstr "Extra kosten"
msgstr "Extra succesbericht na het plaatsen van een bestelling"
#: 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 naast de standaardtekst worden getoond op de "
"bevestigingspagina voor openstaande bestellingen."
"Deze tekst zal worden getoond nadat een klant een bestelling heeft "
"geplaatst. Deze tekst wordt samen met de standaardtekst getoond."
#: pretix/base/settings.py:2086
msgid "Help text of the phone number field"
@@ -7883,12 +7853,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 e-mailadressen van de wachtlijst verwijderen."
msgstr ""
"Dit zal alle namen, e-mailadressen en telefoonnummers van de wachtlijst "
"verwijderen."
#: pretix/base/shredder.py:239
msgid "Attendee info"
@@ -8093,25 +8063,18 @@ 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"
msgstr "aangemaakt door"
#: 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"
@@ -8126,10 +8089,8 @@ 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
@@ -8379,10 +8340,9 @@ msgid "Do not copy"
msgstr "Niet kopiëren"
#: pretix/control/forms/event.py:274 pretix/control/forms/subevents.py:309
#, fuzzy, python-brace-format
#| msgid "Default language"
#, python-brace-format
msgid "Default ({value})"
msgstr "Standaardtaal"
msgstr "Standaard ({value})"
#: pretix/control/forms/event.py:326 pretix/control/forms/organizer.py:84
msgid "Custom domain"
@@ -8902,7 +8862,7 @@ msgstr "Annulering aangevraagd"
#: pretix/control/forms/filter.py:158
msgid "Fully canceled but invoice not canceled"
msgstr ""
msgstr "Compleet geannuleerd, maar factuur niet geannuleerd"
#: pretix/control/forms/filter.py:160
msgid "Payment process"
@@ -9532,10 +9492,8 @@ 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 gebruiker per e-mail op de hoogte"
msgstr "Stel de klant per e-mail op de hoogte"
#: pretix/control/forms/orders.py:114
msgid "Keep a cancellation fee of"
@@ -9556,10 +9514,8 @@ 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"
msgstr "Genereer annulering voor factuur"
#: pretix/control/forms/orders.py:158
msgid "Payment amount"
@@ -9934,21 +9890,17 @@ 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 "Huidige waarde"
msgstr "Houd huidige waarden"
#: pretix/control/forms/subevents.py:113 pretix/control/forms/subevents.py:121
msgid "Selection contains various values"
msgstr ""
msgstr "Selectie bevat verschillende waarden"
#: pretix/control/forms/subevents.py:369
msgid "Exclude these dates instead of adding them."
@@ -10359,40 +10311,28 @@ 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 bestelling is aangepast."
msgstr "De organisator is veranderd."
#: pretix/control/logdisplay.py:277
#, fuzzy
#| msgid "The team settings have been changed."
msgid "The organizer settings have been changed."
msgstr "De teaminstellingen zijn aangepast."
msgstr "De instellingen van de organisator 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 "De nieuwe organisator is aangemaakt."
msgstr "Cadeaubonacceptatie voor een andere organisator is toegevoegd."
#: pretix/control/logdisplay.py:279
#, fuzzy
#| msgid "The new organizer has been created."
msgid "Gift card acceptance for another organizer has been removed."
msgstr "De nieuwe organisator is aangemaakt."
msgstr "Cadeaubonacceptatie voor een andere organisator is verwijderd."
#: pretix/control/logdisplay.py:280
#, fuzzy
#| msgid "The user has been created."
msgid "The webhook has been created."
msgstr "De gebruiker is aangemaakt."
msgstr "De webhook is aangemaakt."
#: pretix/control/logdisplay.py:281
#, fuzzy
#| msgid "The gate has been changed."
msgid "The webhook has been changed."
msgstr "De toegangslocatie is aangepast."
msgstr "De webhook is aangepast."
#: pretix/control/logdisplay.py:282
msgid "The event's internal comment has been updated."
@@ -10403,10 +10343,8 @@ 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 "Dit evenement is verwijderd."
msgstr "Een webhook is verwijderd."
#: pretix/control/logdisplay.py:285
msgid "The order details have been changed."
@@ -12437,25 +12375,19 @@ msgstr "Geocoding-data © OpenStreetMap"
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:4
msgid "Failed to retrieve geo coordinates"
msgstr ""
msgstr "Geo-coördinaten ophalen mislukt"
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:5
#, fuzzy
#| msgid "Geo coordinates"
msgid "Retrieving geo coordinates …"
msgstr "Geo-coördinaten"
msgstr "Geo-coördinaten ophalen…"
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:6
#, fuzzy
#| msgid "Geo coordinates"
msgid "Geo coordinates updated"
msgstr "Geo-coördinaten"
msgstr "Geo-coördinaten bijgewerkt"
#: pretix/control/templates/pretixcontrol/event/fragment_geodata_autoupdate.html:7
#, fuzzy
#| msgid "Update comment"
msgid "Update map?"
msgstr "Commentaar bijwerken"
msgstr "Kaart bijwerken?"
#: pretix/control/templates/pretixcontrol/event/fragment_timeline.html:5
msgid "Your timeline"
@@ -15564,7 +15496,7 @@ msgstr "VOLLEDIG BETAALD"
#: pretix/control/templates/pretixcontrol/orders/index.html:175
msgid "INVOICE NOT CANCELED"
msgstr ""
msgstr "FACTUUR NIET GEANNULEERD"
#: pretix/control/templates/pretixcontrol/orders/index.html:186
msgid "Sum over all pages"
@@ -15950,10 +15882,8 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/logs.html:4
#: pretix/control/templates/pretixcontrol/organizers/logs.html:6
#, fuzzy
#| msgid "Organizers"
msgid "Organizer logs"
msgstr "Organisatoren"
msgstr "Organisatorlogs"
#: pretix/control/templates/pretixcontrol/organizers/properties.html:7
msgid ""
@@ -15967,34 +15897,24 @@ 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 "Nieuw product aanmaken"
msgstr "Nieuwe eigenschap aanmaken"
#: pretix/control/templates/pretixcontrol/organizers/properties.html:20
#, fuzzy
#| msgid "Add property"
msgid "Property"
msgstr "Eigenschap toevoegen"
msgstr "Eigenschap"
#: pretix/control/templates/pretixcontrol/organizers/property_delete.html:5
#, fuzzy
#| msgid "Delete product"
msgid "Delete property:"
msgstr "Product verwijderen"
msgstr "Verwijder eigenschap:"
#: 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 toegangslocatie wilt verwijderen?"
msgstr "Weet u zeker dat u de eigenschap wilt verwijderen?"
#: pretix/control/templates/pretixcontrol/organizers/property_edit.html:6
#, fuzzy
#| msgid "Add property"
msgid "Property:"
msgstr "Eigenschap toevoegen"
msgstr "Eigenschap:"
#: pretix/control/templates/pretixcontrol/organizers/team_delete.html:5
msgid "Delete team:"
@@ -16374,10 +16294,8 @@ msgid "Light"
msgstr "Licht"
#: pretix/control/templates/pretixcontrol/pdf/index.html:349
#, fuzzy
#| msgid "E-mail content"
msgid "Image content"
msgstr "E-mailinhoud"
msgstr "Afbeeldingsinhoud"
#: pretix/control/templates/pretixcontrol/pdf/index.html:360
msgid "Text content"
@@ -16413,7 +16331,7 @@ msgstr "pretix-logo"
#: pretix/control/templates/pretixcontrol/pdf/index.html:410
msgid "Dynamic image"
msgstr ""
msgstr "Dynamische afbeelding"
#: pretix/control/templates/pretixcontrol/search/orders.html:104
msgid ""
@@ -16666,18 +16584,14 @@ 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 "Maak meerdere datums aan"
msgstr "Meerdere datums veranderen"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:13
#, fuzzy, python-format
#| msgid "Enable selected"
#, python-format
msgid "%(number)s selected"
msgstr "Schakel geselecteerde in"
msgstr "%(number)s geselecteerd"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:171
msgid ""
@@ -16686,12 +16600,18 @@ 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
@@ -16751,7 +16671,7 @@ msgstr "Maak meerdere nieuwe datums"
#: pretix/control/templates/pretixcontrol/subevents/index.html:70
msgid "select all rows for batch-operation"
msgstr ""
msgstr "Selecteer alle rijen voor batch-handeling"
#: pretix/control/templates/pretixcontrol/subevents/index.html:77
msgid "Begin"
@@ -16759,11 +16679,11 @@ msgstr "Begin"
#: pretix/control/templates/pretixcontrol/subevents/index.html:100
msgid "Select all results on other pages as well"
msgstr ""
msgstr "Selecteer ook alle resultaten op andere pagina's"
#: pretix/control/templates/pretixcontrol/subevents/index.html:111
msgid "select row for batch-operation"
msgstr ""
msgstr "Selecteer rij voor batch-handeling"
#: pretix/control/templates/pretixcontrol/subevents/index.html:152
msgctxt "subevent"
@@ -16781,23 +16701,16 @@ 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 "Geen datum geselecteerd."
msgstr "Bewerk geselecteerde"
#: pretix/control/templates/pretixcontrol/subevents/index.html:179
#, fuzzy
#| msgid "Disable selected"
msgid "Activate selected"
msgstr "Schakel geselecteerde uit"
msgstr "Activeer geselecteerde"
#: pretix/control/templates/pretixcontrol/subevents/index.html:182
#, fuzzy
#| msgid "Delete selected"
msgid "Deactivate selected"
msgstr "Verwijder geselecteerde"
msgstr "Deactiveer geselecteerde"
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:4
#: pretix/control/templates/pretixcontrol/user/2fa_add.html:6
@@ -18695,16 +18608,12 @@ 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 "Het product is aangemaakt."
msgstr "De eigenschap is aangemaakt."
#: pretix/control/views/organizer.py:1437
#, fuzzy
#| msgid "The selected product has been deleted."
msgid "The selected property has been deleted."
msgstr "Het gekozen product is verwijderd."
msgstr "De eigenschap is verwijderd."
#: pretix/control/views/pdf.py:53
msgid "The uploaded PDF file is too large."
@@ -20457,19 +20366,14 @@ 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 naar klanten met bestelstatus"
msgstr "Stuur alleen naar klanten met bestellingen geplaatst na"
#: 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 van subevenementen die beginnen voor"
msgstr "Stuur alleen naar klanten met bestellingen geplaatst voor"
#: pretix/plugins/sendmail/forms.py:108
msgid "Everyone who created a ticket order"
@@ -20553,16 +20457,13 @@ msgid "There are no orders matching this selection."
msgstr "Er zijn geen bestellingen die overeenkomen met deze selectie."
#: pretix/plugins/sendmail/views.py:180
#, fuzzy, python-format
#| msgid ""
#| "Your message has been queued and will be sent to the contact addresses of "
#| "%d orders in the next minutes."
#, python-format
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 in de komende minuten naar de e-"
"mailadressen van %d bestellingen worden verstuurd."
"Uw bericht is in de wachtrij gezet en zal binnenkort naar de e-mailadressen "
"van %d bestellingen worden verstuurd."
#: pretix/plugins/statistics/__init__.py:9
#: pretix/plugins/statistics/__init__.py:12
@@ -21553,12 +21454,9 @@ 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
@@ -21576,10 +21474,8 @@ msgid "Organizer: {organizer}"
msgstr "Organisator: {organizer}"
#: pretix/presale/templates/pretixpresale/base.html:59
#, fuzzy
#| msgid "Toggle navigation"
msgid "Footer Navigation"
msgstr "Navigatie schakelen"
msgstr "Footer-navigatie"
#: pretix/presale/templates/pretixpresale/base_footer.html:36
#, python-format
@@ -21596,10 +21492,8 @@ 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 "Gebruik talen"
msgstr "taal kiezen"
#: pretix/presale/templates/pretixpresale/event/base.html:52
#, python-format
@@ -21758,7 +21652,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 ""
msgstr "Stap %(current)s van %(total)s: %(label)s"
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:12
#: pretix/presale/templates/pretixpresale/event/checkout_base.html:57
@@ -22030,16 +21924,12 @@ msgid "Redeem voucher"
msgstr "Voucher inwisselen"
#: pretix/presale/templates/pretixpresale/event/fragment_checkoutflow.html:13
#, fuzzy
#| msgid "Completion date"
msgid "Completed:"
msgstr "Voltooiingsdatum"
msgstr "Voltooid:"
#: pretix/presale/templates/pretixpresale/event/fragment_checkoutflow.html:15
#, fuzzy
#| msgid "Currency"
msgid "Current:"
msgstr "Munteenheid"
msgstr "Huidig:"
#: pretix/presale/templates/pretixpresale/event/fragment_checkoutflow.html:29
msgctxt "checkoutflow"
@@ -22053,39 +21943,27 @@ 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 uw tickets per e-mail naar u verzonden. Kijk in uw inbox om uw "
"tickets te ontvangen."
msgstr "We hebben een e-mail naar u verzonden."
#: 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 ""
"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."
"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."
#: 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 ""
"Als de email geen bijlage heeft kunt u op de link in de mail klikken om de "
"tickets te downloaden."
"U kunt uw tickets op deze pagina downloaden zodra u op de link in onze e-"
"mail klikt."
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:30
msgid ""
@@ -22139,17 +22017,14 @@ msgid "Confirmed"
msgstr "Bevestigd"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:9
#, fuzzy
#| msgid "Uncategorized"
msgid "Uncategorized products"
msgstr "Ongecategoriseerd"
msgstr "Ongecategoriseerde producten"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:28
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:187
#, fuzzy, python-format
#| msgid "Show all events of %(name)s"
#, python-format
msgid "Show full-size image of %(item)s"
msgstr "Toon alle evenementen van %(name)s"
msgstr "Toon volledige afbeelding van %(item)s"
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:63
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:120
@@ -22217,10 +22092,12 @@ 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 ""
msgstr "Algemene informatie, nieuwe producten aan winkelwagen toevoegen"
#: pretix/presale/templates/pretixpresale/event/index.html:65
#: pretix/presale/templates/pretixpresale/event/index.html:82

View File

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

View File

@@ -159,7 +159,7 @@ class CheckInListMixin(BaseExporter):
), self.event.timezone)
qs = qs.filter(subevent__date_from__lt=dt)
o = tuple()
o = ()
if self.event.has_subevents and not cl.subevent:
o = ('subevent__date_from', 'subevent__name')

View File

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

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" %}
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" with auto_submit=True %}
</form>
{% endif %}
{% if has_orders %}

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
from django.forms import CheckboxInput, CheckboxSelectMultiple, RadioSelect
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='', optional=False):
def render_label(content, label_for=None, label_class=None, label_title='', label_id='', optional=False, is_valid=None):
"""
Render a label with content
"""
@@ -19,6 +19,17 @@ def render_label(content, label_for=None, label_class=None, label_title='', opti
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
@@ -26,17 +37,17 @@ def render_label(content, label_for=None, label_class=None, label_title='', opti
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
del(attrs['for'])
opt = ""
if 'for' in attrs:
del(attrs['for'])
else:
opt = mark_safe('<i class="sr-only"> {}</i>'.format(pgettext('form', 'required'))) if not optional else ''
opt += '<i class="sr-only label-required">, {}</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=opt,
opt=mark_safe(opt),
content=text_value(content),
)
@@ -45,6 +56,7 @@ 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
@@ -64,6 +76,26 @@ 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()
@@ -73,11 +105,25 @@ 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=self.field.id_for_label,
label_for=label_for,
label_class=self.get_label_class(),
optional=not required and not isinstance(self.widget, CheckboxInput)
label_id=label_id,
optional=not required and not isinstance(self.widget, CheckboxInput),
is_valid=is_valid
) + html
return html
@@ -88,3 +134,10 @@ 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

@@ -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:"" %}">
<a href="{% eventurl request.event "presale:event.checkout" step="payment" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans "Modify payment" %}">
<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">
<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" %}">
<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:"" %}">
<a href="{% eventurl request.event "presale:event.checkout" step="questions" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans "Modify contact information" %}">
<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="horizontal" %}
{% bootstrap_form contact_form layout="checkout" %}
{% if not invoice_address_asked and event.settings.invoice_name_required %}
{% bootstrap_form invoice_form layout="horizontal" %}
{% bootstrap_form invoice_form layout="checkout" %}
{% endif %}
</div>
</div>
@@ -46,7 +46,7 @@
{{ event.settings.invoice_address_explanation_text|rich_text }}
</div>
{% endif %}
{% bootstrap_form invoice_form layout="horizontal" %}
{% bootstrap_form invoice_form layout="checkout" %}
</div>
</div>
</details>

View File

@@ -51,49 +51,47 @@
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee name" %}">
{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "No attendee name provided" %}</em>{% endif %}
</span>
</dd>
{% endif %}
{% if line.item.admission and event.settings.attendee_emails_asked %}
{% if line.item.admission and event.settings.attendee_emails_asked and line.attendee_email %}
<dt class="sr-only">
{% trans "Attendee email" %}
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee email" %}">
{% if line.attendee_email %}{{ line.attendee_email }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
{{ line.attendee_email }}
</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 %}
{% if line.item.admission and event.settings.attendee_company_asked and line.company %}
<dt class="sr-only">
{% trans "Attendee company" %}
</dt>
<dd class="toplevel">
<span data-toggle="tooltip" title="{% trans "Attendee company" %}">
{% if line.company %}{{ line.company }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
{{ line.company }}
</span>
</dd>
{% endif %}
{% if line.item.admission and event.settings.attendee_addresses_asked %}
<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 %}
{% 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" %}">
{{ 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 %}
{% else %}
<em>{% trans "not answered" %}</em>
{% endif %}
</span>
</dd>
</span>
</dd>
{% endif %}
{% endif %}
{% for q in line.questions %}
<dt>{{ q.question }}</dt>
@@ -140,6 +138,7 @@
{% 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 %}
@@ -163,6 +162,7 @@
</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,6 +189,7 @@
{% 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 %}
@@ -197,6 +198,7 @@
</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 %}
@@ -306,11 +308,12 @@
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" placeholder="{% trans "Voucher code" %}" aria-label="{% trans "Voucher code" %}">
<input type="text" class="form-control" name="voucher" id="voucher_code" placeholder="{% trans "Voucher code" %}" aria-label="{% trans "Voucher code" %}">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit" aria-label="{% trans "Redeem voucher" %}">
<span class="fa fa-check" aria-hidden="true"></span>
<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>
</span>
</div>

View File

@@ -6,12 +6,14 @@
{% load eventsignal %}
{% load rich_text %}
{% for tup in items_by_category %}
<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 %}>
<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 %}>
{% 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">
<a class="subevent-toggle btn btn-primary btn-block btn-lg" href="#subevent-list">
<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">
<a class="subevent-toggle btn btn-default btn-block" href="#subevent-list">
{% trans "View other date" %}
</a>
{% endif %}
{% else %}
<h3>{% trans "Choose date to book a ticket" %}</h3>
{% endif %}
<div class="panel panel-default subevent-list">
<div class="panel panel-default subevent-list" id="subevent-list">
<div class="panel-heading">
{% if subevent %}
{% trans "Other dates" context "subevent" %}
@@ -298,10 +298,11 @@
<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" %}" aria-label="{% trans "Voucher code" %}">
placeholder="{% 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 %}">
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}" aria-label="{% trans "Change ordered items" %}">
<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 %}">
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}" aria-label="{% trans "Change your information" %}">
<span class="fa fa-edit" aria-hidden="true"></span>
{% trans "Change details" %}
</a>

View File

@@ -653,7 +653,7 @@ LOGGING = {
'django.db.backends': {
'handlers': ['file', 'console'],
'level': 'INFO', # Do not output all the queries
'propagate': True,
'propagate': False,
},
'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: #999 !default;
$input-color-placeholder: #767676 !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, 60%);
$gray-light: lighten(#000, 55%);
$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: #999999 !default;
$text-muted: #767676 !default;
$brand-primary: #7f5a91 !default;
$brand-success: #50a167 !default;
$brand-info: #5f9cd4 !default;
$brand-warning: #ffb419 !default;
$brand-danger: #d36060 !default;
$brand-danger: #c44f4f !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', '#D36060'];
colors = ['#50A167', '#C44F4F'];
} else {
colors = ['#D36060', '#50A167'];
colors = ['#C44F4F', '#50A167'];
}
new Morris.Donut({
element: 'question_chart',
@@ -65,7 +65,7 @@ $(function () {
'#50A167',
'#FFB419',
'#5F9CD4',
'#D36060',
'#C44F4F',
'#83FFFA',
'#FF6C38',
'#1f5b8e',

View File

@@ -1,3 +1,7 @@
.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[ŧype=text]").first().focus();
$(".apply-voucher input[type=text]").first().focus();
e.preventDefault();
return true;
});

View File

@@ -292,6 +292,7 @@ $(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();
@@ -299,6 +300,12 @@ $(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,7 +229,9 @@ Vue.component('availbox', {
},
amount_selected: {
get: function () {
return this.item.has_variations ? this.variation.amount_selected : this.item.amount_selected
var selected = this.item.has_variations ? this.variation.amount_selected : this.item.amount_selected
if (selected === 0) return undefined;
return selected
},
set: function (value) {
// Unary operator to force boolean to integer conversion, as the HTML form submission
@@ -612,7 +614,12 @@ var shared_methods = {
},
resume: function () {
var redirect_url;
redirect_url = this.$root.target_url + 'w/' + widget_id + '/?iframe=1&locale=' + lang;
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;
if (this.$root.cart_id) {
redirect_url += '&take_cart_id=' + this.$root.cart_id;
}
@@ -659,7 +666,9 @@ 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">X</a></div>'
+ '<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>'
+ '</div>'
);
@@ -1338,7 +1347,7 @@ var shared_root_methods = {
url += '&voucher=' + encodeURIComponent(this.$root.voucher_code);
}
if (cart_id) {
url += "&cart_id=" + cart_id;
url += "&cart_id=" + encodeURIComponent(cart_id);
}
if (this.$root.date !== null) {
url += "&year=" + this.$root.date.substr(0, 4) + "&month=" + this.$root.date.substr(5, 2);
@@ -1346,18 +1355,21 @@ var shared_root_methods = {
url += "&year=" + this.$root.week[0] + "&week=" + this.$root.week[1];
}
if (this.$root.style !== null) {
url = url + '&style=' + this.$root.style;
url = url + '&style=' + encodeURIComponent(this.$root.style);
}
var root = this.$root;
api._getJSON(url, function (data, xhr) {
if (typeof xhr.responseURL !== "undefined" && xhr.responseURL !== url) {
if (typeof xhr.responseURL !== "undefined") {
var new_url = xhr.responseURL.substr(0, xhr.responseURL.indexOf("/widget/product_list?") + 1);
if (root.subevent) {
new_url = new_url.substr(0, new_url.lastIndexOf("/", new_url.length - 1) + 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;
}
root.target_url = new_url;
root.reload();
return;
}
if (data.weeks !== undefined) {
root.weeks = data.weeks;
@@ -1503,7 +1515,7 @@ var shared_root_computed = {
return form_target
},
useIframe: function () {
return !this.disable_iframe && Math.min(screen.width, window.innerWidth) >= 800 && (this.skip_ssl || site_is_secure());
return !this.disable_iframe && (this.skip_ssl || site_is_secure());
},
showPrices: function () {
var has_priced = false;

View File

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

View File

@@ -788,5 +788,30 @@
}
}
@media (max-width: 800px) {
.pretix-widget-frame-holder .pretix-widget-frame-inner {
left: 0;
width: 100%;
height: 100%;
top: 0;
background: $brand-primary;
border-radius: 0;
-moz-border-radius: 0;
-webkit-border-radius: 0;
box-shadow: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
padding: 40px 0 0 0;
}
.pretix-widget-frame-holder .pretix-widget-frame-close {
right: 20px;
top: 20px;
background: white;
svg path {
fill: $brand-primary;
}
}
}
// https://github.com/Akryum/vue-resize/blob/master/dist/vue-resize.css
.resize-observer[data-v-b329ee4c]{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%;border:none;background-color:transparent;pointer-events:none;display:block;overflow:hidden;opacity:0}.resize-observer[data-v-b329ee4c] object{display:block;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}

View File

@@ -1,9 +1,9 @@
django-debug-toolbar==3.2
django-debug-toolbar==2.1
# Testing requirements
pycodestyle==2.6.*
pyflakes==2.2.*
pycodestyle==2.5.*
pyflakes==2.1.*
pep8-naming
flake8==3.8.*
flake8==3.7.*
codecov
coverage
pytest-cov
@@ -11,12 +11,12 @@ pytest==6.*
pytest-django==4.*
isort
pytest-rerunfailures==9.*
pytest-mock==3.5.*
pytest-mock==2.0.*
responses
potypo
freezegun
# Not really required, just nice to have
pytest-xdist==2.2.*
pytest-xdist==1.31.*
pytest-cache
pytest-sugar

View File

@@ -1,19 +1,19 @@
# Functional requirements
Django==3.1.*
djangorestframework==3.12.*
Django==3.0.*,>=3.0.9
djangorestframework==3.11.*
python-dateutil==2.8.*
isoweek
requests==2.25.0
requests==2.24.0
pytz
django-bootstrap3==14.0.*
django-bootstrap3==12.0.*
django-formset-js-improved==0.5.0.2
django-compressor==2.4.*
django-hierarkey==1.0.*,>=1.0.4
django-filter==2.4.*
django-filter==2.2.*
django-scopes==1.2.*
reportlab>=3.5.65
reportlab>=3.5.18
PyPDF2==1.26.*
Pillow==8.*
Pillow==7.*
django-libsass==0.8
libsass==0.20.*
django-otp==0.7.*,>=0.7.5
@@ -22,9 +22,9 @@ webauthn==0.4.*
django-formtools==2.2
celery==4.4.*
kombu==4.6.*
django-statici18n==2.0.*
django-statici18n==1.9.*
inlinestyler==0.2.*
BeautifulSoup4==4.9.*
BeautifulSoup4==4.8.*
slimit
lxml
static3==0.7.*
@@ -35,38 +35,39 @@ markdown==3.3.*
bleach==3.3.*
sentry-sdk==0.14.*
babel
django-i18nfield==1.9.*
django-hijack==2.3.*
django-i18nfield>=1.7.0
django-hijack>=2.1.10,<2.2.0
jsonschema
openpyxl==3.0.*
django-oauth-toolkit==1.4.*
django-oauth-toolkit==1.2.*
oauthlib==3.1.*
django-jsonfallback>=2.1.2
psycopg2-binary
tqdm==4.*
# Stripe
stripe==2.56.*
stripe==2.42.*
# PayPal
paypalrestsdk==1.13.*
pycparser==2.13 # https://github.com/eliben/pycparser/issues/147
# Banktransfer
chardet==4.0.*
chardet<3.1.0,>=3.0.2
mt-940==3.2
vobject==0.9.*
pycountry
django-countries==7.*
django-countries>=6.0
pyuca # for better sorting of country names in django-countries
defusedcsv>=1.1.0
vat_moss_forked==2020.3.20.0.11.0
django-localflavor==3.0.*
django-redis==4.12.*
redis==3.5.*
django-phonenumber-field==5.0.*
phonenumberslite==8.12.*
django-localflavor>=2.2
django-redis==4.11.*
redis==3.4.*
django-phonenumber-field==4.0.*
phonenumberslite==8.11.*
python-bidi==0.4.* # Support for arabic in reportlab
arabic-reshaper==2.0.15 # Support for Aabic in reportlab
packaging
tlds>=2021031000
tlds>=2020041600
text-unidecode==1.*
protobuf==3.15.*
protobuf==3.13.*
cryptography>=3.4.2
sepaxml==2.4.*,>=2.4.1

View File

@@ -88,19 +88,19 @@ setup(
keywords='tickets web shop ecommerce',
install_requires=[
'Django==3.1.*',
'djangorestframework==3.12.*',
'Django==3.0.*,>=3.0.9',
'djangorestframework==3.11.*',
'python-dateutil==2.8.*',
'isoweek',
'requests==2.25.*',
'requests==2.24.*',
'pytz',
'django-bootstrap3==14.0.*',
'django-bootstrap3==12.0.*',
'django-formset-js-improved==0.5.0.2',
'django-compressor==2.4.*',
'django-hierarkey==1.0.*,>=1.0.4',
'django-filter==2.4.*',
'django-filter==2.2.*',
'django-scopes==1.2.*',
'reportlab>=3.5.65',
'reportlab>=3.5.18',
'Pillow==7.*',
'PyPDF2==1.26.*',
'django-libsass==0.8',
@@ -111,9 +111,9 @@ setup(
'django-formtools==2.2',
'celery==4.4.*',
'kombu==4.6.*',
'django-statici18n==2.0.*',
'django-statici18n==1.9.*',
'inlinestyler==0.2.*',
'BeautifulSoup4==4.9.*',
'BeautifulSoup4==4.8.*',
'slimit',
'lxml',
'static3==0.7.*',
@@ -125,52 +125,53 @@ setup(
'sentry-sdk==0.14.*',
'babel',
'paypalrestsdk==1.13.*',
'django-redis==4.12.*',
'redis==3.5.*',
'stripe==2.56.*',
'chardet==4.0.*',
'pycparser==2.13',
'django-redis==4.11.*',
'redis==3.4.*',
'stripe==2.42.*',
'chardet<3.1.0,>=3.0.2',
'mt-940==3.2',
'django-i18nfield==1.9.*',
'django-i18nfield>=1.7.0',
'django-jsonfallback>=2.1.2',
'psycopg2-binary',
'tqdm==4.*',
'vobject==0.9.*',
'pycountry',
'django-countries==7.*',
'django-countries>=6.0',
'pyuca',
'defusedcsv>=1.1.0',
'vat_moss_forked==2020.3.20.0.11.0',
'django-localflavor==3.0.*',
'django-localflavor>=2.2',
'jsonschema',
'django-hijack==2.3.*',
'django-hijack>=2.1.10,<2.2.0',
'openpyxl==3.0.*',
'django-oauth-toolkit==1.4.*',
'django-oauth-toolkit==1.2.*',
'oauthlib==3.1.*',
'django-phonenumber-field==5.0.*',
'phonenumberslite==8.12.*',
'django-phonenumber-field==4.0.*',
'phonenumberslite==8.11.*',
'python-bidi==0.4.*', # Support for Arabic in reportlab
'arabic-reshaper==2.0.15', # Support for Arabic in reportlab
'packaging',
'tlds>=2021031000',
'tlds>=2020041600',
'text-unidecode==1.*',
'protobuf==3.15.*',
'protobuf==3.13.*',
'cryptography>=3.4.2',
'sepaxml==2.4.*,>=2.4.1',
],
extras_require={
'dev': [
'django-debug-toolbar==3.2',
'pycodestyle==2.6.*',
'pyflakes==2.2.*',
'flake8==3.8.*',
'django-debug-toolbar==2.1',
'pycodestyle==2.5.*',
'pyflakes==2.1.*',
'flake8==3.7.*',
'pep8-naming',
'coveralls',
'coverage',
'pytest==6.*',
'pytest-django==4.*',
'pytest-xdist==2.2.*',
'pytest-xdist==1.31.*',
'isort',
'pytest-mock==3.5.*',
'pytest-mock==2.0.*',
'pytest-rerunfailures==9.*',
'responses',
'potypo',

View File

@@ -19,7 +19,7 @@ from pretix.testutils.mock import mocker_context
@pytest.fixture
def variations(item):
v = list()
v = []
v.append(item.variations.create(value="ChildA1"))
v.append(item.variations.create(value="ChildA2"))
return v

View File

@@ -1055,7 +1055,7 @@ def test_items_with_cart_position_delete(token_client, organizer, event, item, c
@pytest.fixture
def variations(item):
v = list()
v = []
v.append(item.variations.create(value="ChildA1"))
v.append(item.variations.create(value="ChildA2"))
return v
@@ -1063,7 +1063,7 @@ def variations(item):
@pytest.fixture
def variations2(item2):
v = list()
v = []
v.append(item2.variations.create(value="ChildB1"))
v.append(item2.variations.create(value="ChildB2"))
return v

View File

@@ -15,7 +15,7 @@ from pretix.base.models.orders import OrderFee
@pytest.fixture
def variations(item):
v = list()
v = []
v.append(item.variations.create(value="ChildA1", default_price='12.00'))
v.append(item.variations.create(value="ChildA2", default_price='13.00'))
return v
@@ -23,7 +23,7 @@ def variations(item):
@pytest.fixture
def variations2(item2):
v = list()
v = []
v.append(item2.variations.create(value="ChildB1", default_price='12.00'))
v.append(item2.variations.create(value="ChildB2", default_price='13.00'))
return v