Compare commits

..

2 Commits

Author SHA1 Message Date
Richard Schreiber
8a669ab231 add trigger change to fix e.g. country-state-refresh 2025-02-19 15:45:16 +01:00
Richard Schreiber
6a7bc64e11 Fix selector for copying order/invoice answers to first ticket 2025-02-19 15:39:06 +01:00
38 changed files with 4985 additions and 6020 deletions

View File

@@ -39,7 +39,7 @@ dependencies = [
"django-bootstrap3==24.3",
"django-compressor==4.5.1",
"django-countries==7.6.*",
"django-filter==25.1",
"django-filter==24.3",
"django-formset-js-improved==0.5.0.3",
"django-formtools==2.5.1",
"django-hierarkey==1.2.*",
@@ -91,7 +91,7 @@ dependencies = [
"redis==5.2.*",
"reportlab==4.3.*",
"requests==2.31.*",
"sentry-sdk==2.22.*",
"sentry-sdk==2.20.*",
"sepaxml==2.6.*",
"stripe==7.9.*",
"text-unidecode==1.*",

View File

@@ -93,7 +93,6 @@ ALL_LANGUAGES = [
('zh-hans', _('Chinese (simplified)')),
('zh-hant', _('Chinese (traditional)')),
('cs', _('Czech')),
('hr', _('Croatian')),
('da', _('Danish')),
('nl', _('Dutch')),
('nl-informal', _('Dutch (informal)')),

View File

@@ -148,7 +148,7 @@ def oidc_validate_and_complete_config(config):
return config
def oidc_authorize_url(provider, state, redirect_uri, pkce_code_verifier):
def oidc_authorize_url(provider, state, redirect_uri):
endpoint = provider.configuration['provider_config']['authorization_endpoint']
params = {
# https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
@@ -163,14 +163,10 @@ def oidc_authorize_url(provider, state, redirect_uri, pkce_code_verifier):
if "query_parameters" in provider.configuration and provider.configuration["query_parameters"]:
params.update(parse_qsl(provider.configuration["query_parameters"]))
if pkce_code_verifier and "S256" in provider.configuration['provider_config'].get('code_challenge_methods_supported', []):
params["code_challenge"] = base64.urlsafe_b64encode(hashlib.sha256(pkce_code_verifier.encode()).digest()).decode().rstrip("=")
params["code_challenge_method"] = "S256"
return endpoint + '?' + urlencode(params)
def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier):
def oidc_validate_authorization(provider, code, redirect_uri):
endpoint = provider.configuration['provider_config']['token_endpoint']
# Wall of shame and RFC ignorant IDPs
@@ -192,9 +188,6 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
'redirect_uri': redirect_uri,
}
if pkce_code_verifier and "S256" in provider.configuration['provider_config'].get('code_challenge_methods_supported', []):
params["code_verifier"] = pkce_code_verifier
if token_endpoint_auth_method == 'client_secret_post':
params['client_id'] = provider.configuration['client_id']
params['client_secret'] = provider.configuration['client_secret']

View File

@@ -1,28 +0,0 @@
# Generated by Django 4.2.17 on 2025-02-07 16:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0276_item_hidden_if_item_available_mode"),
]
operations = [
migrations.AddField(
model_name="customerssoclient",
name="require_pkce",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="customerssogrant",
name="code_challenge",
field=models.TextField(null=True),
),
migrations.AddField(
model_name="customerssogrant",
name="code_challenge_method",
field=models.CharField(max_length=255, null=True),
),
]

View File

@@ -416,10 +416,6 @@ class CustomerSSOClient(LoggedModel):
authorization_grant_type = models.CharField(
max_length=32, choices=GRANT_TYPES, verbose_name=_("Grant type"), default=GRANT_AUTHORIZATION_CODE,
)
require_pkce = models.BooleanField(
verbose_name=_("Require PKCE extension"),
default=False,
)
redirect_uris = models.TextField(
blank=False,
verbose_name=_("Redirection URIs"),
@@ -485,8 +481,6 @@ class CustomerSSOGrant(models.Model):
expires = models.DateTimeField()
redirect_uri = models.TextField()
scope = models.TextField(blank=True)
code_challenge = models.TextField(blank=True, null=True)
code_challenge_method = models.CharField(max_length=255, blank=True, null=True)
class CustomerSSOAccessToken(models.Model):

View File

@@ -37,7 +37,7 @@ import logging
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import connections, models
from django.db import models
from django.utils.functional import cached_property
from pretix.base.logentrytype_registry import log_entry_types, make_link
@@ -165,15 +165,6 @@ class LogEntry(models.Model):
def delete(self, using=None, keep_parents=False):
raise TypeError("Logs cannot be deleted.")
@classmethod
def bulk_create_and_postprocess(cls, objects):
if connections['default'].features.can_return_rows_from_bulk_insert:
cls.objects.bulk_create(objects)
else:
for le in objects:
le.save()
cls.bulk_postprocess(objects)
@classmethod
def bulk_postprocess(cls, objects):
from pretix.api.webhooks import notify_webhooks

View File

@@ -822,10 +822,6 @@ class Renderer:
kwargs = {}
if o.get('nowhitespace', False):
kwargs['barBorder'] = 0
if o.get('color'):
kwargs['barFillColor'] = Color(o['color'][0] / 255, o['color'][1] / 255, o['color'][2] / 255)
qrw = QrCodeWidget(content, barLevel=level, barHeight=reqs, barWidth=reqs, **kwargs)
d = Drawing(reqs, reqs)
d.add(qrw)

View File

@@ -624,9 +624,6 @@ class CartManager:
if p.is_bundled:
continue
if p.custom_price_input and p.custom_price_input != p.listed_price:
continue
if p.listed_price is None:
if p.addon_to_id and is_included_for_free(p.item, p.addon_to):
listed_price = Decimal('0.00')
@@ -1349,10 +1346,8 @@ class CartManager:
op.position.price_after_voucher = op.price_after_voucher
op.position.voucher = op.voucher
if op.position.custom_price_input and op.position.custom_price_input == op.position.listed_price:
op.position.custom_price_input = op.price_after_voucher
# op.position.price will be set in recompute_final_prices_and_taxes
op.position.save(update_fields=['price_after_voucher', 'voucher', 'custom_price_input'])
op.position.save(update_fields=['price_after_voucher', 'voucher'])
vouchers_ok[op.voucher] -= 1
if op.voucher.all_bundles_included or op.voucher.all_addons_included:

View File

@@ -1113,7 +1113,7 @@ class SSOClientForm(I18nModelForm):
class Meta:
model = CustomerSSOClient
fields = ['is_active', 'name', 'client_id', 'client_type', 'authorization_grant_type', 'redirect_uris',
'allowed_scopes', 'require_pkce']
'allowed_scopes']
widgets = {
'authorization_grant_type': forms.RadioSelect,
'client_type': forms.RadioSelect,

View File

@@ -322,17 +322,6 @@
id="toolbox-squaresize">
</div>
</div>
<div class="row control-group squaresize">
<div class="col-sm-12">
<label for="toolbox-col">{% trans "QR color" %}</label><br>
<div class="input-group">
<input type="text" value="#000000" class="input-block-level form-control colorpickerfield"
id="toolbox-qrcolor">
<span class="input-group-addon contrast-icon">
</span>
</div>
</div>
</div>
<div class="row control-group squaresize">
<div class="col-sm-12">
<div class="checkbox">

View File

@@ -46,7 +46,7 @@ from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.files import File
from django.db import transaction
from django.db import connections, transaction
from django.db.models import (
Count, Exists, F, IntegerField, Max, Min, OuterRef, Prefetch,
ProtectedError, Q, Subquery, Sum,
@@ -1159,7 +1159,13 @@ class DeviceBulkUpdateView(DeviceQueryMixin, OrganizerDetailViewMixin, Organizer
obj.log_action('pretix.device.changed', data=data, user=self.request.user, save=False)
)
LogEntry.bulk_create_and_postprocess(log_entries)
if connections['default'].features.can_return_rows_from_bulk_insert:
LogEntry.objects.bulk_create(log_entries, batch_size=200)
LogEntry.bulk_postprocess(log_entries)
else:
for le in log_entries:
le.save()
LogEntry.bulk_postprocess(log_entries)
messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form)

View File

@@ -40,7 +40,7 @@ from dateutil.rrule import rruleset
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import transaction
from django.db import connections, transaction
from django.db.models import Count, F, Prefetch, ProtectedError
from django.db.models.functions import Coalesce, TruncDate, TruncTime
from django.forms import inlineformset_factory
@@ -657,30 +657,24 @@ class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View)
@transaction.atomic
def post(self, request, *args, **kwargs):
if request.POST.get('action') == 'disable':
log_entries = []
for obj in self.get_queryset():
log_entries.append(obj.log_action(
obj.log_action(
'pretix.subevent.changed', user=self.request.user, data={
'active': False
}, save=False
))
}
)
obj.active = False
obj.save(update_fields=['active'])
LogEntry.bulk_create_and_postprocess(log_entries)
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been disabled.'))
elif request.POST.get('action') == 'enable':
log_entries = []
for obj in self.get_queryset():
log_entries.append(obj.log_action(
obj.log_action(
'pretix.subevent.changed', user=self.request.user, data={
'active': True
}, save=False
))
}
)
obj.active = True
obj.save(update_fields=['active'])
LogEntry.bulk_create_and_postprocess(log_entries)
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been enabled.'))
elif request.POST.get('action') == 'delete':
return render(request, 'pretixcontrol/subevents/delete_bulk.html', {
@@ -688,28 +682,22 @@ class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View)
'forbidden': self.get_queryset().filter(orderposition__isnull=False).distinct(),
})
elif request.POST.get('action') == 'delete_confirm':
log_entries = []
to_delete = []
for obj in self.get_queryset():
try:
if not obj.allow_delete():
raise ProtectedError('only deactivate', [obj])
log_entries.append(obj.log_action('pretix.subevent.deleted', user=self.request.user, save=False))
to_delete.append(obj.pk)
CartPosition.objects.filter(addon_to__subevent=obj).delete()
obj.cartposition_set.all().delete()
obj.log_action('pretix.subevent.deleted', user=self.request.user)
obj.delete()
except ProtectedError:
log_entries.append(obj.log_action(
obj.log_action(
'pretix.subevent.changed', user=self.request.user, data={
'active': False
}, save=False,
))
}
)
obj.active = False
obj.save(update_fields=['active'])
if to_delete:
CartPosition.objects.filter(addon_to__subevent_id__in=to_delete).delete()
CartPosition.objects.filter(subevent_id__in=to_delete).delete()
SubEvent.objects.filter(pk__in=to_delete).delete()
LogEntry.bulk_create_and_postprocess(log_entries)
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been deleted or disabled.'))
return redirect(self.get_success_url())
@@ -1021,7 +1009,13 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
f.save()
set_progress(90)
LogEntry.bulk_create_and_postprocess(log_entries)
if connections['default'].features.can_return_rows_from_bulk_insert:
LogEntry.objects.bulk_create(log_entries)
LogEntry.bulk_postprocess(log_entries)
else:
for le in log_entries:
le.save()
LogEntry.bulk_postprocess(log_entries)
self.request.event.cache.clear()
return len(subevents)
@@ -1584,7 +1578,13 @@ class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormVie
self.save_itemvars()
self.save_meta()
LogEntry.bulk_create_and_postprocess(log_entries)
if connections['default'].features.can_return_rows_from_bulk_insert:
LogEntry.objects.bulk_create(log_entries, batch_size=200)
LogEntry.bulk_postprocess(log_entries)
else:
for le in log_entries:
le.save()
LogEntry.bulk_postprocess(log_entries)
self.request.event.cache.clear()
messages.success(self.request, _('Your changes have been saved.'))

View File

@@ -477,7 +477,7 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, AsyncFormView):
log_entries.append(
v.log_action('pretix.voucher.added', data=data, user=self.request.user, save=False)
)
LogEntry.bulk_create_and_postprocess(log_entries)
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.))
@@ -619,26 +619,19 @@ class VoucherBulkAction(EventPermissionRequiredMixin, View):
'forbidden': self.objects.exclude(redeemed=0),
})
elif request.POST.get('action') == 'delete_confirm':
log_entries = []
to_delete = []
for obj in self.objects:
if obj.allow_delete():
log_entries.append(obj.log_action('pretix.voucher.deleted', user=self.request.user, save=False))
to_delete.append(obj.pk)
obj.log_action('pretix.voucher.deleted', user=self.request.user)
CartPosition.objects.filter(addon_to__voucher=obj).delete()
obj.cartposition_set.all().delete()
obj.delete()
else:
log_entries.append(obj.log_action('pretix.voucher.changed', user=self.request.user, data={
obj.log_action('pretix.voucher.changed', user=self.request.user, data={
'max_usages': min(obj.redeemed, obj.max_usages),
'bulk': True
}), save=False)
})
obj.max_usages = min(obj.redeemed, obj.max_usages)
obj.save(update_fields=['max_usages'])
if to_delete:
CartPosition.objects.filter(addon_to__voucher_id__in=to_delete).delete()
CartPosition.objects.filter(voucher_id__in=to_delete).delete()
Voucher.objects.filter(pk__in=to_delete).delete()
LogEntry.bulk_create_and_postprocess(log_entries)
messages.success(request, _('The selected vouchers have been deleted or disabled.'))
return redirect(self.get_success_url())

View File

@@ -49,7 +49,7 @@ from django.utils.translation import gettext_lazy as _, pgettext
from django.views import View
from django.views.generic import ListView
from pretix.base.models import Item, LogEntry, Quota, WaitingListEntry
from pretix.base.models import Item, Quota, WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException
from pretix.base.services.waitinglist import assign_automatically
from pretix.base.views.tasks import AsyncAction
@@ -160,15 +160,10 @@ class WaitingListActionView(EventPermissionRequiredMixin, WaitingListQuerySetMix
'forbidden': self.get_queryset().filter(voucher__isnull=False),
})
elif request.POST.get('action') == 'delete_confirm':
with transaction.atomic():
log_entries = []
to_delete = []
for obj in self.get_queryset(force_filtered=True):
if not obj.voucher_id:
log_entries.append(obj.log_action('pretix.event.orders.waitinglist.deleted', user=self.request.user, save=False))
to_delete.append(obj.pk)
WaitingListEntry.objects.filter(id__in=to_delete).delete()
LogEntry.bulk_create_and_postprocess(log_entries)
obj.log_action('pretix.event.orders.waitinglist.deleted', user=self.request.user)
obj.delete()
messages.success(request, _('The selected entries have been deleted.'))
return self._redirect_back()
@@ -191,7 +186,6 @@ class WaitingListActionView(EventPermissionRequiredMixin, WaitingListQuerySetMix
if 'move_top' in request.POST:
try:
with transaction.atomic():
wle = WaitingListEntry.objects.get(
pk=request.POST.get('move_top'), event=self.request.event,
)
@@ -210,7 +204,6 @@ class WaitingListActionView(EventPermissionRequiredMixin, WaitingListQuerySetMix
if 'move_end' in request.POST:
try:
with transaction.atomic():
wle = WaitingListEntry.objects.get(
pk=request.POST.get('move_end'), event=self.request.event,
)

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-21 19:00+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2024-09-09 06:00+0000\n"
"Last-Translator: Ahmad AlHarthi <to.ahmad@pm.me>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix/ar/"
">\n"
"Language: ar\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 5.10\n"
"X-Generator: Weblate 5.7\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -21685,7 +21685,6 @@ msgid "Items"
msgstr "العناصر"
#: pretix/control/templates/pretixcontrol/items/quota_edit.html:31
#, fuzzy
msgid ""
"Please select the products or product variations this quota should be "
"applied to. If you apply two quotas to the same product, it will only be "
@@ -29510,7 +29509,7 @@ msgid "Export SEPA xml"
msgstr "تصدير SEPA xml"
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/sepa_export.html:13
#, fuzzy, python-format
#, python-format
msgid ""
"You are trying to download a refund export from %(date)s with one order and "
"a total of %(sum)s."
@@ -29518,18 +29517,20 @@ msgid_plural ""
"You are trying to download a refund export from %(date)s with %(cnt)s order "
"and a total of %(sum)s."
msgstr[0] ""
"تحاول تنزيل اصدار استرداد من%(date)s لصفر طلب %(cnt)s وبمبلغ اجمالي %(sum)"
"s.تحاول تنزيل اصدار استرداد من%(date)s لطلب واحدs%(cnt)sوبمبلغ اجمالي %(sum)"
"s.تحاول تنزيل اصدار استرداد من%(date)s لطلبين s%(cnt)sوبمبلغ اجمالي %(sum)"
"s.تحاول تنزيل اصدار استرداد من%(date)s لعدة طلبات s%(cnt)sوبمبلغ اجمالي "
"%(sum)s.تحاول تنزيل اصدار استرداد من%(date)s لطلبات كثيرة s%(cnt)sوبمبلغ "
"اجمالي %(sum)s.تحاول تنزيل اصدار استرداد من%(date)s لطلبات غير محددة %(cnt)"
"sوبمبلغ اجمالي %(sum)s."
"تحاول تنزيل اصدار استرداد من%(date)s لصفر طلب %(cnt)s وبمبلغ اجمالي %(sum)s."
msgstr[1] ""
"تحاول تنزيل اصدار استرداد من%(date)s لطلب واحدs%(cnt)sوبمبلغ اجمالي %(sum)s."
msgstr[2] ""
"تحاول تنزيل اصدار استرداد من%(date)s لطلبين s%(cnt)sوبمبلغ اجمالي %(sum)s."
msgstr[3] ""
"تحاول تنزيل اصدار استرداد من%(date)s لعدة طلبات s%(cnt)sوبمبلغ اجمالي "
"%(sum)s."
msgstr[4] ""
"تحاول تنزيل اصدار استرداد من%(date)s لطلبات كثيرة s%(cnt)sوبمبلغ اجمالي "
"%(sum)s."
msgstr[5] ""
"تحاول تنزيل اصدار استرداد من%(date)s لطلبات غير محددة %(cnt)sوبمبلغ اجمالي "
"%(sum)s."
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/sepa_export.html:23
msgid ""

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: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-19 17:00+0000\n"
"Last-Translator: Petr Čermák <pcermak@live.com>\n"
"PO-Revision-Date: 2023-09-15 06:00+0000\n"
"Last-Translator: Michael <michael.happl@gmx.at>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
"cs/>\n"
"Language: 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.10\n"
"X-Generator: Weblate 5.0.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -314,7 +314,7 @@ msgstr "Kód vstupenky je v seznamu nejednoznačný"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
msgid "Order not approved"
msgstr "Objednávka nebyla potvrzena"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
msgid "Checked-in Tickets"

View File

@@ -8,10 +8,10 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-14 21:00+0000\n"
"Last-Translator: deborahfoell <deborah.foell@om.org>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix/el/>"
"\n"
"PO-Revision-Date: 2024-12-22 00:00+0000\n"
"Last-Translator: Dimitris Tsimpidis <tsimpidisd@gmail.com>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix/el/"
">\n"
"Language: el\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -1443,7 +1443,7 @@ msgstr "Κωδικός παραγγελίας"
#: pretix/control/views/waitinglist.py:307
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/pending.html:134
msgid "Email address"
msgstr "ηλεκτρονική διεύθυνση"
msgstr "Διεύθυνση ηλεκτρονικού ταχυδρομείου"
#: pretix/base/exporters/invoices.py:203 pretix/base/exporters/invoices.py:330
msgid "Invoice type"
@@ -34211,7 +34211,7 @@ msgstr ""
#: pretix/presale/forms/checkout.py:70
msgid "Email address (repeated)"
msgstr "επαλήθευση ηλεκτρονικής διεύθυνσης"
msgstr "Διεύθυνση ηλεκτρονικού ταχυδρομείου (επαναλαμβανόμενη)"
#: pretix/presale/forms/checkout.py:71
msgid ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-21 19:00+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2024-07-30 08:36+0000\n"
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix/"
"gl/>\n"
"Language: gl\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.10\n"
"X-Generator: Weblate 5.6.2\n"
#: pretix/_base_settings.py:87
#, fuzzy
@@ -11272,7 +11272,7 @@ msgid "Your orders for {event}"
msgstr "Os teus pedidos de {event}"
#: pretix/base/settings.py:2192
#, fuzzy, python-brace-format
#, python-brace-format
msgid ""
"Hello,\n"
"\n"
@@ -35837,7 +35837,7 @@ msgid "Print"
msgstr "Pie de imprenta"
#: pretix/presale/templates/pretixpresale/event/order.html:152
#, fuzzy, python-format
#, python-format
msgid ""
"We've issued your refund of %(amount)s as a gift card. On your next purchase "
"with us, you can use the following gift card code during payment:"

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: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-21 19:00+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2024-09-16 13:00+0000\n"
"Last-Translator: Svyatoslav <slava@digitalarthouse.eu>\n"
"Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix/"
"lv/>\n"
"Language: lv\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= "
"19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n"
"X-Generator: Weblate 5.10\n"
"X-Generator: Weblate 5.7\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -31377,14 +31377,12 @@ msgid "Usage:"
msgstr "Lietošana:"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:138
#, fuzzy, python-format
#, python-format
msgid "This ticket has been used once."
msgid_plural "This ticket has been used %(count)s times."
msgstr[0] ""
"Šī biļete jau ir izmantota.Šī biļete jau %(count)s reiz ir izmantota.Šī "
"biļete jau ir izmantota %(count)s reizes."
msgstr[1] ""
msgstr[2] ""
msgstr[0] "Šī biļete jau ir izmantota."
msgstr[1] "Šī biļete jau %(count)s reiz ir izmantota."
msgstr[2] "Šī biļete jau ir izmantota %(count)s reizes."
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:170
msgid "No attendee name provided"
@@ -31476,12 +31474,12 @@ msgid "Current value:"
msgstr "Šī brīža vērtība"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:463
#, fuzzy, python-format
#, python-format
msgid "One product"
msgid_plural "%(num)s products"
msgstr[0] "0 produktu1 produkts%(num)s produkti"
msgstr[1] ""
msgstr[2] ""
msgstr[0] "0 produktu"
msgstr[1] "1 produkts"
msgstr[2] "%(num)s produkti"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:477
#, fuzzy, python-format

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-18 11:00+0000\n"
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
"PO-Revision-Date: 2025-01-31 01:00+0000\n"
"Last-Translator: Lorhan Sohaky <lorhansohaky@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix/pt_BR/>\n"
"Language: pt_BR\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.10\n"
"X-Generator: Weblate 5.9.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -3321,10 +3321,11 @@ msgid "Single price: {net_price} net / {gross_price} gross"
msgstr "Preço único: {net_price} líquido / {gross_price} bruto"
#: pretix/base/invoice.py:668
#, python-brace-format
#, fuzzy, python-brace-format
#| msgid "Original price"
msgctxt "invoice"
msgid "Single price: {price}"
msgstr "Preço simples: {price}"
msgstr "Preço original"
#: pretix/base/invoice.py:686 pretix/base/invoice.py:692
msgctxt "invoice"
@@ -3332,9 +3333,11 @@ msgid "Invoice total"
msgstr "Total da fatura"
#: pretix/base/invoice.py:702
#, fuzzy
#| msgid "Paid orders"
msgctxt "invoice"
msgid "Received payments"
msgstr "Pagamentos recebidos"
msgstr "Ordens pagas"
#: pretix/base/invoice.py:707
msgctxt "invoice"
@@ -3342,14 +3345,18 @@ msgid "Outstanding payments"
msgstr "Pagamentos pendentes"
#: pretix/base/invoice.py:724
#, fuzzy
#| msgid "Gift card"
msgctxt "invoice"
msgid "Paid by gift card"
msgstr "Pago com cartão-presente"
msgstr "Cartão Presente"
#: pretix/base/invoice.py:729
#, fuzzy
#| msgid "Payment date"
msgctxt "invoice"
msgid "Remaining amount"
msgstr "Valor remanescente"
msgstr "Data de pagamento"
#: pretix/base/invoice.py:778
msgctxt "invoice"
@@ -3372,14 +3379,18 @@ msgid "Included taxes"
msgstr "Taxas incluídas"
#: pretix/base/invoice.py:838
#, python-brace-format
#, fuzzy, python-brace-format
#| msgctxt "invoice"
#| msgid ""
#| "Using the conversion rate of 1:{rate} as published by the European "
#| "Central Bank on {date}, this corresponds to:"
msgctxt "invoice"
msgid ""
"Using the conversion rate of 1:{rate} as published by the {authority} on "
"{date}, this corresponds to:"
msgstr ""
"Usando a taxa de conversão de 1: {rate} como publicado pelo {authority} na "
"{date}, isto corresponde a:"
"Usando a taxa de conversão de 1: {rate} como publicado pelo Banco Central "
"Europeu na {date}, isto corresponde á:"
#: pretix/base/invoice.py:853
#, fuzzy, python-brace-format
@@ -4681,10 +4692,10 @@ msgid "Owned by ticket holder"
msgstr "Fatura para"
#: pretix/base/models/giftcards.py:93
#, fuzzy
#| msgid "The slug may only contain letters, numbers, dots and dashes."
msgid "The gift card code may only contain letters, numbers, dots and dashes."
msgstr ""
"O código do cartão-presente pode conter apenas letras, números, pontos e "
"traços."
msgstr "A campo pode conter apenas letras, números, pontos e traços."
#: pretix/base/models/giftcards.py:105
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html:39
@@ -4693,8 +4704,10 @@ msgid "Special terms and conditions"
msgstr "Termos e condições especiais"
#: pretix/base/models/giftcards.py:219 pretix/base/models/giftcards.py:223
#, fuzzy
#| msgid "Category description"
msgid "Manual transaction"
msgstr "Transação manual"
msgstr "Descrição da categoria"
#: pretix/base/models/invoices.py:185
#, python-format
@@ -4739,8 +4752,10 @@ msgstr ""
#: pretix/base/models/items.py:114 pretix/base/models/items.py:159
#: pretix/control/forms/item.py:99
#, fuzzy
#| msgid "Product category"
msgid "Normal category"
msgstr "Categoria normal"
msgstr "Categoria de produtos"
#: pretix/base/models/items.py:115 pretix/control/forms/item.py:112
msgid "Normal + cross-selling category"
@@ -4796,8 +4811,10 @@ msgid "Add-on category"
msgstr "Categoria de produtos"
#: pretix/base/models/items.py:222 pretix/base/models/items.py:278
#, fuzzy
#| msgid "Optional. No products will be sold before this date."
msgid "Disable product for this date"
msgstr "Desabilitar produto para esta data"
msgstr "Opcional. Nenhum produto será vendido antes desta data."
#: pretix/base/models/items.py:226 pretix/base/models/items.py:282
#: pretix/base/models/items.py:564
@@ -4810,16 +4827,23 @@ msgid "This product will not be sold after the given date."
msgstr "Este produto não será vendido após a data indicada."
#: pretix/base/models/items.py:436
#, fuzzy
#| msgid "Event start date"
msgid "Event validity (default)"
msgstr "Validade do evento (padrão)"
msgstr "Data de início do evento"
#: pretix/base/models/items.py:437
#, fuzzy
#| msgctxt "refund_source"
#| msgid "Customer"
msgid "Fixed time frame"
msgstr "Prazo fixo"
msgstr "Cliente"
#: pretix/base/models/items.py:438
#, fuzzy
#| msgid "Gift card"
msgid "Dynamic validity"
msgstr "Validade dinâmica"
msgstr "Cartão Presente"
#: pretix/base/models/items.py:444 pretix/control/forms/item.py:660
#: pretix/control/templates/pretixcontrol/subevents/fragment_unavail_mode_indicator.html:3
@@ -4897,8 +4921,10 @@ msgstr ""
"que são comprados como complemento para outros produtos."
#: pretix/base/models/items.py:512 pretix/base/models/items.py:1175
#, fuzzy
#| msgid "Default price"
msgid "Suggested price"
msgstr "Preço sugerido"
msgstr "Preço padrão"
#: pretix/base/models/items.py:513 pretix/base/models/items.py:1176
msgid ""
@@ -4918,8 +4944,10 @@ msgstr ""
"Quer comprar este produto ou não permitir que uma pessoa entre no seu evento"
#: pretix/base/models/items.py:532
#, fuzzy
#| msgid "Is an admission ticket"
msgid "Is a personalized ticket"
msgstr "É um ingresso personalizado"
msgstr "É um bilhete de admissão"
#: pretix/base/models/items.py:534
#, fuzzy
@@ -5150,8 +5178,10 @@ msgid "Membership duration in months"
msgstr "Duração da assinatura em meses"
#: pretix/base/models/items.py:728
#, fuzzy
#| msgid "Valid until"
msgid "Validity"
msgstr "Validade"
msgstr "Válido até"
#: pretix/base/models/items.py:730
msgid ""
@@ -5175,12 +5205,16 @@ msgstr ""
"alteração, mas manterão a validade atual."
#: pretix/base/models/items.py:738 pretix/control/forms/item.py:728
#, fuzzy
#| msgid "Gift card"
msgid "Start of validity"
msgstr "Início da validade"
msgstr "Cartão Presente"
#: pretix/base/models/items.py:739
#, fuzzy
#| msgid "End of presale"
msgid "End of validity"
msgstr "Fim da validade"
msgstr "Fim a pré venda"
#: pretix/base/models/items.py:742
msgid "Minutes"
@@ -5335,8 +5369,10 @@ msgid "This is shown below the variation name in lists."
msgstr "Isso é mostrado abaixo do nome da variação nas listas."
#: pretix/base/models/items.py:1182
#, fuzzy
#| msgid "New order requires approval"
msgid "Require approval"
msgstr "Requer aprovação"
msgstr "Novo pedido precisa ser aprovado"
#: pretix/base/models/items.py:1184
#, fuzzy
@@ -5363,12 +5399,16 @@ msgid "Membership types"
msgstr "Tipos de assinaturas"
#: pretix/base/models/items.py:1206
#, fuzzy
#| msgid "This product will not be sold before the given date."
msgid "This variation will not be sold before the given date."
msgstr "Esta variação não será vendida antes da data indicada."
msgstr "Este produto não será vendido antes da data indicada."
#: pretix/base/models/items.py:1216
#, fuzzy
#| msgid "This product will not be sold after the given date."
msgid "This variation will not be sold after the given date."
msgstr "Esta variação não será vendida após a data indicada."
msgstr "Este produto não será vendido após a data indicada."
#: pretix/base/models/items.py:1224
msgid "Sell on all sales channels the product is sold on"
@@ -5434,8 +5474,10 @@ msgstr ""
"os add-ons normalmente custem dinheiro individualmente."
#: pretix/base/models/items.py:1476
#, fuzzy
#| msgid "Allow product to be canceled"
msgid "Allow the same product to be selected multiple times"
msgstr "Permitir que o mesmo produto seja selecionado múltiplas vezes"
msgstr "Permitir que o produto seja cancelado"
#: pretix/base/models/items.py:1495
msgid "The add-on's category must belong to the same event as the item."
@@ -11398,8 +11440,6 @@ msgid ""
"By clicking \"Accept all cookies\", you agree to the storing of cookies and "
"use of similar technologies on your device."
msgstr ""
"Ao clicar em \"Aceitar todos os cookies\", você concorda em armazenar "
"cookies e utilizar tecnologias similares em seu dispositivo."
#: pretix/base/settings.py:3311
#, fuzzy
@@ -11421,8 +11461,10 @@ msgid "Secondary dialog text"
msgstr ""
#: pretix/base/settings.py:3332
#, fuzzy
#| msgid "Payment date"
msgid "Privacy settings"
msgstr "Preferências de privacidade"
msgstr "Data de pagamento"
#: pretix/base/settings.py:3337
msgid "Dialog title"
@@ -11430,7 +11472,7 @@ msgstr ""
#: pretix/base/settings.py:3343
msgid "Accept all cookies"
msgstr "Aceitar todos os cookies"
msgstr ""
#: pretix/base/settings.py:3348
#, fuzzy
@@ -11478,12 +11520,12 @@ msgstr ""
#: pretix/base/settings.py:3429
msgctxt "person_name_salutation"
msgid "Ms"
msgstr "Sra"
msgstr ""
#: pretix/base/settings.py:3430
msgctxt "person_name_salutation"
msgid "Mr"
msgstr "Sr"
msgstr ""
#: pretix/base/settings.py:3431
msgctxt "person_name_salutation"
@@ -11517,7 +11559,7 @@ msgstr "Sobrenome"
#: pretix/base/settings.py:3687
msgctxt "person_name_sample"
msgid "John"
msgstr "João"
msgstr ""
#: pretix/base/settings.py:3469 pretix/base/settings.py:3485
#: pretix/base/settings.py:3501 pretix/base/settings.py:3517
@@ -11526,25 +11568,27 @@ msgstr "João"
#: pretix/base/settings.py:3657 pretix/base/settings.py:3688
msgctxt "person_name_sample"
msgid "Doe"
msgstr "Silva"
msgstr ""
#: pretix/base/settings.py:3475 pretix/base/settings.py:3491
#: pretix/base/settings.py:3523 pretix/base/settings.py:3642
#: pretix/base/settings.py:3664
msgctxt "person_name"
msgid "Title"
msgstr "Título"
msgstr ""
#: pretix/base/settings.py:3483 pretix/base/settings.py:3499
#: pretix/base/settings.py:3532 pretix/base/settings.py:3655
#: pretix/base/settings.py:3686
msgctxt "person_name_sample"
msgid "Dr"
msgstr "Dr"
msgstr ""
#: pretix/base/settings.py:3507 pretix/base/settings.py:3524
#, fuzzy
#| msgid "Device name"
msgid "First name"
msgstr "Nome"
msgstr "Nome do dispositivo"
#: pretix/base/settings.py:3508 pretix/base/settings.py:3525
#, fuzzy
@@ -11556,7 +11600,7 @@ msgstr "Nome do dispositivo"
#: pretix/control/forms/organizer.py:651
msgctxt "person_name_sample"
msgid "John Doe"
msgstr "João Silva"
msgstr ""
#: pretix/base/settings.py:3595
#, fuzzy
@@ -11583,7 +11627,7 @@ msgstr "Cancelamento"
#: pretix/base/settings.py:3685
msgctxt "person_name_sample"
msgid "Mr"
msgstr "Sr"
msgstr ""
#: pretix/base/settings.py:3667
msgctxt "person_name"
@@ -18131,7 +18175,7 @@ msgstr "Organizadores"
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:29
msgid "This is the SPF record we found on your domain:"
msgstr "Este é o registro SPF encontrado em seu domínio:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html:33
msgid "To fix this, include the following part before the last word:"
@@ -24249,7 +24293,7 @@ msgstr "Produto"
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html:264
#: pretix/control/templates/pretixcontrol/subevents/detail.html:124
msgid "Add a new quota"
msgstr "Adicionar uma quota nova"
msgstr ""
#: pretix/control/templates/pretixcontrol/subevents/bulk.html:485
#: pretix/control/templates/pretixcontrol/subevents/detail.html:128

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-21 19:00+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2024-09-16 13:00+0000\n"
"Last-Translator: Svyatoslav <slava@digitalarthouse.eu>\n"
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix/"
"ru/>\n"
"Language: ru\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.10\n"
"X-Generator: Weblate 5.7\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -32414,12 +32414,12 @@ msgid "Current value:"
msgstr "Валюта"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:463
#, fuzzy, python-format
#, python-format
msgid "One product"
msgid_plural "%(num)s products"
msgstr[0] "Один продуктДополнительные продуктыМного продуктов"
msgstr[1] ""
msgstr[2] ""
msgstr[0] "Один продукт"
msgstr[1] "Дополнительные продукты"
msgstr[2] "Много продуктов"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:477
#, fuzzy, python-format

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-21 19:00+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2024-09-15 18:00+0000\n"
"Last-Translator: Kristian Feldsam <feldsam@gmail.com>\n"
"Language-Team: Slovak <https://translate.pretix.eu/projects/pretix/pretix/sk/"
">\n"
"Language: sk\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.10\n"
"X-Generator: Weblate 5.7\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -15412,7 +15412,7 @@ msgid "The order has been changed:"
msgstr "Poradie bolo zmenené:"
#: pretix/control/logdisplay.py:99
#, fuzzy, python-brace-format
#, python-brace-format
msgid ""
"Position #{posid}: {old_item} ({old_price}) changed to {new_item} "
"({new_price})."
@@ -15515,7 +15515,7 @@ msgid "A block has been removed for position #{posid}."
msgstr "Blok bol odstránený pre pozíciu #{posid}."
#: pretix/control/logdisplay.py:285
#, fuzzy, python-brace-format
#, python-brace-format
msgid ""
"Position #{posid} ({old_item}, {old_price}) split into new order: {order}"
msgstr ""

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-21 19:00+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2024-05-23 14:03+0000\n"
"Last-Translator: Serhii Horichenko <m@sgg.im>\n"
"Language-Team: Ukrainian <https://translate.pretix.eu/projects/pretix/pretix/"
"uk/>\n"
"Language: uk\n"
@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 5.10\n"
"X-Generator: Weblate 5.5.5\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -32502,26 +32502,26 @@ msgstr ""
"відповідно змініть налаштування свого браузера."
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:17
#, fuzzy, python-format
#, python-format
msgid "You need to choose exactly one option from this category."
msgid_plural "You need to choose %(min_count)s options from this category."
msgstr[0] ""
"Однина\n"
"Вам необхідно обрати одну опцію з цієї категорії.Множина\n"
"Вам обрати %(min_count)s опції з цієї категорії.Множина\n"
"Вам обрати %(min_count)s опцій з цієї категорії."
"Вам необхідно обрати одну опцію з цієї категорії."
msgstr[1] ""
"Множина\n"
"Вам обрати %(min_count)s опції з цієї категорії."
msgstr[2] ""
"Множина\n"
"Вам обрати %(min_count)s опцій з цієї категорії."
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:26
#, fuzzy, python-format
#, python-format
msgid "You can choose %(max_count)s option from this category."
msgid_plural "You can choose up to %(max_count)s options from this category."
msgstr[0] ""
"Ви можете обрати одну опцію з цієї категорії.Ви можете обрати %(max_count)s "
"опції з цієї категорії.Ви можете обрати %(max_count)s опцій з цієї категорії."
msgstr[1] ""
msgstr[2] ""
msgstr[0] "Ви можете обрати одну опцію з цієї категорії."
msgstr[1] "Ви можете обрати %(max_count)s опції з цієї категорії."
msgstr[2] "Ви можете обрати %(max_count)s опцій з цієї категорії."
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:34
#, python-format
@@ -32799,14 +32799,12 @@ msgid "Usage:"
msgstr "Використання:"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:138
#, fuzzy, python-format
#, python-format
msgid "This ticket has been used once."
msgid_plural "This ticket has been used %(count)s times."
msgstr[0] ""
"Цей квиток було використано один раз.Цей квиток було використано кілька "
"разів.Цей квиток було використано %(count)s разів."
msgstr[1] ""
msgstr[2] ""
msgstr[0] "Цей квиток було використано один раз."
msgstr[1] "Цей квиток було використано кілька разів."
msgstr[2] "Цей квиток було використано %(count)s разів."
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:170
msgid "No attendee name provided"
@@ -32893,12 +32891,12 @@ msgid "Current value:"
msgstr "Поточна вартість"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:463
#, fuzzy, python-format
#, python-format
msgid "One product"
msgid_plural "%(num)s products"
msgstr[0] "Один продукт%(num)s продукти%(num)s продуктів"
msgstr[1] ""
msgstr[2] ""
msgstr[0] "Один продукт"
msgstr[1] "%(num)s продукти"
msgstr[2] "%(num)s продуктів"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:477
#, python-format

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-29 13:18+0000\n"
"PO-Revision-Date: 2025-02-21 19:00+0000\n"
"Last-Translator: anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2025-01-31 01:00+0000\n"
"Last-Translator: Chislon <chislon@gmail.com>\n"
"Language-Team: Chinese (Traditional Han script) <https://translate.pretix.eu/"
"projects/pretix/pretix/zh_Hant/>\n"
"Language: zh_Hant\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.10\n"
"X-Generator: Weblate 5.9.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -19302,7 +19302,7 @@ msgstr "是否確實要刪除商品<strong>%(item)s</strong>"
#: pretix/control/templates/pretixcontrol/item/delete.html:22
#: pretix/control/templates/pretixcontrol/items/quota_delete.html:24
#, fuzzy, python-format
#, python-format
msgid "That will cause %(count)s voucher to be unusable."
msgid_plural "That will cause %(count)s voucher to be unusable."
msgstr[0] "這將導致%(count)s的優惠券不可用.這將導致 %(count)s的優惠券不可用."
@@ -24625,7 +24625,7 @@ msgid "Soon"
msgstr "很快地"
#: pretix/control/views/dashboards.py:561
#, fuzzy, python-brace-format
#, python-brace-format
msgid "{num} order"
msgid_plural "{num} orders"
msgstr[0] ""
@@ -25135,7 +25135,7 @@ msgid_plural "Your invoices"
msgstr[0] "你的發票"
#: pretix/control/views/orders.py:537
#, fuzzy, python-brace-format
#, python-brace-format
msgid ""
"Hello,\n"
"\n"
@@ -28600,7 +28600,7 @@ msgid "on {date} at {time}"
msgstr "在{date}在{time}"
#: pretix/plugins/sendmail/models.py:340
#, fuzzy, python-format
#, python-format
msgid "%(count)d day after event end at %(time)s"
msgid_plural "%(count)d days after event end at %(time)s"
msgstr[0] ""
@@ -28620,10 +28620,11 @@ msgid_plural "%(count)d days after event start at %(time)s"
msgstr[0] "%(count)d 事件在%(time)s開始後的一天"
#: pretix/plugins/sendmail/models.py:368
#, fuzzy, python-format
#, python-format
msgid "%(count)d day before event start at %(time)s"
msgid_plural "%(count)d days before event start at %(time)s"
msgstr[0] "%(count)d 事件在%(time)s開始的前一天 , 事件在%(time)s開始前的%(count)d天"
msgstr[0] ""
"%(count)d 事件在%(time)s開始的前一天 , 事件在%(time)s開始前的%(count)d天"
#: pretix/plugins/sendmail/signals.py:101
msgid "Scheduled emails"
@@ -28872,7 +28873,7 @@ msgid ""
msgstr "通過等候名單功能,向當前等待接收優惠券的每個人發送電子郵件。"
#: pretix/plugins/sendmail/views.py:516
#, fuzzy, python-format
#, python-format
msgid "%(number)s waiting list entry"
msgid_plural "%(number)s waiting list entries"
msgstr[0] "%(number)s的等待名單條目%(number)s的等待名單條目"
@@ -30753,11 +30754,12 @@ msgstr[0] ""
"你需要從此類別中選擇一個選項。你需要從此類別中選擇%(min_count)s的選項。"
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:26
#, fuzzy, python-format
#, python-format
msgid "You can choose %(max_count)s option from this category."
msgid_plural "You can choose up to %(max_count)s options from this category."
msgstr[0] "你可以從此類別中選擇%(max_count)s選項。你可以從此類別中選擇%(max_count)s選項"
""
msgstr[0] ""
"你可以從此類別中選擇%(max_count)s選項。你可以從此類別中選擇%(max_count)s選"
"項。"
#: pretix/presale/templates/pretixpresale/event/fragment_addon_choice.html:34
#, python-format

File diff suppressed because one or more lines are too long

View File

@@ -37,13 +37,7 @@ DEFAULT_TICKET_LAYOUT = '''[
"content": "secret",
"text": "",
"text_i18n": {},
"nowhitespace": false,
"color": [
0,
0,
0,
1
]
"nowhitespace": false
},
{
"type": "poweredby",

View File

@@ -386,11 +386,6 @@ def get_or_create_cart_id(request, create=True):
if 'carts' in request.session:
request.session['carts'][current_id] = {}
else:
if 'widget_data' in request.GET:
try:
request.session['carts'][current_id]['widget_data'] = json.loads(request.GET.get('widget_data'))
except ValueError:
pass
return current_id
cart_data = {}

View File

@@ -676,8 +676,6 @@ class SSOLoginView(RedirectBackMixin, View):
popup_origin = None
nonce = get_random_string(32)
pkce_code_verifier = get_random_string(64)
request.session[f'pretix_customerauth_{self.provider.pk}_pkce_code_verifier'] = pkce_code_verifier
request.session[f'pretix_customerauth_{self.provider.pk}_nonce'] = nonce
request.session[f'pretix_customerauth_{self.provider.pk}_popup_origin'] = popup_origin
request.session[f'pretix_customerauth_{self.provider.pk}_cross_domain_requested'] = self.request.GET.get("request_cross_domain_customer_auth") == "true"
@@ -686,7 +684,7 @@ class SSOLoginView(RedirectBackMixin, View):
})
if self.provider.method == "oidc":
return redirect_to_url(oidc_authorize_url(self.provider, f'{nonce}%{next_url}', redirect_uri, pkce_code_verifier))
return redirect_to_url(oidc_authorize_url(self.provider, f'{nonce}%{next_url}', redirect_uri))
else:
raise Http404("Unknown SSO method.")
@@ -720,7 +718,6 @@ class SSOLoginReturnView(RedirectBackMixin, View):
)
return HttpResponseRedirect(redirect_to)
r = super().dispatch(request, *args, **kwargs)
request.session.pop(f'pretix_customerauth_{self.provider.pk}_pkce_code_verifier', None)
request.session.pop(f'pretix_customerauth_{self.provider.pk}_nonce', None)
request.session.pop(f'pretix_customerauth_{self.provider.pk}_popup_origin', None)
request.session.pop(f'pretix_customerauth_{self.provider.pk}_cross_domain_requested', None)
@@ -766,7 +763,6 @@ class SSOLoginReturnView(RedirectBackMixin, View):
self.provider,
request.GET.get('code'),
redirect_uri,
request.session.get(f'pretix_customerauth_{self.provider.pk}_pkce_code_verifier'),
)
except ValidationError as e:
for msg in e:

View File

@@ -72,9 +72,6 @@ We currently do not implement the following optional parts of the spec:
We also implement the Discovery extension (without issuer discovery)
as per https://openid.net/specs/openid-connect-discovery-1_0.html
We also implement the PKCE extension for OAuth:
https://www.rfc-editor.org/rfc/rfc7636
The implementation passed the certification tests against the following profiles, but we did not
acquire formal certification:
@@ -139,22 +136,19 @@ class AuthorizeView(View):
self._construct_redirect_uri(redirect_uri, response_mode, qs)
)
def _require_login(self, request, client, scope, redirect_uri, response_type, response_mode, state, nonce,
code_challenge, code_challenge_method):
def _require_login(self, request, client, scope, redirect_uri, response_type, response_mode, state, nonce):
form = AuthenticationForm(data=request.POST if "login-email" in request.POST else None, request=request,
prefix="login")
if "login-email" in request.POST and form.is_valid():
customer_login(request, form.get_customer())
return self._success(client, scope, redirect_uri, response_type, response_mode, state, nonce,
code_challenge, code_challenge_method, form.get_customer())
return self._success(client, scope, redirect_uri, response_type, response_mode, state, nonce, form.get_customer())
else:
return render(request, 'pretixpresale/organizers/customer_login.html', {
'providers': [],
'form': form,
})
def _success(self, client, scope, redirect_uri, response_type, response_mode, state, nonce, code_challenge,
code_challenge_method, customer):
def _success(self, client, scope, redirect_uri, response_type, response_mode, state, nonce, customer):
response_type = response_type.split(' ')
qs = {}
id_token_kwargs = {}
@@ -168,8 +162,6 @@ class AuthorizeView(View):
expires=now() + timedelta(minutes=10),
auth_time=get_customer_auth_time(self.request),
nonce=nonce,
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
)
qs['code'] = grant.code
id_token_kwargs['with_code'] = grant.code
@@ -217,8 +209,6 @@ class AuthorizeView(View):
prompt = request_data.get("prompt")
response_type = request_data.get("response_type")
scope = request_data.get("scope", "").split(" ")
code_challenge = request_data.get("code_challenge")
code_challenge_method = request_data.get("code_challenge_method")
if not client_id:
return self._final_error("invalid_request", "client_id missing")
@@ -257,16 +247,6 @@ class AuthorizeView(View):
return self._redirect_error("invalid_request", "id_token_hint currently not supported by this server",
redirect_uri, response_mode, state)
if code_challenge and code_challenge_method != "S256":
# "Clients re permitted to use "plain" only if they cannot support "S256" for some technical reason and
# know via out-of-band configuration that the S256 MUST be implemented, plain is not mandatory."
return self._redirect_error("invalid_request", "code_challenge transform algorithm not supported",
redirect_uri, response_mode, state)
if client.require_pkce and not code_challenge:
return self._redirect_error("invalid_request", "code_challenge (PKCE) required",
redirect_uri, response_mode, state)
has_valid_session = bool(request.customer)
if has_valid_session and max_age:
try:
@@ -282,11 +262,9 @@ class AuthorizeView(View):
has_valid_session = False
if has_valid_session:
return self._success(client, scope, redirect_uri, response_type, response_mode, state, nonce, code_challenge,
code_challenge_method, request.customer)
return self._success(client, scope, redirect_uri, response_type, response_mode, state, nonce, request.customer)
else:
return self._require_login(request, client, scope, redirect_uri, response_type, response_mode, state, nonce,
code_challenge, code_challenge_method)
return self._require_login(request, client, scope, redirect_uri, response_type, response_mode, state, nonce)
class TokenView(View):
@@ -384,24 +362,6 @@ class TokenView(View):
"error_description": "Mismatch of redirect_uri"
}, status=400)
if grant.code_challenge:
if not request.POST.get("code_verifier"):
return JsonResponse({
"error": "invalid_grant",
"error_description": "Missing of code_verifier"
}, status=400)
if grant.code_challenge_method == "S256":
expected_challenge = base64.urlsafe_b64encode(hashlib.sha256(request.POST["code_verifier"].encode()).digest()).decode().rstrip("=")
print(grant.code_challenge, expected_challenge)
if expected_challenge != grant.code_challenge:
return JsonResponse({
"error": "invalid_grant",
"error_description": "Mismatch of code_verifier with code_challenge"
}, status=400)
else:
raise ValueError("Unsupported code_challenge_method in database")
with transaction.atomic():
token = self.client.access_tokens.create(
customer=grant.customer,
@@ -543,7 +503,6 @@ class ConfigurationView(View):
'token_endpoint_auth_methods_supported': [
'client_secret_post', 'client_secret_basic'
],
'code_challenge_methods_supported': ['S256'],
'claims_supported': [
'iss',
'aud',

View File

@@ -313,7 +313,6 @@ var editor = {
content: o.content,
});
} else if (o.type === "barcodearea") {
col = (new fabric.Color(o.fill))._source;
d.push({
type: "barcodearea",
page: editor.pdf_page_number,
@@ -324,7 +323,6 @@ var editor = {
text: o.text,
text_i18n: o.text_i18n || {},
nowhitespace: o.nowhitespace || false,
color: col,
});
} else if (o.type === "poweredby") {
d.push({
@@ -351,10 +349,6 @@ var editor = {
o.content = d.content;
o.scaleToHeight(editor._mm2px(d.size));
o.nowhitespace = d.nowhitespace || false;
if (!d.color) {
d.color = [0, 0, 0, 1];
}
o.set('fill', 'rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
if (d.content === "other") {
o.text = d.text
} else if (d.content === "other_i18n") {
@@ -646,8 +640,6 @@ var editor = {
}));
});
} else if (o.type === "barcodearea") {
var col = (new fabric.Color(o.fill))._source;
$("#toolbox-qrcolor").val("#" + ((1 << 24) + (col[0] << 16) + (col[1] << 8) + col[2]).toString(16).slice(1));
$("#toolbox-squaresize").val(editor._px2mm(o.height * o.scaleY).toFixed(2));
$("#toolbox-qrwhitespace").prop("checked", o.nowhitespace || false);
} else if (o.type === "imagearea") {
@@ -750,7 +742,6 @@ var editor = {
o.set('scaleX', 1);
o.set('scaleY', 1);
o.set('top', new_top)
o.set('fill', $("#toolbox-qrcolor").val());
o.nowhitespace = $("#toolbox-qrwhitespace").prop("checked") || false;
$("#toolbox-content-other").toggle($("#toolbox-content").val() === "other");
@@ -1037,7 +1028,7 @@ var editor = {
width: 100,
height: 100,
lockRotation: true,
fill: '#000',
fill: '#666',
content: $(this).attr("data-content"),
text: '',
nowhitespace: true,

View File

@@ -31,16 +31,7 @@ $(function () {
var show_dialog = !storage_val;
var consent_checkboxes = $("#cookie-consent-details input[type=checkbox][name]");
var consent_modal = $("#cookie-consent-modal");
var widget_consent = $("#cookie-consent-from-widget").text();
if (widget_consent) {
widget_consent = JSON.parse(widget_consent);
storage_val = {}
consent_checkboxes.each(function () {
this.checked = storage_val[this.name] = widget_consent.indexOf(this.name) > -1;
})
show_dialog = false;
$("#cookie-consent-reopen").hide();
} else if (storage_val) {
if (storage_val) {
storage_val = JSON.parse(storage_val);
consent_checkboxes.each(function () {
if (typeof storage_val[this.name] === "undefined") {
@@ -52,6 +43,14 @@ $(function () {
})
} else {
storage_val = {}
var consented = $("#cookie-consent-from-widget").text();
if (consented) {
consented = JSON.parse(consented);
consent_checkboxes.each(function () {
this.checked = storage_val[this.name] = consented.indexOf(this.name) > -1;
})
show_dialog = false
}
}
update_consent(storage_val);

View File

@@ -385,23 +385,30 @@ $(function () {
" #id_city, #id_country, #id_state").change(function () {
if (copy_to_first_ticket) {
var $first_ticket_form = $(".questions-form").first().find("[data-addonidx=0]");
$first_ticket_form.find("[id$=" + this.id.substring(3) + "]").val(this.value);
$first_ticket_form.find("[id$=" + this.id.substring(3) + "]").val(this.value).trigger("change");
// when matching by placeholder or label, make sure to always match the tagName of the element
// filter any checkbox/radio inputs as copied input is either select or type=email|text, but could be
// matched when custom answer options have the same label, see https://github.com/pretix/pretix/issues/4860
var selectorPrefix = this.tagName + ":not([type='checkbox'], [type='radio'])";
if (this.placeholder) {
$first_ticket_form.find("[placeholder='" + CSS.escape(this.placeholder) + "']").val(this.value);
$first_ticket_form.find(
selectorPrefix + "[placeholder='" + CSS.escape(this.placeholder) + "']"
).val(this.value).trigger("change");
}
var label = $("label[for=" + this.id +"]").first().contents().filter(function () {
return this.nodeType != Node.ELEMENT_NODE || !this.classList.contains("sr-only");
}).text().trim();
if (label) {
// match to placeholder and label
$first_ticket_form.find("[placeholder='" + CSS.escape(label) + "']").val(this.value);
$first_ticket_form.find(selectorPrefix + "[placeholder='" + CSS.escape(label) + "']").val(this.value).trigger("change");
var v = this.value;
$first_ticket_form.find("label").each(function() {
var text = $(this).first().contents().filter(function () {
return this.nodeType != Node.ELEMENT_NODE || !this.classList.contains("sr-only");
}).text().trim();
if (text == label) {
$("#" + this.getAttribute("for")).val(v);
$(selectorPrefix + "#" + this.getAttribute("for")).val(v).trigger("change");
}
});
}

View File

@@ -80,17 +80,6 @@
"nowhitespace": {
"description": "Whether a barcode should be rendered without margins. Only used for type 'barcodearea'.",
"type": "boolean"
},
"color": {
"description": "QR color as a tuple of three integers in the 0-255 range and one float in the 0-1 range. The last value (alpha) is ignored by the current implementation but might be used in the future.",
"type": "array",
"items": {
"type": "number",
"minimum": 0,
"maximum": 255
},
"minItems": 3,
"maxItems": 4
}
},
"additionalProperties": false

View File

@@ -236,8 +236,7 @@ def provider(organizer):
"response_modes_supported": ["query"],
"grant_types_supported": ["authorization_code"],
"scopes_supported": ["openid", "email", "profile"],
"claims_supported": ["email", "sub"],
"code_challenge_methods_supported": ["plain", "S256"],
"claims_supported": ["email", "sub"]
}
}
)
@@ -245,21 +244,6 @@ def provider(organizer):
@pytest.mark.django_db
def test_authorize_url(provider):
assert (
"https://example.com/authorize?"
"response_type=code&"
"client_id=abc123&"
"scope=openid+email+profile&"
"state=state_val&"
"redirect_uri=https%3A%2F%2Fredirect%3Ffoo%3Dbar&"
"code_challenge=S1ZnvzwMZHrWOO62nENdJ6jhODhf7VfyZFBIXQyrTKo&"
"code_challenge_method=S256"
) == oidc_authorize_url(provider, "state_val", "https://redirect?foo=bar", "pkce_value")
@pytest.mark.django_db
def test_authorize_url_no_pkce(provider):
del provider.configuration["provider_config"]["code_challenge_methods_supported"]
assert (
"https://example.com/authorize?"
"response_type=code&"
@@ -267,7 +251,7 @@ def test_authorize_url_no_pkce(provider):
"scope=openid+email+profile&"
"state=state_val&"
"redirect_uri=https%3A%2F%2Fredirect%3Ffoo%3Dbar"
) == oidc_authorize_url(provider, "state_val", "https://redirect?foo=bar", "pkce_value")
) == oidc_authorize_url(provider, "state_val", "https://redirect?foo=bar")
@pytest.mark.django_db
@@ -280,7 +264,7 @@ def test_validate_authorization_invalid(provider):
status=400,
)
with pytest.raises(ValidationError):
oidc_validate_authorization(provider, "code_received", "https://redirect?foo=bar", "pkce_value")
oidc_validate_authorization(provider, "code_received", "https://redirect?foo=bar")
@pytest.mark.django_db
@@ -297,7 +281,6 @@ def test_validate_authorization_userinfo_invalid(provider):
"grant_type": "authorization_code",
"code": "code_received",
"redirect_uri": "https://redirect?foo=bar",
"code_verifier": "pkce_value",
})
],
)
@@ -313,7 +296,7 @@ def test_validate_authorization_userinfo_invalid(provider):
],
)
with pytest.raises(ValidationError) as e:
oidc_validate_authorization(provider, "code_received", "https://redirect?foo=bar", "pkce_value")
oidc_validate_authorization(provider, "code_received", "https://redirect?foo=bar")
assert 'could not fetch' in str(e.value)
@@ -331,7 +314,6 @@ def test_validate_authorization_valid(provider):
"grant_type": "authorization_code",
"code": "code_received",
"redirect_uri": "https://redirect?foo=bar",
"code_verifier": "pkce_value",
})
],
)
@@ -346,4 +328,4 @@ def test_validate_authorization_valid(provider):
matchers.header_matcher({"Authorization": "Bearer test_access_token"})
],
)
oidc_validate_authorization(provider, "code_received", "https://redirect?foo=bar", "pkce_value")
oidc_validate_authorization(provider, "code_received", "https://redirect?foo=bar")

View File

@@ -1685,7 +1685,7 @@ class CartTest(CartTestMixin, TestCase):
def test_voucher_free_price_lower_bound(self):
with scopes_disabled():
v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event)
self.ticket.free_price = True
self.ticket.free_price = False
self.ticket.save()
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
@@ -1708,70 +1708,6 @@ class CartTest(CartTestMixin, TestCase):
self.assertEqual(objs[0].listed_price, Decimal('23.00'))
self.assertEqual(objs[0].price_after_voucher, Decimal('20.70'))
def test_voucher_free_price_redeem_lowers_if_min(self):
with scopes_disabled():
v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event)
self.ticket.free_price = True
self.ticket.save()
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'price_%d' % self.ticket.id: '23.00',
}, follow=True)
with scopes_disabled():
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, Decimal('23.00'))
self.assertEqual(objs[0].listed_price, Decimal('23.00'))
self.assertEqual(objs[0].price_after_voucher, Decimal('23.00'))
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug),
{'voucher': v.code},
follow=True)
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
target_status_code=200)
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertIn('Early-bird', doc.select('.cart .cart-row')[0].select('strong')[0].text)
self.assertIn('1', doc.select('.cart .cart-row')[0].select('.count')[0].text)
self.assertIn('20.70', doc.select('.cart .cart-row')[0].select('.price')[0].text)
self.assertIn('20.70', doc.select('.cart .cart-row')[0].select('.price')[1].text)
with scopes_disabled():
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, Decimal('20.70'))
self.assertEqual(objs[0].listed_price, Decimal('23.00'))
self.assertEqual(objs[0].price_after_voucher, Decimal('20.70'))
def test_voucher_free_price_redeem_keeps_if_not_min(self):
with scopes_disabled():
v = Voucher.objects.create(item=self.ticket, value=Decimal('10.00'), price_mode='percent', event=self.event)
self.ticket.free_price = True
self.ticket.save()
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
'item_%d' % self.ticket.id: '1',
'price_%d' % self.ticket.id: '25.00',
}, follow=True)
response = self.client.post('/%s/%s/cart/voucher' % (self.orga.slug, self.event.slug),
{'voucher': v.code},
follow=True)
doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertIn('We did not find any position in your cart that we could use this voucher for', doc.select('.alert-danger')[0].text)
with scopes_disabled():
objs = list(CartPosition.objects.filter(cart_id=self.session_key, event=self.event))
self.assertEqual(len(objs), 1)
self.assertEqual(objs[0].item, self.ticket)
self.assertIsNone(objs[0].variation)
self.assertEqual(objs[0].price, Decimal('25.00'))
self.assertEqual(objs[0].listed_price, Decimal('23.00'))
self.assertEqual(objs[0].price_after_voucher, Decimal('23.00'))
def test_voucher_redemed(self):
with scopes_disabled():
v = Voucher.objects.create(item=self.ticket, value=Decimal('12.00'), event=self.event, redeemed=1)

View File

@@ -208,37 +208,6 @@ def test_authorize_with_prompt_none(env, client, ssoclient):
assert re.match(r'https://example.net\?code=([a-z0-9A-Z]{64})&state=STATE', r.headers['Location'])
@pytest.mark.django_db
def test_authorize_with_invalid_pkce_method(env, client, ssoclient):
url = f'/bigevents/oauth2/v1/authorize?' \
f'client_id={ssoclient[0].client_id}&' \
f'redirect_uri=https://example.net&' \
f'response_type=code&state=STATE&scope=openid+profile&' \
f'code_challenge=pkce_value&code_challenge_method=plain'
r = client.get(url)
assert r.status_code == 302
assert r.headers['Location'] == 'https://example.net?' \
'error=invalid_request&' \
'error_description=code_challenge+transform+algorithm+not+supported&' \
'state=STATE'
@pytest.mark.django_db
def test_authorize_with_missing_pkce_if_required(env, client, ssoclient):
ssoclient[0].require_pkce = True
ssoclient[0].save()
url = f'/bigevents/oauth2/v1/authorize?' \
f'client_id={ssoclient[0].client_id}&' \
f'redirect_uri=https://example.net&' \
f'response_type=code&state=STATE&scope=openid+profile'
r = client.get(url)
assert r.status_code == 302
assert r.headers['Location'] == 'https://example.net?' \
'error=invalid_request&' \
'error_description=code_challenge+%28PKCE%29+required&' \
'state=STATE'
@pytest.mark.django_db
def test_authorize_require_login_if_prompt_requires_it_or_is_expired(env, client, ssoclient):
with freeze_time("2021-04-10T11:00:00+02:00"):
@@ -317,7 +286,7 @@ def test_token_require_client_id(env, client, ssoclient):
assert b'unsupported_grant_type' in r.content
def _authorization_step(client, ssoclient, code_challenge=None):
def _authorization_step(client, ssoclient):
r = client.post('/bigevents/account/login', {
'email': 'john@example.org',
'password': 'foo',
@@ -330,8 +299,6 @@ def _authorization_step(client, ssoclient, code_challenge=None):
f'client_id={ssoclient[0].client_id}&' \
f'redirect_uri=https://example.net&' \
f'response_type=code&state=STATE&scope=openid+profile+email+phone'
if code_challenge:
url += f'&code_challenge={code_challenge}&code_challenge_method=S256'
r = client.get(url)
assert r.status_code == 302
m = re.match(r'https://example.net\?code=([a-z0-9A-Z]{64})&state=STATE', r.headers['Location'])
@@ -406,55 +373,6 @@ def test_token_success(env, client, ssoclient):
CustomerSSOAccessToken.objects.get(token=d['access_token']).expires < now()
@pytest.mark.django_db
def test_token_pkce_required_if_used_in_authorization(env, client, ssoclient):
code = _authorization_step(client, ssoclient, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
r = client.post('/bigevents/oauth2/v1/token', {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': 'https://example.net',
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(f'{ssoclient[0].client_id}:{ssoclient[1]}'.encode()).decode())
assert r.status_code == 400
d = json.loads(r.content)
assert d['error'] == 'invalid_grant'
assert d['error_description'] == 'Missing of code_verifier'
@pytest.mark.django_db
def test_token_pkce_incorrect(env, client, ssoclient):
code = _authorization_step(client, ssoclient, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
r = client.post('/bigevents/oauth2/v1/token', {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': 'https://example.net',
'code_verifier': "WRONG",
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(f'{ssoclient[0].client_id}:{ssoclient[1]}'.encode()).decode())
assert r.status_code == 400
d = json.loads(r.content)
assert d['error'] == 'invalid_grant'
assert d['error_description'] == 'Mismatch of code_verifier with code_challenge'
@pytest.mark.django_db
def test_token_success_pkce(env, client, ssoclient):
# this is the sample from the actual RFC
code_verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
code = _authorization_step(client, ssoclient, "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM")
r = client.post('/bigevents/oauth2/v1/token', {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': 'https://example.net',
'code_verifier': code_verifier,
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(f'{ssoclient[0].client_id}:{ssoclient[1]}'.encode()).decode())
print(r.content)
assert r.status_code == 200
d = json.loads(r.content)
assert d['access_token']
@pytest.mark.django_db
def test_scope_enforcement(env, client, ssoclient):
ssoclient[0].allowed_scopes = ['openid', 'profile']