mirror of
https://github.com/pretix/pretix.git
synced 2025-12-22 16:52:27 +00:00
Compare commits
2 Commits
fix-widget
...
fix-copy-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a669ab231 | ||
|
|
6a7bc64e11 |
@@ -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.*",
|
||||
|
||||
@@ -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)')),
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.'))
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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)
|
||||
for obj in self.get_queryset(force_filtered=True):
|
||||
if not obj.voucher_id:
|
||||
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,17 +186,16 @@ 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,
|
||||
)
|
||||
wle.priority = self.request.event.waitinglistentries.aggregate(m=Max('priority'))['m'] + 1
|
||||
wle.save(update_fields=['priority'])
|
||||
wle.log_action(
|
||||
'pretix.event.orders.waitinglist.changed',
|
||||
data={'priority': wle.priority},
|
||||
user=self.request.user,
|
||||
)
|
||||
wle = WaitingListEntry.objects.get(
|
||||
pk=request.POST.get('move_top'), event=self.request.event,
|
||||
)
|
||||
wle.priority = self.request.event.waitinglistentries.aggregate(m=Max('priority'))['m'] + 1
|
||||
wle.save(update_fields=['priority'])
|
||||
wle.log_action(
|
||||
'pretix.event.orders.waitinglist.changed',
|
||||
data={'priority': wle.priority},
|
||||
user=self.request.user,
|
||||
)
|
||||
messages.success(request, _('The waiting list entry has been moved to the top.'))
|
||||
return self._redirect_back()
|
||||
except WaitingListEntry.DoesNotExist:
|
||||
@@ -210,17 +204,16 @@ 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,
|
||||
)
|
||||
wle.priority = self.request.event.waitinglistentries.aggregate(m=Min('priority'))['m'] - 1
|
||||
wle.save(update_fields=['priority'])
|
||||
wle.log_action(
|
||||
'pretix.event.orders.waitinglist.changed',
|
||||
data={'priority': wle.priority},
|
||||
user=self.request.user,
|
||||
)
|
||||
wle = WaitingListEntry.objects.get(
|
||||
pk=request.POST.get('move_end'), event=self.request.event,
|
||||
)
|
||||
wle.priority = self.request.event.waitinglistentries.aggregate(m=Min('priority'))['m'] - 1
|
||||
wle.save(update_fields=['priority'])
|
||||
wle.log_action(
|
||||
'pretix.event.orders.waitinglist.changed',
|
||||
data={'priority': wle.priority},
|
||||
user=self.request.user,
|
||||
)
|
||||
messages.success(request, _('The waiting list entry has been moved to the end of the list.'))
|
||||
return self._redirect_back()
|
||||
except WaitingListEntry.DoesNotExist:
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -37,13 +37,7 @@ DEFAULT_TICKET_LAYOUT = '''[
|
||||
"content": "secret",
|
||||
"text": "",
|
||||
"text_i18n": {},
|
||||
"nowhitespace": false,
|
||||
"color": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
"nowhitespace": false
|
||||
},
|
||||
{
|
||||
"type": "poweredby",
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user