Compare commits

..

1 Commits

Author SHA1 Message Date
Phin Wolkwitz
8164f469d3 Prefetch program times, add test for query count 2026-01-19 18:11:12 +01:00
45 changed files with 2072 additions and 1996 deletions

View File

@@ -65,7 +65,7 @@ dependencies = [
"kombu==5.6.*",
"libsass==0.23.*",
"lxml",
"markdown==3.10.1", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
"markdown==3.10", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*",
"oauthlib==3.3.*",
@@ -80,7 +80,7 @@ dependencies = [
"protobuf==6.33.*",
"psycopg2-binary",
"pycountry",
"pycparser==3.0",
"pycparser==2.23",
"pycryptodome==3.23.*",
"pypdf==6.5.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
@@ -92,7 +92,7 @@ dependencies = [
"redis==7.1.*",
"reportlab==4.4.*",
"requests==2.32.*",
"sentry-sdk==2.50.*",
"sentry-sdk==2.49.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",

View File

@@ -806,7 +806,6 @@ class EventSettingsSerializer(SettingsSerializer):
'invoice_reissue_after_modify',
'invoice_include_free',
'invoice_generate',
'invoice_generate_only_business',
'invoice_period',
'invoice_numbers_consecutive',
'invoice_numbers_prefix',

View File

@@ -704,16 +704,6 @@ class CheckinListOrderPositionSerializer(OrderPositionSerializer):
if 'answers.question' in self.context['expand']:
self.fields['answers'].child.fields['question'] = QuestionSerializer(read_only=True)
if 'addons' in self.context['expand']:
# Experimental feature, undocumented on purpose for now in case we need to remove it again
# for performance reasons
subl = CheckinListOrderPositionSerializer(read_only=True, many=True, context={
**self.context,
'expand': [v for v in self.context['expand'] if v != 'addons'],
'pdf_data': False,
})
self.fields['addons'] = subl
class OrderPaymentTypeField(serializers.Field):
# TODO: Remove after pretix 2.2

View File

@@ -381,21 +381,15 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
qs = qs.filter(reduce(operator.or_, lists_qs))
prefetch_related = [
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
]
select_related = [
'item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat'
]
if pdf_data:
qs = qs.prefetch_related(
# Don't add to list, we don't want to propagate to addons
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')),
Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related(
Prefetch(
'event',
@@ -410,39 +404,32 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
)
)
))
).select_related(
'item', 'variation', 'item__category', 'addon_to', 'order', 'order__invoice_address', 'seat'
)
else:
qs = qs.prefetch_related(
Prefetch(
lookup='checkins',
queryset=Checkin.objects.filter(list_id__in=[cl.pk for cl in checkinlists]).select_related('device')
),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question',
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order', 'seat')
if expand and 'subevent' in expand:
prefetch_related += [
qs = qs.prefetch_related(
'subevent', 'subevent__event', 'subevent__subeventitem_set', 'subevent__subeventitemvariation_set',
'subevent__seat_category_mappings', 'subevent__meta_values'
]
)
if expand and 'item' in expand:
prefetch_related += [
'item', 'item__addons', 'item__bundles', 'item__meta_values',
'item__variations',
]
select_related.append('item__tax_rule')
qs = qs.prefetch_related('item', 'item__addons', 'item__bundles', 'item__meta_values',
'item__variations').select_related('item__tax_rule')
if expand and 'variation' in expand:
prefetch_related += [
'variation', 'variation__meta_values',
]
if expand and 'addons' in expand:
prefetch_related += [
Prefetch('addons', OrderPosition.objects.prefetch_related(*prefetch_related).select_related(*select_related)),
]
else:
prefetch_related += [
Prefetch('addons', OrderPosition.objects.select_related('item', 'variation'))
]
if pdf_data:
select_related.remove("order") # Don't need it twice on this queryset
qs = qs.prefetch_related(*prefetch_related).select_related(*select_related)
qs = qs.prefetch_related('variation', 'variation__meta_values')
return qs
@@ -979,7 +966,6 @@ class CheckinRPCSearchView(ListAPIView):
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['expand'] = self.request.query_params.getlist('expand')
ctx['organizer'] = self.request.organizer
ctx['pdf_data'] = False
return ctx

View File

@@ -2031,7 +2031,7 @@ class InvoiceViewSet(viewsets.ReadOnlyModelViewSet):
else:
order = Order.objects.select_for_update(of=OF_SELF).get(pk=inv.order_id)
c = generate_cancellation(inv)
if invoice_qualified(order):
if inv.order.status != Order.STATUS_CANCELED:
inv = generate_invoice(order)
else:
inv = c

View File

@@ -364,7 +364,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state_for_address,
order.invoice_address.state,
order.invoice_address.custom_field,
order.invoice_address.vat_id,
]
@@ -515,7 +515,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state_for_address,
order.invoice_address.state,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:
@@ -617,7 +617,6 @@ class OrderListExporter(MultiSheetListExporter):
_('Country'),
pgettext('address', 'State'),
_('Voucher'),
_('Voucher budget usage'),
_('Pseudonymization ID'),
_('Ticket secret'),
_('Seat ID'),
@@ -733,9 +732,8 @@ class OrderListExporter(MultiSheetListExporter):
op.zipcode or '',
op.city or '',
op.country if op.country else '',
op.state_for_address or '',
op.state or '',
op.voucher.code if op.voucher else '',
op.voucher_budget_use if op.voucher_budget_use else '',
op.pseudonymization_id,
op.secret,
]
@@ -799,7 +797,7 @@ class OrderListExporter(MultiSheetListExporter):
order.invoice_address.city,
order.invoice_address.country if order.invoice_address.country else
order.invoice_address.country_old,
order.invoice_address.state_for_address,
order.invoice_address.state,
order.invoice_address.vat_id,
]
except InvoiceAddress.DoesNotExist:

View File

@@ -141,7 +141,7 @@ def get_babel_locale():
for locale in try_locales:
if localedata.exists(locale):
return localedata.normalize_locale(locale)
return locale
return "en"

View File

@@ -349,7 +349,7 @@ class AttendeeProfile(models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return _(sd.name)
return sd.name
return self.state
@property

View File

@@ -594,11 +594,10 @@ class Item(LoggedModel):
on_delete=models.SET_NULL,
verbose_name=_("Only show after sellout of"),
help_text=_("If you select a product here, this product will only be shown when that product is "
"no longer available. This will happen either because the other product has sold out or because "
"the time is outside of the sales window for the other product. If combined with the option "
"to hide sold-out products, this allows you to swap out products for more expensive ones once "
"the cheaper option is sold out. There might be a short period in which both products are visible "
"while all tickets of the referenced product are reserved, but not yet sold.")
"sold out. If combined with the option to hide sold-out products, this allows you to "
"swap out products for more expensive ones once the cheaper option is sold out. There might "
"be a short period in which both products are visible while all tickets of the referenced "
"product are reserved, but not yet sold.")
)
hidden_if_item_available_mode = models.CharField(
choices=UNAVAIL_MODES,

View File

@@ -1675,7 +1675,7 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return _(sd.name)
return sd.name
return self.state
@property
@@ -3480,7 +3480,7 @@ class InvoiceAddress(models.Model):
def state_name(self):
sd = pycountry.subdivisions.get(code='{}-{}'.format(self.country, self.state))
if sd:
return _(sd.name)
return sd.name
return self.state
@property

View File

@@ -159,7 +159,6 @@ class WaitingListEntry(LoggedModel):
if availability[1] is None or availability[1] < 1:
raise WaitingListException(_('This product is currently not available.'))
event = self.event
ev = self.subevent or self.event
if ev.seat_category_mappings.filter(product=self.item).exists():
# Generally, we advertise the waiting list to be based on quotas only. This makes it dangerous
@@ -192,7 +191,6 @@ class WaitingListEntry(LoggedModel):
with transaction.atomic():
locked_wle = WaitingListEntry.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
locked_wle.event = event
if locked_wle.voucher:
raise WaitingListException(_('A voucher has already been sent to this person.'))
e = locked_wle.email
@@ -229,7 +227,6 @@ class WaitingListEntry(LoggedModel):
locked_wle.save()
self.refresh_from_db()
self.event = event
with language(self.locale, self.event.settings.region):
self.send_mail(

View File

@@ -521,20 +521,9 @@ def invoice_pdf_task(invoice: int):
def invoice_qualified(order: Order):
if order.total == Decimal('0.00'):
if order.total == Decimal('0.00') or order.require_approval or \
order.sales_channel.identifier not in order.event.settings.get('invoice_generate_sales_channels'):
return False
if order.require_approval:
return False
if order.sales_channel.identifier not in order.event.settings.invoice_generate_sales_channels:
return False
if order.status in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
return False
if order.event.settings.invoice_generate_only_business:
try:
ia = order.invoice_address
return ia.is_business
except InvoiceAddress.DoesNotExist:
return False
return True

View File

@@ -112,8 +112,7 @@ def dictsum(*dicts) -> dict:
def order_overview(
event: Event, subevent: SubEvent=None, date_filter='', date_from=None, date_until=None, fees=False,
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None,
skip_empty_lines=False,
admission_only=False, base_qs=None, base_fees_qs=None, subevent_date_from=None, subevent_date_until=None
) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
items = event.items.all().select_related(
'category', # for re-grouping
@@ -206,21 +205,13 @@ def order_overview(
for l in states.keys():
var.num[l] = num[l].get((item.id, variid), (0, 0, 0))
var.num['total'] = num['total'].get((item.id, variid), (0, 0, 0))
var._skip = all(v[0] == 0 for v in var.num.values())
for l in states.keys():
item.num[l] = tuplesum(var.num[l] for var in item.all_variations)
item.num['total'] = tuplesum(var.num['total'] for var in item.all_variations)
if skip_empty_lines:
item.all_variations = [v for v in item.all_variations if not v._skip]
item._skip = not item.all_variations
else:
for l in states.keys():
item.num[l] = num[l].get((item.id, None), (0, 0, 0))
item.num['total'] = num['total'].get((item.id, None), (0, 0, 0))
item._skip = all(v[0] == 0 for v in item.num.values())
if skip_empty_lines:
items = [i for i in items if not i._skip]
nonecat = ItemCategory(name=_('Uncategorized'))
# Regroup those by category

View File

@@ -1225,15 +1225,6 @@ DEFAULTS = {
'default': json.dumps(['web']),
'type': list
},
'invoice_generate_only_business': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Only issue invoices to business customers"),
)
},
'invoice_address_from': {
'default': '',
'type': str,

View File

@@ -82,7 +82,7 @@ def _info(cc):
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
return {
'data': [
{'name': gettext(s.name), 'code': s.code[3:]}
{'name': s.name, 'code': s.code[3:]}
for s in sorted(statelist, key=lambda s: s.name)
],
**info,

View File

@@ -939,7 +939,6 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
'invoice_show_payments',
'invoice_reissue_after_modify',
'invoice_generate',
'invoice_generate_only_business',
'invoice_period',
'invoice_attendee_name',
'invoice_event_location',

View File

@@ -1315,10 +1315,10 @@ class QuestionAnswerFilterForm(forms.Form):
if date_range is not None:
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone)
if d_start:
opqs = opqs.filter(subevent__date_from__gte=d_start)
if d_end:
opqs = opqs.filter(subevent__date_from__lt=d_end)
opqs = opqs.filter(
subevent__date_from__gte=d_start,
subevent__date_from__lt=d_end
)
s = fdata.get("status", Order.STATUS_PENDING + Order.STATUS_PAID)
if s != "":

View File

@@ -12,7 +12,6 @@
<legend>{% trans "Invoice generation" %}</legend>
{% bootstrap_field form.invoice_generate layout="control" %}
{% bootstrap_field form.invoice_generate_sales_channels layout="control" %}
{% bootstrap_field form.invoice_generate_only_business layout="control" %}
{% bootstrap_field form.invoice_email_attachment layout="control" %}
{% bootstrap_field form.invoice_email_organizer layout="control" %}
{% bootstrap_field form.invoice_language layout="control" %}

View File

@@ -353,7 +353,7 @@
data-toggle="tooltip"
title="{% trans 'Generate a cancellation document for this invoice and create a new invoice with a new invoice number.' %}"
{% endif %}>
{% if order.status == "c" or not invoice_qualified %}
{% if order.status == "c" %}
{% trans "Generate cancellation" %}
{% else %}
{% trans "Cancel and reissue" %}

View File

@@ -22,7 +22,7 @@
{{ s.owner.fullname|default:s.owner.email }}
</span>
</div>
<div class="col-lg-4 col-md-5 col-xs-12">
<div class="col-lg-5 col-md-6 col-xs-12">
{% if s.schedule_next_run %}
<span class="fa fa-clock-o fa-fw"></span>
{% trans "Next run:" %}
@@ -53,7 +53,7 @@
{{ s.mail_subject }}
</span>
</div>
<div class="col-lg-3 col-md-3 col-xs-12 text-right">
<div class="col-lg-2 col-md-2 col-xs-12 text-right">
<form action="{% url "control:event.orders.export.do" event=request.event.slug organizer=request.organizer.slug %}"
method="post" class="form-horizontal" data-asynctask data-asynctask-download
data-asynctask-long>
@@ -73,9 +73,6 @@
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
<span class="fa fa-edit"></span>
</a>
<a href="?identifier={{ s.export_identifier }}&scheduled_copy_from={{ s.pk }}" class="btn btn-default" title="{% trans "Copy" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
{% endif %}
<a href="{% url "control:event.orders.export.scheduled.delete" event=request.event.slug organizer=request.event.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
<span class="fa fa-trash"></span>

View File

@@ -42,11 +42,7 @@
<div class="form-group submit-group">
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
class="btn btn-primary btn-save" data-no-asynctask>
{% if scheduled_copy_from %}
{% trans "Save copy" %}
{% else %}
{% trans "Save" %}
{% endif %}
{% trans "Save" %}
</button>
</div>
{% else %}

View File

@@ -22,7 +22,7 @@
{{ s.owner.fullname|default:s.owner.email }}
</span>
</div>
<div class="col-lg-4 col-md-5 col-xs-12">
<div class="col-lg-5 col-md-6 col-xs-12">
{% if s.schedule_next_run %}
<span class="fa fa-clock-o fa-fw"></span>
{% trans "Next run:" %}
@@ -53,7 +53,7 @@
{{ s.mail_subject }}
</span>
</div>
<div class="col-lg-3 col-md-3 col-xs-12 text-right">
<div class="col-lg-2 col-md-2 col-xs-12 text-right">
<form action="{% url "control:organizer.export.do" organizer=request.organizer.slug %}"
method="post" class="form-horizontal" data-asynctask data-asynctask-download
data-asynctask-long>
@@ -73,9 +73,6 @@
<a href="?identifier={{ s.export_identifier }}&scheduled={{ s.pk }}" class="btn btn-default" title="{% trans "Edit" %}" data-toggle="tooltip">
<span class="fa fa-edit"></span>
</a>
<a href="?identifier={{ s.export_identifier }}&scheduled_copy_from={{ s.pk }}" class="btn btn-default" title="{% trans "Copy" %}" data-toggle="tooltip">
<span class="fa fa-copy"></span>
</a>
{% endif %}
<a href="{% url "control:organizer.export.scheduled.delete" organizer=request.organizer.slug pk=s.pk %}" class="btn btn-danger" title="{% trans "Delete" %}" data-toggle="tooltip">
<span class="fa fa-trash"></span>

View File

@@ -43,11 +43,7 @@
<div class="form-group submit-group">
<button formaction="{{ request.get_full_path }}" name="schedule" value="save" type="submit"
class="btn btn-primary btn-save" data-no-asynctask>
{% if scheduled_copy_from %}
{% trans "Save copy" %}
{% else %}
{% trans "Save" %}
{% endif %}
{% trans "Save" %}
</button>
</div>
{% else %}

View File

@@ -363,7 +363,7 @@ class Forgot(TemplateView):
else:
messages.info(request, _('If the address is registered to valid account, then we have sent you an email containing further instructions.'))
return redirect('control:auth.forgot')
return redirect('control:auth.forgot')
else:
return self.get(request, *args, **kwargs)

View File

@@ -33,7 +33,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import copy
import json
import logging
import mimetypes
@@ -509,10 +508,9 @@ class OrderView(EventPermissionRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['invoice_qualified'] = invoice_qualified(self.order)
ctx['can_generate_invoice'] = ctx['invoice_qualified'] and (
ctx['can_generate_invoice'] = invoice_qualified(self.order) and (
self.request.event.settings.invoice_generate in ('admin', 'user', 'paid', 'user_paid', 'True')
) and (
) and self.order.status in (Order.STATUS_PAID, Order.STATUS_PENDING) and (
not self.order.invoices.exists()
or self.order.invoices.filter(is_cancellation=True).count() >= self.order.invoices.filter(is_cancellation=False).count()
)
@@ -1743,15 +1741,14 @@ class OrderInvoiceReissue(OrderView):
messages.error(self.request, _('The invoice has been cleaned of personal data.'))
else:
c = generate_cancellation(inv)
if invoice_qualified(order):
if order.status not in (Order.STATUS_CANCELED, Order.STATUS_EXPIRED):
inv = generate_invoice(order)
messages.success(self.request, _('The invoice has been reissued.'))
else:
inv = c
messages.success(self.request, _('The invoice has been canceled.'))
order.log_action('pretix.event.order.invoice.reissued', user=self.request.user, data={
'invoice': inv.pk
})
messages.success(self.request, _('The invoice has been reissued.'))
return redirect(self.get_order_url())
def get(self, *args, **kwargs): # NOQA
@@ -2680,8 +2677,8 @@ class ExportMixin:
if id != ex.identifier:
continue
if self.scheduled or self.scheduled_copy_from:
initial = dict((self.scheduled or self.scheduled_copy_from).export_form_data)
if self.scheduled:
initial = dict(self.scheduled.export_form_data)
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
test_form.fields = ex.export_form_fields
@@ -2724,11 +2721,6 @@ class ExportMixin:
elif "scheduled" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled"))
@cached_property
def scheduled_copy_from(self):
if "scheduled_copy_from" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled_copy_from"))
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['exporters'] = self.exporters
@@ -2798,16 +2790,7 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
@transaction.atomic()
def post(self, request, *args, **kwargs):
if request.POST.get("schedule") == "save":
if not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
return super().get(request, *args, **kwargs)
elif self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
if self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
self.schedule_form.instance.export_identifier = self.exporter.identifier
self.schedule_form.instance.export_form_data = self.exporter.form.cleaned_data
self.schedule_form.instance.schedule_rrule = str(self.rrule_form.to_rrule())
@@ -2846,8 +2829,6 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
def rrule_form(self):
if self.scheduled:
initial = RRuleForm.initial_from_rrule(self.scheduled.schedule_rrule)
elif self.scheduled_copy_from:
initial = RRuleForm.initial_from_rrule(self.scheduled_copy_from.schedule_rrule)
else:
initial = {}
return RRuleForm(
@@ -2858,15 +2839,11 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
@cached_property
def schedule_form(self):
if self.scheduled_copy_from:
instance = copy.copy(self.scheduled_copy_from)
instance.pk = None
else:
instance = self.scheduled or ScheduledEventExport(
event=self.request.event,
owner=self.request.user,
)
if not self.scheduled and not self.scheduled_copy_from:
instance = self.scheduled or ScheduledEventExport(
event=self.request.event,
owner=self.request.user,
)
if not self.scheduled:
initial = {
"mail_subject": gettext("Export: {title}").format(title=self.exporter.verbose_name),
"mail_template": gettext("Hello,\n\nattached to this email, you can find a new scheduled report for {name}.").format(
@@ -2891,11 +2868,18 @@ class ExportView(EventPermissionRequiredMixin, ExportMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if "schedule" in self.request.POST or self.scheduled or self.scheduled_copy_from:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
ctx['scheduled_copy_from'] = self.scheduled_copy_from
if "schedule" in self.request.POST or self.scheduled:
if "schedule" in self.request.POST and not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
else:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
elif not self.exporter:
for s in ctx['scheduled']:
try:

View File

@@ -32,7 +32,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import copy
import json
import logging
import re
@@ -1944,8 +1943,8 @@ class ExportMixin:
for ex in self.exporters:
if id != ex.identifier:
continue
if self.scheduled or self.scheduled_copy_from:
initial = dict((self.scheduled or self.scheduled_copy_from).export_form_data)
if self.scheduled:
initial = dict(self.scheduled.export_form_data)
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
test_form.fields = ex.export_form_fields
@@ -2048,11 +2047,6 @@ class ExportMixin:
elif "scheduled" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled"))
@cached_property
def scheduled_copy_from(self):
if "scheduled_copy_from" in self.request.GET:
return get_object_or_404(self.get_scheduled_queryset(), pk=self.request.GET.get("scheduled_copy_from"))
class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, TemplateView):
known_errortypes = ['ExportError', 'ExportEmptyError']
@@ -2119,16 +2113,7 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
@transaction.atomic()
def post(self, request, *args, **kwargs):
if request.POST.get("schedule") == "save":
if not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
return super().get(request, *args, **kwargs)
elif self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
if self.exporter.form.is_valid() and self.rrule_form.is_valid() and self.schedule_form.is_valid():
self.schedule_form.instance.export_identifier = self.exporter.identifier
self.schedule_form.instance.export_form_data = self.exporter.form.cleaned_data
self.schedule_form.instance.schedule_rrule = str(self.rrule_form.to_rrule())
@@ -2166,8 +2151,6 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
def rrule_form(self):
if self.scheduled:
initial = RRuleForm.initial_from_rrule(self.scheduled.schedule_rrule)
elif self.scheduled_copy_from:
initial = RRuleForm.initial_from_rrule(self.scheduled_copy_from.schedule_rrule)
else:
initial = {}
return RRuleForm(
@@ -2179,15 +2162,11 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
@cached_property
def schedule_form(self):
if self.scheduled_copy_from:
instance = copy.copy(self.scheduled_copy_from)
instance.pk = None
else:
instance = self.scheduled or ScheduledOrganizerExport(
organizer=self.request.organizer,
owner=self.request.user,
timezone=str(get_current_timezone()),
)
instance = self.scheduled or ScheduledOrganizerExport(
organizer=self.request.organizer,
owner=self.request.user,
timezone=str(get_current_timezone()),
)
if not self.scheduled:
initial = {
"mail_subject": gettext("Export: {title}").format(title=self.exporter.verbose_name),
@@ -2220,10 +2199,18 @@ class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if "schedule" in self.request.POST or self.scheduled or self.scheduled_copy_from:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
ctx['scheduled_copy_from'] = self.scheduled_copy_from
if "schedule" in self.request.POST or self.scheduled:
if "schedule" in self.request.POST and not self.has_permission():
messages.error(
self.request,
_(
"Your user account does not have sufficient permission to run this report, therefore "
"you cannot schedule it."
)
)
else:
ctx['schedule_form'] = self.schedule_form
ctx['rrule_form'] = self.rrule_form
elif not self.exporter:
for s in ctx['scheduled']:
try:

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2026-01-21 21:00+0000\n"
"PO-Revision-Date: 2026-01-11 22:00+0000\n"
"Last-Translator: Jiří Pastrňák <jiri@pastrnak.email>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
"\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 5.15.2\n"
"X-Generator: Weblate 5.15.1\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -1623,7 +1623,7 @@ msgstr "Čas konce události"
#: pretix/base/models/event.py:1536 pretix/control/forms/subevents.py:494
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:274
msgid "Admission time"
msgstr "Vstup"
msgstr "Vstupní čas"
#: pretix/base/exporters/events.py:65 pretix/base/models/event.py:634
#: pretix/base/models/event.py:1545 pretix/control/forms/subevents.py:93
@@ -14592,7 +14592,7 @@ msgstr "Zaplaceno do"
#: pretix/plugins/reports/exporters.py:396
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:23
msgid "Paid"
msgstr "Zaplaceno"
msgstr "Placené"
#: pretix/control/forms/filter.py:1304
msgctxt "subevent"
@@ -35218,8 +35218,10 @@ msgid "Save selection"
msgstr "Uložit volbu"
#: pretix/presale/templates/pretixpresale/fragment_modals.html:145
#, fuzzy
#| msgid "You did not select any products."
msgid "You didn't select any ticket."
msgstr "Nevybrali jste žádné vstupenky."
msgstr "Nevybrali jste žádné produkty."
#: pretix/presale/templates/pretixpresale/fragment_modals.html:146
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2026-01-23 15:00+0000\n"
"Last-Translator: Vajda Tamás <vajda.tamas@szwg.hu>\n"
"PO-Revision-Date: 2025-05-17 18:00+0000\n"
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix/"
"hu/>\n"
"Language: hu\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.2\n"
"X-Generator: Weblate 5.11.4\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -37,11 +37,11 @@ msgstr "arab"
#: pretix/_base_settings.py:91
msgid "Basque"
msgstr "Baszk"
msgstr ""
#: pretix/_base_settings.py:92
msgid "Catalan"
msgstr "Katalán"
msgstr ""
#: pretix/_base_settings.py:93
msgid "Chinese (simplified)"
@@ -57,7 +57,7 @@ msgstr "cseh"
#: pretix/_base_settings.py:96
msgid "Croatian"
msgstr "Horvát"
msgstr ""
#: pretix/_base_settings.py:97
msgid "Danish"
@@ -89,7 +89,7 @@ msgstr "görög"
#: pretix/_base_settings.py:104
msgid "Hebrew"
msgstr "Héber"
msgstr ""
#: pretix/_base_settings.py:105
msgid "Indonesian"
@@ -101,7 +101,7 @@ msgstr "olasz"
#: pretix/_base_settings.py:107
msgid "Japanese"
msgstr "Japán"
msgstr ""
#: pretix/_base_settings.py:108
msgid "Latvian"
@@ -133,11 +133,11 @@ msgstr "orosz"
#: pretix/_base_settings.py:115
msgid "Slovak"
msgstr "Szlovák"
msgstr ""
#: pretix/_base_settings.py:116
msgid "Swedish"
msgstr "Svéd"
msgstr ""
#: pretix/_base_settings.py:117
msgid "Spanish"
@@ -145,7 +145,7 @@ msgstr "spanyol"
#: pretix/_base_settings.py:118
msgid "Spanish (Latin America)"
msgstr "Spanyol (Latin-Amerikai változat)"
msgstr ""
#: pretix/_base_settings.py:119
msgid "Turkish"
@@ -305,7 +305,7 @@ msgstr "A fizetés túl későn történt meg az elfogadáshoz."
#: pretix/api/serializers/item.py:242 pretix/base/models/items.py:2321
msgid "The program end must not be before the program start."
msgstr "A program vége időpont nem lehet a kezdő időpont előtt."
msgstr ""
#: pretix/api/serializers/item.py:247 pretix/base/models/items.py:2315
#, fuzzy
@@ -369,12 +369,12 @@ msgstr "Ezt a fajta kérdést nem lehet megmutatni becsekkolás közben."
msgid ""
"A medium with the same identifier and type already exists in your organizer "
"account."
msgstr "Már létező médium (azonos azonosító és típus)."
msgstr ""
#: pretix/api/serializers/order.py:85
#, python-brace-format
msgid "\"{input}\" is not a valid choice."
msgstr "\"{input}\" nem választható."
msgstr ""
#: pretix/api/serializers/order.py:1453 pretix/api/views/cart.py:224
#: pretix/base/services/orders.py:1605
@@ -397,38 +397,38 @@ msgstr "Nem áll elegendő \"{}\" kvóta rendelkezésre a művelet végrehajtás
#: pretix/api/serializers/organizer.py:145
#: pretix/control/forms/organizer.py:925 pretix/presale/forms/customer.py:458
msgid "An account with this email address is already registered."
msgstr "Ezzel az email címmel már létezik regisztráció."
msgstr ""
#: pretix/api/serializers/organizer.py:278
#: pretix/control/forms/organizer.py:761
msgid ""
"A gift card with the same secret already exists in your or an affiliated "
"organizer account."
msgstr "Ezzel a titkos kulccsal már létezik ajándékkártya."
msgstr ""
#: pretix/api/serializers/organizer.py:369
#: pretix/control/views/organizer.py:1042
msgid "pretix account invitation"
msgstr "pretix ügyfél meghívó"
msgstr ""
#: pretix/api/serializers/organizer.py:391
#: pretix/control/views/organizer.py:1141
msgid "This user already has been invited for this team."
msgstr "Ez a felhasználó már meghívásra került ebbe a csoportba."
msgstr ""
#: pretix/api/serializers/organizer.py:407
#: pretix/control/views/organizer.py:1158
msgid "This user already has permissions for this team."
msgstr "A felhasználó már rendelkezik hozzáféréssel."
msgstr ""
#: pretix/api/views/cart.py:209
msgid ""
"The specified voucher has already been used the maximum number of times."
msgstr "A kupon elérte a felhasználhatóságának felső határát."
msgstr ""
#: pretix/api/views/checkin.py:627 pretix/api/views/checkin.py:634
msgid "Medium connected to other event"
msgstr "A médium rendezvényhez csatlakoztatása megtörtént"
msgstr ""
#: pretix/api/views/oauth.py:107 pretix/control/logdisplay.py:764
#, python-brace-format
@@ -436,23 +436,21 @@ msgid ""
"The application \"{application_name}\" has been authorized to access your "
"account."
msgstr ""
"A(z) \"{application_name}\" applikáció mostantól eléréssel rendelkezik a "
"fiókodhoz."
#: pretix/api/views/order.py:609 pretix/control/views/orders.py:1608
#: pretix/presale/views/order.py:736 pretix/presale/views/order.py:816
msgid "You cannot generate an invoice for this order."
msgstr "Ehhez a megrendeléshez nem készíthető számla."
msgstr ""
#: pretix/api/views/order.py:614 pretix/control/views/orders.py:1610
#: pretix/presale/views/order.py:738 pretix/presale/views/order.py:818
msgid "An invoice for this order already exists."
msgstr "Ehhez a megrendeléshez már készült számla."
msgstr ""
#: pretix/api/views/order.py:640 pretix/control/views/orders.py:1788
#: pretix/control/views/users.py:145
msgid "There was an error sending the mail. Please try again later."
msgstr "Az email küldése sikertelen. Kérjük, próbálja meg később."
msgstr ""
#: pretix/api/views/order.py:720 pretix/base/services/cart.py:223
#: pretix/base/services/orders.py:190 pretix/presale/views/order.py:800
@@ -463,22 +461,22 @@ msgstr "\"{}\" termékhez nincs kvóta rendelve."
#: pretix/api/webhooks.py:263 pretix/base/notifications.py:233
msgid "New order placed"
msgstr "Új megrendelés került rögzítésre"
msgstr ""
#: pretix/api/webhooks.py:267 pretix/base/notifications.py:239
msgid "New order requires approval"
msgstr "Az új megrendelés jóváhagyásra vár"
msgstr ""
#: pretix/api/webhooks.py:271 pretix/base/notifications.py:245
msgid "Order marked as paid"
msgstr "A megrendelés fizetett státuszba került"
msgstr ""
#: pretix/api/webhooks.py:275 pretix/base/models/checkin.py:355
#: pretix/base/notifications.py:251
#: pretix/control/templates/pretixcontrol/event/mail.html:114
#: pretix/control/views/orders.py:1569
msgid "Order canceled"
msgstr "A megrendelés lemondásra került"
msgstr ""
#: pretix/api/webhooks.py:279 pretix/base/notifications.py:257
msgid "Order reactivated"
@@ -616,23 +614,29 @@ msgstr ""
#: pretix/api/webhooks.py:401
msgid "Waiting list entry changed"
msgstr "A várólista eleme megváltozott."
msgstr ""
#: pretix/api/webhooks.py:405
#, fuzzy
#| msgid "The selected seat \"{seat}\" is not available."
msgid "Waiting list entry deleted"
msgstr "A várólista eleme törlésre került."
msgstr "A kiválasztott \"{seat}\" ülés nem elérhető."
#: pretix/api/webhooks.py:409
msgid "Waiting list entry received voucher"
msgstr "A várólista eleme kupont kapott"
msgstr ""
#: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Voucher code"
msgid "Voucher added"
msgstr "Kuponkód hozzáadásra került"
msgstr "Kuponkód"
#: pretix/api/webhooks.py:417
#, fuzzy
#| msgid "Voucher code"
msgid "Voucher changed"
msgstr "Kuponkód megváltozott"
msgstr "Kuponkód"
#: pretix/api/webhooks.py:418
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:13+0000\n"
"PO-Revision-Date: 2026-01-23 15:00+0000\n"
"Last-Translator: Vajda Tamás <vajda.tamas@szwg.hu>\n"
"PO-Revision-Date: 2024-11-28 06:00+0000\n"
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
"js/hu/>\n"
"Language: hu\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.2\n"
"X-Generator: Weblate 5.8.3\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -317,7 +317,7 @@ msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
msgid "Order canceled"
msgstr "A megrendelés lemondásra került"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list"

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:13+0000\n"
"PO-Revision-Date: 2026-01-24 01:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"PO-Revision-Date: 2025-06-10 04:00+0000\n"
"Last-Translator: Tim Maurizio Dullaart <Tim.maurizio@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
"nl/>\n"
"Language: nl\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.2\n"
"X-Generator: Weblate 5.11.4\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -63,7 +63,7 @@ msgstr "iDEAL"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
msgid "SEPA Direct Debit"
msgstr "SEPA-incasso"
msgstr "SEPA Automatische Incasso"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
msgid "Bancontact"
@@ -160,7 +160,7 @@ msgstr "Betaalde bestellingen"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
msgid "Total revenue"
msgstr "Totale omzet"
msgstr "Totaal omzet"
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:15
msgid "Contacting Stripe …"
@@ -180,7 +180,7 @@ msgstr "Kies een inchecklijst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
msgid "No active check-in lists found."
msgstr "Geen actieve check-inlijsten gevonden."
msgstr "Geen actieve inchecklijsten gevonden."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
msgid "Switch check-in list"
@@ -245,11 +245,11 @@ msgstr "Goedkeuring in afwachting"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
msgid "Redeemed"
msgstr "Ingewisseld"
msgstr "Gebruikt"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
msgid "Cancel"
msgstr "Annuleren"
msgstr "Annuleer"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
@@ -310,7 +310,7 @@ msgstr "Bestelling geannuleerd"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list"
msgstr "Ticketcode op de lijst is niet eenduidig"
msgstr "Ticket-code op de lijst is niet eenduidig"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
msgid "Order not approved"
@@ -389,14 +389,14 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:162
#: pretix/static/pretixcontrol/js/ui/mail.js:21
msgid "The request took too long. Please try again."
msgstr "De aanvraag duurde te lang. Probeer het opnieuw."
msgstr "De aanvraag duurde te lang, probeer het alstublieft opnieuw."
#: pretix/static/pretixbase/js/asynctask.js:188
#: pretix/static/pretixcontrol/js/ui/mail.js:26
msgid ""
"We currently cannot reach the server. Please try again. Error code: {code}"
msgstr ""
"De server is op dit moment niet bereikbaar. Probeer het alstublieft opnieuw. "
"De server is op dit moment niet bereikbaar, probeer het alstublieft opnieuw. "
"Foutcode: {code}"
#: pretix/static/pretixbase/js/asynctask.js:216
@@ -466,11 +466,11 @@ msgstr "Huidige datum en tijd"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
msgstr "Huidige dag van de week (1 = maandag, 7 = zondag)"
msgstr "Huidige dag van de week (1 = Maandag, 7 = Zondag)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
msgid "Current entry status"
msgstr "Huidige toegangsstatus"
msgstr "Huidige toegangstatus"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
msgid "Number of previous entries"
@@ -530,7 +530,7 @@ msgstr "Toegangstijd evenement"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:187
msgid "custom date and time"
msgstr "aangepaste datum en tijd"
msgstr "Aangepaste datum en tijd"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:188
msgid "custom time"
@@ -550,7 +550,7 @@ msgstr "minuten"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
msgid "Duplicate"
msgstr "Duplicaat"
msgstr "duplicaat"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status"
@@ -564,11 +564,11 @@ msgstr "afwezig"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!"
msgstr "Fout: product niet gevonden!"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!"
msgstr "Fout: variant niet gevonden!"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/editor.js:171
msgid "Check-in QR"
@@ -588,7 +588,7 @@ msgstr "Tekstobject (verouderd)"
#: pretix/static/pretixcontrol/js/ui/editor.js:911
msgid "Text box"
msgstr "Tekstvak"
msgstr "Tekstkader"
#: pretix/static/pretixcontrol/js/ui/editor.js:913
msgid "Barcode area"
@@ -617,7 +617,7 @@ msgstr "Opslaan mislukt."
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
msgid "Error while uploading your PDF file, please try again."
msgstr "Probleem bij het uploaden van het PDF-bestand. Probeer het opnieuw."
msgstr "Probleem bij het uploaden van het PDF-bestand, probeer het opnieuw."
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
msgid "Do you really want to leave the editor without saving your changes?"
@@ -638,7 +638,7 @@ msgstr "Onbekende fout."
#: pretix/static/pretixcontrol/js/ui/main.js:309
msgid "Your color has great contrast and will provide excellent accessibility."
msgstr ""
"Uw kleur heeft een goed contrast en zal zorgen voor een uitstekende "
"Uw kleur heeft een goed contrast, en zal zorgen voor een uitstekende "
"toegankelijkheid."
#: pretix/static/pretixcontrol/js/ui/main.js:313
@@ -646,16 +646,16 @@ msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility "
"requirements."
msgstr ""
"Uw kleur heeft een redelijk contrast en is voldoende voor de minimale "
"toegankelijkheidseisen."
"Uw kleur heeft een redelijk contrast, en is voldoende voor de minimale "
"toegankelijkheids eisen."
#: pretix/static/pretixcontrol/js/ui/main.js:317
msgid ""
"Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted."
msgstr ""
"Uw kleur heeft te weinig contrast met wit. De toegankelijkheid van uw site "
"wordt negatief beïnvloed."
"Uw kleur heeft een te weinig contrast met wit. De toegankelijkheid van jouw "
"site wordt beïnvloed."
#: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463
@@ -676,11 +676,11 @@ msgstr "Alleen geselecteerde"
#: pretix/static/pretixcontrol/js/ui/main.js:839
msgid "Enter page number between 1 and %(max)s."
msgstr "Voer een paginanummer tussen 1 en %(max)s in."
msgstr "voer een pagina nummer tussen 1 en %(max)s in."
#: pretix/static/pretixcontrol/js/ui/main.js:842
msgid "Invalid page number."
msgstr "Ongeldig paginanummer."
msgstr "Ongeldig pagina nummer."
#: pretix/static/pretixcontrol/js/ui/main.js:1000
msgid "Use a different name internally"
@@ -692,7 +692,7 @@ msgstr "Klik om te sluiten"
#: pretix/static/pretixcontrol/js/ui/main.js:1121
msgid "You have unsaved changes!"
msgstr "Sommige wijzigingen zijn nog niet opgeslagen!"
msgstr "U heeft nog niet opgeslagen wijzigingen!"
#: pretix/static/pretixcontrol/js/ui/orderchange.js:25
msgid "Calculating default price…"
@@ -731,7 +731,7 @@ msgstr "Winkelwagen is verlopen"
#: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire."
msgstr "Uw winkelwagen verloopt bijna."
msgstr "Uw winkelwagen staat op het punt om te verlopen."
#: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute."
@@ -754,7 +754,7 @@ msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?"
msgstr "Wilt u de reserveringsperiode vernieuwen?"
msgstr "Wilt u de reservererings periode vernieuwen?"
#: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation"
@@ -893,7 +893,7 @@ msgstr "incl. belasting"
#: pretix/static/pretixpresale/js/widget/widget.js:37
msgctxt "widget"
msgid "plus taxes"
msgstr "plus belasting"
msgstr "excl. belasting"
#: pretix/static/pretixpresale/js/widget/widget.js:38
#, javascript-format
@@ -960,7 +960,9 @@ msgstr "Afrekenen"
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
msgid "The cart could not be created. Please try again later"
msgstr "De winkelwagen kon niet aangemaakt worden. Probeer het later opnieuw."
msgstr ""
"De winkelwagen kon niet gemaakt worden. Probeer het alstublieft later "
"opnieuw."
#: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget"
@@ -969,8 +971,8 @@ msgid ""
"this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr ""
"Uw winkelwagen kon niet worden aangemaakt omdat er op dit moment te veel "
"gebruikers actief zijn in deze ticketwinkel. Klik op \"Doorgaan\" om opnieuw "
"te proberen in een nieuw tabblad."
"gebruikers actief zijn in deze ticketwinkel. Klik op \"Doorgaan\" om dit "
"opnieuw te proberen in een nieuw tabblad."
#: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget"
@@ -983,8 +985,8 @@ msgid ""
"You currently have an active cart for this event. If you select more "
"products, they will be added to your existing cart."
msgstr ""
"U hebt momenteel een actieve winkelwagen voor dit evenement. Als u meer "
"producten selecteert, worden deze toegevoegd aan uw bestaande winkelwagen."
"U heeft momenteel een actieve winkelwagen voor dit evenement. Als u meer "
"producten selecteert worden deze toegevoegd aan uw bestaande winkelwagen."
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgctxt "widget"
@@ -994,12 +996,12 @@ msgstr "Doorgaan met afrekenen"
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgctxt "widget"
msgid "Redeem a voucher"
msgstr "Voucher inwisselen"
msgstr "Verzilver een voucher"
#: pretix/static/pretixpresale/js/widget/widget.js:59
msgctxt "widget"
msgid "Redeem"
msgstr "Inwisselen"
msgstr "Verzilveren"
#: pretix/static/pretixpresale/js/widget/widget.js:60
msgctxt "widget"
@@ -1014,17 +1016,18 @@ msgstr "Sluiten"
#: pretix/static/pretixpresale/js/widget/widget.js:62
msgctxt "widget"
msgid "Close checkout"
msgstr "Afrekenen sluiten"
msgstr "Stoppen met afrekenen"
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr "U kunt deze actie niet annuleren. Wacht tot het laden is voltooid."
msgstr ""
"U kan niet deze actie annuleren. Alstublieft wacht tot het laden is voltooid."
#: pretix/static/pretixpresale/js/widget/widget.js:64
msgctxt "widget"
msgid "Continue"
msgstr "Doorgaan"
msgstr "Ga verder"
#: pretix/static/pretixpresale/js/widget/widget.js:65
msgctxt "widget"
@@ -1083,9 +1086,9 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available "
"again."
msgstr ""
"Sommige of alle ticketcategorieën zijn uitverkocht. Als u wilt, kunt u zich "
"op de wachtlijst zetten. We zullen u informeren wanneer er weer plaatsen "
"beschikbaar zijn."
"Sommige of alle tiketcategorieën zijn op heden uitverkocht. Als je wilt, kun "
"je jezelf toevoegen aan de wachtlijst. We zullen je informeren wanneer er "
"weer plaatsen beschikbaar zijn."
#: pretix/static/pretixpresale/js/widget/widget.js:76
msgctxt "widget"
@@ -1122,31 +1125,31 @@ msgstr "Zo"
#: pretix/static/pretixpresale/js/widget/widget.js:85
msgid "Monday"
msgstr "maandag"
msgstr "Maandag"
#: pretix/static/pretixpresale/js/widget/widget.js:86
msgid "Tuesday"
msgstr "dinsdag"
msgstr "Dinsdag"
#: pretix/static/pretixpresale/js/widget/widget.js:87
msgid "Wednesday"
msgstr "woensdag"
msgstr "Woensdag"
#: pretix/static/pretixpresale/js/widget/widget.js:88
msgid "Thursday"
msgstr "donderdag"
msgstr "Donderdag"
#: pretix/static/pretixpresale/js/widget/widget.js:89
msgid "Friday"
msgstr "vrijdag"
msgstr "Vrijdag"
#: pretix/static/pretixpresale/js/widget/widget.js:90
msgid "Saturday"
msgstr "zaterdag"
msgstr "Zaterdag"
#: pretix/static/pretixpresale/js/widget/widget.js:91
msgid "Sunday"
msgstr "zondag"
msgstr "Zondag"
#: pretix/static/pretixpresale/js/widget/widget.js:94
msgid "January"

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-05 12:12+0000\n"
"PO-Revision-Date: 2026-01-16 22:00+0000\n"
"Last-Translator: Linnea Thelander <linnea@coeo.events>\n"
"PO-Revision-Date: 2026-01-16 09:22+0000\n"
"Last-Translator: Richard Schreiber <schreiber@rami.io>\n"
"Language-Team: Swedish <https://translate.pretix.eu/projects/pretix/pretix/"
"sv/>\n"
"Language: sv\n"
@@ -35839,11 +35839,9 @@ msgid "A payment of %(total)s is still pending for this order."
msgstr "Tack för din bokning på %(total)s."
#: pretix/presale/templates/pretixpresale/event/order.html:97
#, python-format
#, fuzzy, python-format
msgid "Please complete your payment before %(date)s"
msgstr ""
"Om din betalning inte gick igenom, se till att uppdatera din "
"betalningsinformation innan %(date)s via knappen nedan."
msgstr "Tack för din bokning!"
#: pretix/presale/templates/pretixpresale/event/order.html:108
msgid "Re-try payment or choose another payment method"

View File

@@ -337,8 +337,7 @@ class OverviewReport(Report):
date_until=d_end,
subevent_date_from=sd_start,
subevent_date_until=sd_end,
fees=True,
skip_empty_lines=form_data.get("skip_empty_lines")
fees=True
)
def _table_story(self, doc, form_data, net=False):
@@ -479,10 +478,6 @@ class OverviewReport(Report):
'Use the "Accounting report" in the export section instead.'
))
)
f.fields['skip_empty_lines'] = forms.BooleanField(
label=_("Skip empty lines"),
required=False,
)
return f.fields

View File

@@ -38,7 +38,6 @@
<div class="details">
<code>{{ checkResult.position.order }}-{{ checkResult.position.positionid }}</code>
<h4>{{ checkResult.position.attendee_name }}</h4>
<div v-if="checkResultAddons" class="addons">{{ checkResultAddons }}</div>
<span v-if="checkResultSubevent">{{ checkResultSubevent }}<br></span>
<span class="secret">{{ checkResult.position.secret }}</span>
<span v-if="checkResult.position.seat"><br>{{ checkResult.position.seat.name }}</span>
@@ -266,16 +265,6 @@ export default {
const date = moment.utc(this.checkinlist.subevent.date_from).tz(this.$root.timezone).format(this.$root.datetime_format)
return `${name} · ${date}`
},
checkResultAddons() {
if (!this.checkResult) return ''
if (!this.checkResult.position.addons) return ''
return this.checkResult.position.addons.map((addon) => {
if (addon.variation) {
return `+ ${addon.item.internal_name || i18nstring_localize(addon.item.name)} ${i18nstring_localize(addon.variation.value)}`
}
return "+ " + (addon.item.internal_name || i18nstring_localize(addon.item.name));
}).join("\n")
},
checkResultSubevent() {
if (!this.checkResult) return ''
if (!this.checkResult.position.subevent) return ''
@@ -380,7 +369,7 @@ export default {
this.$refs.input.blur()
})
let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question&expand=addons'
let url = this.$root.api.lists + this.checkinlist.id + '/positions/' + encodeURIComponent(id) + '/redeem/?expand=item&expand=subevent&expand=variation&expand=answers.question'
if (untrusted) {
url += '&untrusted_input=true'
}

View File

@@ -92,9 +92,6 @@ a.searchresult, .check-result {
word-break: break-word;
color: $text-muted;
}
.addons {
white-space: pre-line;
}
}
.check-result-status {

View File

@@ -519,7 +519,7 @@
<dialog role="alertdialog" id="cart-extend-confirmation-dialog" class="inline-dialog" aria-labelledby="cart-deadline">
<form method="dialog">
<p>
<button class="btn btn-success" value="OK">
<button class="btn btn-success" autofocus value="OK">
<span role="img" aria-label="{% trans "OK" %}.">
{% icon "check" %}
</span>

View File

@@ -60,7 +60,7 @@
{% endif %}
{% if providers %}
<ul id="customer-account-login-providers" class="list-inline text-center blank-after">
<ul class="list-inline text-center blank-after">
{% for provider in providers %}
<li>
<a href="{% eventurl request.organizer "presale:organizer.customer.login" provider=provider.pk %}?{{ request.META.QUERY_STRING }}"

View File

@@ -311,8 +311,7 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
)
else:
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
time_available = item.hidden_if_item_available.is_available()
item._dependency_available = (q[0] == Quota.AVAILABILITY_OK) and time_available
item._dependency_available = q[0] == Quota.AVAILABILITY_OK
if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN:
item._remove = True
continue

View File

@@ -166,23 +166,6 @@ $(function () {
};
update();
dependencies.on("change", update);
if (dependents.vat_id && dependents.transmission_type && dependents.transmission_peppol_participant_id) {
// In Belgium, the VAT ID is built from "BE" + the company ID. The Peppol ID also needs to be built
// from the company ID with ID scheme 0208. We can save users some knowing and typing by filling this in!
if (!dependents.transmission_peppol_participant_id.val()) {
const fill_peppol_id = function () {
const vatId = dependents.vat_id.val();
if (vatId && vatId.startsWith("BE") && dependents.transmission_type.val() === "peppol" && autofill_peppol_id) {
dependents.transmission_peppol_participant_id.val("0201:" + vatId.substring(2))
}
}
dependents.vat_id.add(dependents.transmission_type).on("change", fill_peppol_id);
dependents.transmission_peppol_participant_id.one("change", () => {
dependents.vat_id.add(dependents.transmission_type).unbind("change", fill_peppol_id)
});
}
}
});
});

View File

@@ -731,11 +731,6 @@ $(function () {
$(countInput).trigger("change");
});
});
$("#customer-account-login-providers a").click(function () {
// Prevent double-submit, see also https://github.com/pretix/pretix/issues/5836
$(this).addClass("disabled");
});
});
function copy_answers(elements, answers) {

View File

@@ -737,19 +737,6 @@ def test_question_expand(token_client, organizer, clist, event, order, question)
assert resp.data["position"]["answers"][0]["question"]["question"]["en"] == "Size"
@pytest.mark.django_db
def test_addons_expand(token_client, organizer, clist, event, order, question, other_item):
with scopes_disabled():
p = order.positions.first()
question[0].save()
p.answers.create(question=question[0], answer="3")
resp = _redeem(token_client, organizer, clist, p.secret, {"answers": {question[0].pk: ""}}, query="?expand=addons&expand=item")
assert resp.status_code == 201
assert resp.data["status"] == "ok"
assert resp.data["position"]["addons"][0]["item"]["id"] == other_item.pk
@pytest.mark.django_db
def test_store_failed(token_client, organizer, clist, event, order):
with scopes_disabled():
@@ -945,12 +932,6 @@ def test_search(token_client, organizer, event, clist, clist_all, item, other_it
assert resp.status_code == 200
assert [p1] == resp.data['results']
with django_assert_max_num_queries(25):
resp = token_client.get(
'/api/v1/organizers/{}/checkinrpc/search/?list={}&search=z3fsn8jyu&expand=item'.format(organizer.slug, clist_all.pk))
assert resp.status_code == 200
assert resp.data['results'][0]['item']['name']
@pytest.mark.django_db
def test_search_no_list(token_client, organizer, event, clist, clist_all, item, other_item, order):

View File

@@ -612,25 +612,6 @@ def test_sales_channels_qualify(env):
assert invoice_qualified(order) is False
@pytest.mark.django_db
def test_business_only(env):
event, order = env
event.settings.set('invoice_generate', 'admin')
event.settings.set('invoice_generate_only_business', True)
order.total = Decimal('42.00')
ia = InvoiceAddress.objects.create(company='Acme Company', street='221B Baker Street', is_business=True,
zipcode='12345', city='London', country_old='England', country='', order=order)
assert invoice_qualified(order) is True
ia.is_business = False
ia.save()
# Order with default Sales Channel (web)
assert invoice_qualified(order) is False
def test_addon_aware_groupby():
def is_addon(item):
is_addon, id, price = item

View File

@@ -59,7 +59,6 @@ def test_get_locale():
("pt-pt", "PT", "pt-pt", "pt-pt", "pt_PT"),
("es", "MX", "es-mx", "es", "es_MX"),
("es-419", "MX", "es-419", "es-419", "es_MX"),
("zh-hans", "US", "zh-hans", "zh-hans", "zh_Hans"),
("zh-hans", "CN", "zh-hans", "zh-hans", "zh_Hans_CN"),
("zh-hant", "TW", "zh-hant", "zh-hant", "zh_Hant_TW"),
],

View File

@@ -46,7 +46,6 @@ from django.core.exceptions import ValidationError
from django.test import TestCase
from django.utils.timezone import now
from django_scopes import scopes_disabled
from freezegun import freeze_time
from tests.base import SoupTest
from tests.testdummy.signals import FoobarSalesChannel
@@ -273,48 +272,6 @@ class ItemDisplayTest(EventTestMixin, SoupTest):
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertNotIn("Early-bird", resp.rendered_content)
def tiered_availability_by_date_and_quota(self, q1_size, q2_size, time_offset, expected_phase):
current_time = now()
with scopes_disabled():
q1 = Quota.objects.create(event=self.event, name='Phase 1', size=q1_size)
item1 = Item.objects.create(
event=self.event,
name='Phase 1',
default_price=0,
available_from=current_time,
available_until=current_time + datetime.timedelta(days=1),
available_from_mode=Item.UNAVAIL_MODE_HIDDEN,
available_until_mode=Item.UNAVAIL_MODE_HIDDEN,
hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN,
)
q1.items.add(item1)
q2 = Quota.objects.create(event=self.event, name='Phase 2', size=q2_size)
item2 = Item.objects.create(
event=self.event,
name='Phase 2',
default_price=0,
available_from=current_time + datetime.timedelta(days=0),
available_until=current_time + datetime.timedelta(days=2),
available_from_mode=Item.UNAVAIL_MODE_HIDDEN,
available_until_mode=Item.UNAVAIL_MODE_HIDDEN,
hidden_if_item_available_mode=Item.UNAVAIL_MODE_HIDDEN,
hidden_if_item_available=item1
)
q2.items.add(item2)
with freeze_time(current_time + time_offset):
resp = self.client.get('/%s/%s/' % (self.orga.slug, self.event.slug))
self.assertIn(expected_phase, resp.rendered_content)
def test_tiered_availability_by_date_and_quota_phase1_available(self):
self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(seconds=1), "Phase 1")
def test_tiered_availability_by_date_and_quota_phase1_sold_out(self):
self.tiered_availability_by_date_and_quota(0, 1, datetime.timedelta(seconds=1), "Phase 2")
def test_tiered_availability_by_date_and_quota_phase1_timed_out(self):
self.tiered_availability_by_date_and_quota(1, 1, datetime.timedelta(days=1, hours=1), "Phase 2")
def test_subevents_inactive_unknown(self):
self.event.has_subevents = True
self.event.save()