Compare commits

..

33 Commits

Author SHA1 Message Date
Mira Weller
3689d4a5a0 Remove unused import 2025-11-05 12:20:24 +01:00
Mira Weller
867ea39a0d Improve log messages 2025-11-05 12:11:15 +01:00
Mira Weller
b060968f62 Remove EmailVerificationTokenGenerator 2025-11-05 11:48:04 +01:00
Mira Weller
9a47eb6385 Changes from review 2025-11-05 11:16:37 +01:00
Mira Weller
be67e40f1c add test to UserSettingsTest 2025-11-04 14:46:20 +01:00
Mira Weller
c99a9ebe9a make sure UserSettingsTest always uses correct <form> element by adding a testid 2025-11-04 14:44:08 +01:00
Mira Weller
b5cac90475 changing to same password as previous is already prevented by HistoryPasswordValidator
(would have to move this to UserPasswordChangeForm but decided to remove instead as it's redundant)
2025-11-04 14:36:19 +01:00
Mira Weller
58d36706b2 rebase migration 2025-10-30 16:50:36 +01:00
Mira Weller
c520f77bbb Merge branch 'master' into validate-user-email 2025-10-30 16:48:47 +01:00
Mira Weller
73ceeffc7f formatting 2025-10-30 16:36:26 +01:00
Mira Weller
7b6c82c341 Adapt test cases for email change form 2025-10-30 16:33:03 +01:00
Mira Weller
35ac277b3d Add email verification flow for existing users 2025-10-30 13:45:09 +01:00
Mira Weller
eb731f305b Use new control dialog style for confirmation code entry 2025-10-29 17:41:17 +01:00
Mira Weller
bf9af08cab Remove invalid id reference 2025-10-29 17:27:03 +01:00
Mira Weller
95639dc6e1 Add label text to confirmation code form 2025-10-29 17:25:37 +01:00
Mira Weller
966d6bb8e9 Fix PasswordRecoverForm in case of non-existing user 2025-09-30 15:22:22 +02:00
Mira Weller
2c20bf972f Rebase migration 2025-09-30 15:22:06 +02:00
Mira Weller
0deba91e4b Tests: password change tests need to run on the dedicated password change page 2025-09-30 15:15:36 +02:00
Mira Weller
b9b937ea0d Tests: testing that changing settings won't reset the needs_password_change no longer needed/possible, because settings are not available as long as needs_password_change is set 2025-09-30 15:15:36 +02:00
Mira Weller
410215f575 Tests: accept redirect to password change instead of settings 2025-09-30 15:15:36 +02:00
Mira Weller
1fa84b772b Fully block password change for non-native auth backend 2025-09-30 15:15:36 +02:00
Mira Weller
cda950befc Add success message 2025-09-30 15:15:36 +02:00
Mira Weller
783d51b75f Fix needs_password_change 2025-09-30 15:15:36 +02:00
Mira Weller
7333f82e45 Code formatting 2025-09-30 15:15:36 +02:00
Mira Weller
b2a4ba96f8 Improve confirmation code logic (store code in session, generate from SystemRandom)
Add docstrings
2025-09-30 15:15:36 +02:00
Mira Weller
81f5af8414 Add missing EmailVerificationTokenGenerator 2025-09-30 15:15:36 +02:00
Mira Weller
ce1406b158 Change button order 2025-09-30 15:15:36 +02:00
Mira Weller
b5ad68f48d Add "old email address" field 2025-09-30 15:15:36 +02:00
Mira Weller
5512fa8245 Improved style 2025-09-30 15:15:36 +02:00
Mira Weller
83f891ce24 fix default 2025-09-30 15:15:36 +02:00
Mira Weller
04e4e33885 add is_verified to admin user edit page 2025-09-30 15:15:36 +02:00
Mira Weller
2a98907e88 Improve email and password change forms, implement is_verified flag 2025-09-30 15:15:36 +02:00
Mira Weller
22e7962a29 Validation of user email addresses 2025-09-30 15:15:35 +02:00
43 changed files with 1285 additions and 911 deletions

View File

@@ -33,7 +33,7 @@ dependencies = [
"celery==5.5.*",
"chardet==5.2.*",
"cryptography>=44.0.0",
"css-inline==0.18.*",
"css-inline==0.17.*",
"defusedcsv>=1.1.0",
"Django[argon2]==4.2.*,>=4.2.24",
"django-bootstrap3==25.2",

View File

@@ -800,7 +800,7 @@ class SalesChannelViewSet(viewsets.ModelViewSet):
identifier=serializer.instance.identifier,
)
inst.log_action(
'pretix.saleschannel.changed',
'pretix.sales_channel.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data,

View File

@@ -214,21 +214,38 @@ class PasswordRecoverForm(forms.Form):
error_messages = {
'pw_mismatch': _("Please enter the same password twice"),
}
email = forms.EmailField(
max_length=255,
disabled=True,
label=_("Your email address"),
widget=forms.EmailInput(
attrs={'autocomplete': 'username'},
),
)
password = forms.CharField(
label=_('Password'),
widget=forms.PasswordInput,
widget=forms.PasswordInput(attrs={
'autocomplete': 'new-password',
}),
max_length=4096,
required=True
)
password_repeat = forms.CharField(
label=_('Repeat password'),
widget=forms.PasswordInput,
widget=forms.PasswordInput(attrs={
'autocomplete': 'new-password',
}),
max_length=4096,
)
def __init__(self, user_id=None, *args, **kwargs):
self.user_id = user_id
super().__init__(*args, **kwargs)
initial = kwargs.pop('initial', {})
try:
self.user = User.objects.get(id=user_id)
initial['email'] = self.user.email
except User.DoesNotExist:
self.user = None
super().__init__(*args, initial=initial, **kwargs)
def clean(self):
password1 = self.cleaned_data.get('password', '')
@@ -243,11 +260,7 @@ class PasswordRecoverForm(forms.Form):
def clean_password(self):
password1 = self.cleaned_data.get('password', '')
try:
user = User.objects.get(id=self.user_id)
except User.DoesNotExist:
user = None
if validate_password(password1, user=user) is not None:
if validate_password(password1, user=self.user) is not None:
raise forms.ValidationError(_(password_validators_help_texts()), code='pw_invalid')
return password1
@@ -307,3 +320,10 @@ class ReauthForm(forms.Form):
self.error_messages['inactive'],
code='inactive',
)
class ConfirmationCodeForm(forms.Form):
code = forms.IntegerField(
label=_('Confirmation code'),
widget=forms.NumberInput(attrs={'class': 'confirmation-code-input', 'inputmode': 'numeric', 'type': 'text'}),
)

View File

@@ -39,37 +39,16 @@ from django.contrib.auth.password_validation import (
password_validators_help_texts, validate_password,
)
from django.db.models import Q
from django.urls.base import reverse
from django.utils.translation import gettext_lazy as _
from pytz import common_timezones
from pretix.base.models import User
from pretix.control.forms import SingleLanguageWidget
from pretix.helpers.format import format_map
class UserSettingsForm(forms.ModelForm):
error_messages = {
'duplicate_identifier': _("There already is an account associated with this email address. "
"Please choose a different one."),
'pw_current': _("Please enter your current password if you want to change your email address "
"or password."),
'pw_current_wrong': _("The current password you entered was not correct."),
'pw_mismatch': _("Please enter the same password twice"),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
'pw_equal': _("Please choose a password different to your current one.")
}
old_pw = forms.CharField(max_length=255,
required=False,
label=_("Your current password"),
widget=forms.PasswordInput())
new_pw = forms.CharField(max_length=255,
required=False,
label=_("New password"),
widget=forms.PasswordInput())
new_pw_repeat = forms.CharField(max_length=255,
required=False,
label=_("Repeat new password"),
widget=forms.PasswordInput())
timezone = forms.ChoiceField(
choices=((a, a) for a in common_timezones),
label=_("Default timezone"),
@@ -93,11 +72,60 @@ class UserSettingsForm(forms.ModelForm):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['email'].required = True
if self.user.auth_backend != 'native':
del self.fields['old_pw']
del self.fields['new_pw']
del self.fields['new_pw_repeat']
self.fields['email'].disabled = True
self.fields['email'].disabled = True
self.fields['email'].help_text = format_map('<a href="{link}"><span class="fa fa-edit"></span> {text}</a>', {
'text': _("Change email address"),
'link': reverse('control:user.settings.email.change')
})
class User2FADeviceAddForm(forms.Form):
name = forms.CharField(label=_('Device name'), max_length=64)
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
('totp', _('Smartphone with the Authenticator application')),
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
))
class UserPasswordChangeForm(forms.Form):
error_messages = {
'pw_current': _("Please enter your current password if you want to change your email address "
"or password."),
'pw_current_wrong': _("The current password you entered was not correct."),
'pw_mismatch': _("Please enter the same password twice"),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
'pw_equal': _("Please choose a password different to your current one.")
}
email = forms.EmailField(max_length=255,
disabled=True,
label=_("Your email address"),
widget=forms.EmailInput(
attrs={'autocomplete': 'username'},
))
old_pw = forms.CharField(max_length=255,
required=False,
label=_("Your current password"),
widget=forms.PasswordInput(
attrs={'autocomplete': 'current-password'},
))
new_pw = forms.CharField(max_length=255,
required=False,
label=_("New password"),
widget=forms.PasswordInput(
attrs={'autocomplete': 'new-password'},
))
new_pw_repeat = forms.CharField(max_length=255,
required=False,
label=_("Repeat new password"),
widget=forms.PasswordInput(
attrs={'autocomplete': 'new-password'},
))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
initial = kwargs.pop('initial', {})
initial['email'] = self.user.email
super().__init__(*args, initial=initial, **kwargs)
def clean_old_pw(self):
old_pw = self.cleaned_data.get('old_pw')
@@ -121,15 +149,6 @@ class UserSettingsForm(forms.ModelForm):
return old_pw
def clean_email(self):
email = self.cleaned_data['email']
if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.instance.pk)).exists():
raise forms.ValidationError(
self.error_messages['duplicate_identifier'],
code='duplicate_identifier',
)
return email
def clean_new_pw(self):
password1 = self.cleaned_data.get('new_pw', '')
if password1 and validate_password(password1, user=self.user) is not None:
@@ -148,32 +167,24 @@ class UserSettingsForm(forms.ModelForm):
code='pw_mismatch'
)
def clean(self):
password1 = self.cleaned_data.get('new_pw')
email = self.cleaned_data.get('email')
old_pw = self.cleaned_data.get('old_pw')
if (password1 or email != self.user.email) and not old_pw:
class UserEmailChangeForm(forms.Form):
error_messages = {
'duplicate_identifier': _("There already is an account associated with this email address. "
"Please choose a different one."),
}
old_email = forms.EmailField(label=_('Old email address'), disabled=True)
new_email = forms.EmailField(label=_('New email address'))
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
def clean_new_email(self):
email = self.cleaned_data['new_email']
if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.user.pk)).exists():
raise forms.ValidationError(
self.error_messages['pw_current'],
code='pw_current'
self.error_messages['duplicate_identifier'],
code='duplicate_identifier',
)
if password1 and password1 == old_pw:
raise forms.ValidationError(
self.error_messages['pw_equal'],
code='pw_equal'
)
if password1:
self.instance.set_password(password1)
return self.cleaned_data
class User2FADeviceAddForm(forms.Form):
name = forms.CharField(label=_('Device name'), max_length=64)
devicetype = forms.ChoiceField(label=_('Device type'), widget=forms.RadioSelect, choices=(
('totp', _('Smartphone with the Authenticator application')),
('webauthn', _('WebAuthn-compatible hardware token (e.g. Yubikey)')),
))
return email

View File

@@ -19,20 +19,15 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import json
import logging
from collections import defaultdict
from functools import cached_property
from typing import Optional
import jsonschema
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from pretix.base.signals import PluginAwareRegistry
logger = logging.getLogger(__name__)
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
if a_map:
@@ -110,38 +105,12 @@ They are annotated with their ``action_type`` and the defining ``plugin``.
log_entry_types = LogEntryTypeRegistry()
def prepare_schema(schema):
def handle_properties(t):
return {"shred_properties": [k for k, v in t["properties"].items() if v["shred"]]}
def walk_tree(schema):
if type(schema) is dict:
new_keys = {}
for k, v in schema.items():
if k == "properties":
new_keys = handle_properties(schema)
walk_tree(v)
if schema.get("type") == "object" and "additionalProperties" not in new_keys:
new_keys["additionalProperties"] = False
schema.update(new_keys)
elif type(schema) is list:
for v in schema:
walk_tree(v)
walk_tree(schema)
return schema
class LogEntryType:
"""
Base class for a type of LogEntry, identified by its action_type.
"""
data_schema = None # {"type": "object", "properties": []}
def __init__(self, action_type=None, plain=None):
if self.data_schema:
print(self.__class__.__name__, "has schema", self._prepared_schema)
if action_type:
self.action_type = action_type
if plain:
@@ -178,37 +147,12 @@ class LogEntryType:
object_link_wrapper = '{val}'
def validate_data(self, parsed_data):
if not self._prepared_schema:
return
try:
jsonschema.validate(parsed_data, self._prepared_schema)
except jsonschema.exceptions.ValidationError as ex:
logger.warning("%s schema validation failed: %s %s", type(self).__name__, ex.json_path, ex.message)
raise
@cached_property
def _prepared_schema(self):
if self.data_schema:
return prepare_schema(self.data_schema)
def shred_pii(self, logentry):
"""
To be used for shredding personally identified information contained in the data field of a LogEntry of this
type.
"""
if self._prepared_schema:
def shred_fun(validator, value, instance, schema):
for key in value:
instance[key] = "##########"
v = jsonschema.validators.extend(jsonschema.validators.Draft202012Validator,
validators={"shred_properties": shred_fun})
data = logentry.parsed_data
jsonschema.validate(data, self._prepared_schema, v)
logentry.data = json.dumps(data)
else:
raise NotImplementedError
raise NotImplementedError
class NoOpShredderMixin:

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.23 on 2025-09-04 16:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0293_cartposition_price_includes_rounding_correction_and_more"),
]
operations = [
migrations.AddField(
model_name="user",
name="is_verified",
field=models.BooleanField(default=False),
),
]

View File

@@ -35,6 +35,7 @@
import binascii
import json
import operator
import secrets
from datetime import timedelta
from functools import reduce
@@ -44,6 +45,7 @@ from django.contrib.auth.models import (
)
from django.contrib.auth.tokens import default_token_generator
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import BadRequest, PermissionDenied
from django.db import IntegrityError, models, transaction
from django.db.models import Q
from django.utils.crypto import get_random_string, salted_hmac
@@ -239,9 +241,11 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
MAX_CONFIRMATION_CODE_ATTEMPTS = 10
email = models.EmailField(unique=True, db_index=True, null=True, blank=True,
verbose_name=_('Email'), max_length=190)
is_verified = models.BooleanField(default=False, verbose_name=_('Verified email address'))
fullname = models.CharField(max_length=255, blank=True, null=True,
verbose_name=_('Full name'))
is_active = models.BooleanField(default=True,
@@ -353,6 +357,77 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
except SendMailException:
pass # Already logged
def send_confirmation_code(self, session, reason, email=None, state=None):
"""
Sends a confirmation code via email to the user. The code is only valid for the action specified by `reason`.
The email is either sent to the email address currently on file for the user, or to the one given in the optional `email` parameter.
A `state` value can be provided which is bound to this confirmation code, and returned on successfully checking the code.
:param session: the user's request session
:param reason: the action which should be confirmed using this confirmation code (currently, only `email_change` is allowed)
:param email: optional, the email address to send the confirmation code to
:param state: optional
"""
from pretix.base.services.mail import mail
with language(self.locale):
if reason == 'email_change':
msg = str(_('to confirm changing your email address from {old_email}\nto {new_email}, use the following code:').format(
old_email=self.email, new_email=email,
))
elif reason == 'email_verify':
msg = str(_('to confirm that your email address {email} belongs to your pretix account, use the following code:').format(
email=self.email,
))
else:
raise Exception('Invalid confirmation code reason')
code = "%07d" % secrets.SystemRandom().randint(0, 9999999)
session['user_confirmation_code:' + reason] = {
'code': code,
'state': state,
'attempts': 0,
}
mail(
email or self.email,
_('pretix confirmation code'),
'pretixcontrol/email/confirmation_code.txt',
{
'user': self,
'reason': msg,
'code': code,
},
event=None,
user=self,
locale=self.locale
)
def check_confirmation_code(self, session, reason, code):
"""
Checks a confirmation code entered by the user against the valid code stored in the session.
If the code is correct, an optional state bound to the code is returned.
If the code is incorrect, PermissionDenied is raised. If the code could not be validated, either because no
code for the given reason is stored, or the number of input attempts is exceeded, BadRequest is raised.
:param session: the user's request session
:param reason: the action which should be confirmed using this confirmation code
:param code: the code entered by the user
:return: optional state bound to this code using the state parameter of send_confirmation_code, None otherwise
"""
stored = session.get('user_confirmation_code:' + reason)
if not stored:
raise BadRequest
if stored['attempts'] > User.MAX_CONFIRMATION_CODE_ATTEMPTS:
raise BadRequest
if int(stored['code']) == int(code):
del session['user_confirmation_code:' + reason]
return stored['state']
else:
stored['attempts'] += 1
session['user_confirmation_code:' + reason] = stored
raise PermissionDenied
def send_password_reset(self):
from pretix.base.services.mail import mail

View File

@@ -80,7 +80,6 @@ class LoggingMixin:
from pretix.api.models import OAuthAccessToken, OAuthApplication
from pretix.api.webhooks import notify_webhooks
from ..logentrytype_registry import log_entry_types
from ..services.notifications import notify
from .devices import Device
from .event import Event
@@ -125,13 +124,7 @@ class LoggingMixin:
if (sensitivekey in k) and v:
data[k] = "********"
type, meta = log_entry_types.get(action_type=action)
if not type:
raise TypeError("Undefined log entry type '%s'" % action)
logentry.data = json.dumps(data, cls=CustomJSONEncoder, sort_keys=True)
type.validate_data(json.loads(logentry.data))
elif data:
raise TypeError("You should only supply dictionaries as log data.")
if save:

View File

@@ -1377,7 +1377,7 @@ class GiftCardPayment(BasePaymentProvider):
execute_payment_needs_user = False
verbose_name = _("Gift card")
payment_form_class = GiftCardPaymentForm
payment_form_template_name = 'pretixpresale/giftcard/checkout.html'
payment_form_template_name = 'pretixcontrol/giftcards/checkout.html'
@cached_property
def customer_gift_cards(self):
@@ -1504,7 +1504,7 @@ class GiftCardPayment(BasePaymentProvider):
return super().order_change_allowed(order) and self.event.organizer.has_gift_cards
def checkout_confirm_render(self, request, order=None, info_data=None) -> str:
return get_template('pretixpresale/giftcard/checkout_confirm.html').render({
return get_template('pretixcontrol/giftcards/checkout_confirm.html').render({
'info_data': info_data,
})

View File

@@ -258,15 +258,9 @@ def build_invoice(invoice: Invoice) -> Invoice:
if resp:
desc += "<br/>" + resp
answers_qs = p.answers.filter(
question__print_on_invoice=True
).select_related(
'question'
).order_by(
'question__position',
'question__id'
)
for answ in answers_qs:
for answ in p.answers.all():
if not answ.question.print_on_invoice:
continue
desc += "<br />{}{} {}".format(
answ.question.question,
"" if str(answ.question.question).endswith("?") else ":",

View File

@@ -69,6 +69,7 @@ class UserEditForm(forms.ModelForm):
'email',
'require_2fa',
'is_active',
'is_verified',
'is_staff',
'needs_password_change',
'last_login'

View File

@@ -503,6 +503,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
'pretix.event.order.valid_if_pending.set': _('The order has been set to be usable before it is paid.'),
'pretix.event.order.valid_if_pending.unset': _('The order has been set to require payment before use.'),
'pretix.event.order.expired': _('The order has been marked as expired.'),
'pretix.event.order.paid': _('The order has been marked as paid.'),
'pretix.event.order.cancellationrequest.deleted': _('The cancellation request has been deleted.'),
'pretix.event.order.refunded': _('The order has been refunded.'),
'pretix.event.order.reactivated': _('The order has been reactivated.'),
@@ -534,7 +535,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
'pretix.event.order.checkin_attention': _('The order\'s flag to require attention at check-in has been '
'toggled.'),
'pretix.event.order.checkin_text': _('The order\'s check-in text has been changed.'),
'pretix.event.order.valid_if_pending': _('The order\'s flag to be considered valid even if '
'pretix.event.order.pretix.event.order.valid_if_pending': _('The order\'s flag to be considered valid even if '
'unpaid has been toggled.'),
'pretix.event.order.payment.changed': _('A new payment {local_id} has been started instead of the previous one.'),
'pretix.event.order.email.sent': _('An unidentified type email has been sent.'),
@@ -574,21 +575,6 @@ class CoreOrderLogEntryType(OrderLogEntryType):
pass
@log_entry_types.new()
class OrderPaidLogEntryType(CoreOrderLogEntryType):
action_type = 'pretix.event.order.paid'
plain = _('The order has been marked as paid.')
data_schema = {
"type": "object",
"properties": {
"provider": {"type": ["null", "string"], "shred": False, },
"info": {"type": ["null", "string", "object"], "shred": True, },
"date": {"type": ["null", "string"], "shred": False, },
"force": {"type": "boolean", "shred": False, },
},
}
@log_entry_types.new_from_dict({
'pretix.voucher.added': _('The voucher has been created.'),
'pretix.voucher.sent': _('The voucher has been sent to {recipient}.'),
@@ -599,63 +585,13 @@ class OrderPaidLogEntryType(CoreOrderLogEntryType):
'pretix.voucher.added.waitinglist': _('The voucher has been assigned to {email} through the waiting list.'),
})
class CoreVoucherLogEntryType(VoucherLogEntryType):
data_schema = {
"type": "object",
"properties": {
"item": {"type": ["null", "number"], "shred": False, },
"variation": {"type": ["null", "number"], "shred": False, },
"tag": {"type": "string", "shred": False,},
"block_quota": {"type": "boolean", "shred": False, },
"valid_until": {"type": ["null", "string"], "shred": False, },
"min_usages": {"type": "number", "shred": False, },
"max_usages": {"type": "number", "shred": False, },
"subevent": {"type": ["null", "number", "object"], "shred": False, },
"source": {"type": "string", "shred": False,},
"allow_ignore_quota": {"type": "boolean", "shred": False, },
"code": {"type": "string", "shred": False,},
"comment": {"type": "string", "shred": True,},
"price_mode": {"type": "string", "shred": False,},
"seat": {"type": "string", "shred": False,},
"quota": {"type": ["null", "number"], "shred": False,},
"value": {"type": ["null", "string"], "shred": False,},
"redeemed": {"type": "number", "shred": False,},
"all_addons_included": {"type": "boolean", "shred": False, },
"all_bundles_included": {"type": "boolean", "shred": False, },
"budget": {"type": ["null", "number"], "shred": False, },
"itemvar": {"type": "string", "shred": False,},
"show_hidden_items": {"type": "boolean", "shred": False, },
# bulk create:
"bulk": {"type": "boolean", "shred": False,},
"seats": {"type": "array", "shred": False,},
"send": {"type": ["string", "boolean"], "shred": False,},
"send_recipients": {"type": "array", "shred": True,},
"send_subject": {"type": "string", "shred": False,},
"send_message": {"type": "string", "shred": True,},
# pretix.voucher.sent
"recipient": {"type": "string", "shred": True,},
"name": {"type": "string", "shred": True,},
"subject": {"type": "string", "shred": False,},
"message": {"type": "string", "shred": True,},
# pretix.voucher.added.waitinglist
"email": {"type": "string", "shred": True,},
"waitinglistentry": {"type": "number", "shred": False, },
},
}
pass
@log_entry_types.new()
class VoucherRedeemedLogEntryType(VoucherLogEntryType):
action_type = 'pretix.voucher.redeemed'
plain = _('The voucher has been redeemed in order {order_code}.')
data_schema = {
"type": "object",
"properties": {
"order_code": {"type": "string", "shred": False, },
},
}
def display(self, logentry, data):
url = reverse('control:event.order', kwargs={
@@ -698,16 +634,9 @@ class TeamMembershipLogEntryType(LogEntryType):
'pretix.team.member.removed': _('{user} has been removed from the team.'),
'pretix.team.invite.created': _('{user} has been invited to the team.'),
'pretix.team.invite.resent': _('Invite for {user} has been resent.'),
'pretix.team.invite.deleted': _('Invite for {user} has been deleted.'),
})
class CoreTeamMembershipLogEntryType(TeamMembershipLogEntryType):
data_schema = {
"type": "object",
"properties": {
"email": {"type": "string", "shred": True, },
"user": {"type": "number", "shred": False, },
},
}
pass
@log_entry_types.new()
@@ -738,6 +667,14 @@ class UserSettingsChangedLogEntryType(LogEntryType):
return text
@log_entry_types.new_from_dict({
'pretix.user.email.changed': _('Your email address has been changed from {old_email} to {email}.'),
'pretix.user.email.confirmed': _('Your email address {email} has been confirmed.'),
})
class UserEmailChangedLogEntryType(LogEntryType):
pass
class UserImpersonatedLogEntryType(LogEntryType):
def display(self, logentry, data):
return self.plain.format(data['other_email'])
@@ -904,10 +841,6 @@ class OrganizerPluginStateLogEntryType(LogEntryType):
'pretix.event.permissions.invited': _('A user has been invited to the event team.'),
'pretix.event.permissions.changed': _('A user\'s permissions have been changed.'),
'pretix.event.permissions.deleted': _('A user has been removed from the event team.'),
'pretix.event.seats.blocks.changed': _('A seat in the seating plan has been blocked or unblocked.'),
'pretix.seatingplan.added': _('A seating plan has been added.'),
'pretix.seatingplan.changed': _('A seating plan has been changed.'),
'pretix.seatingplan.deleted': _('A seating plan has been deleted.'),
})
class CoreEventLogEntryType(EventLogEntryType):
pass

View File

@@ -72,7 +72,7 @@ class PermissionMiddleware:
)
EXCEPTIONS_FORCED_PW_CHANGE = (
"user.settings",
"user.settings.password.change",
"auth.logout"
)
@@ -139,7 +139,7 @@ class PermissionMiddleware:
return redirect_to_url(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
except SessionPasswordChangeRequired:
if url_name not in self.EXCEPTIONS_FORCED_PW_CHANGE:
return redirect_to_url(reverse('control:user.settings') + '?next=' + quote(request.get_full_path()))
return redirect_to_url(reverse('control:user.settings.password.change') + '?next=' + quote(request.get_full_path()))
except Session2FASetupRequired:
if url_name not in self.EXCEPTIONS_2FA:
return redirect_to_url(reverse('control:user.settings.2fa'))

View File

@@ -7,6 +7,7 @@
<h3>{% trans "Set new password" %}</h3>
{% csrf_token %}
{% bootstrap_form_errors form type='all' layout='inline' %}
{% bootstrap_field form.email %}
{% bootstrap_field form.password %}
{% bootstrap_field form.password_repeat %}
<div class="form-group buttons">

View File

@@ -0,0 +1,13 @@
{% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello,
{{ reason }}
{{ code }}
Please do never give this code to another person. Our support team will never ask for this code.
If this code was not requested by you, please contact us immediately.
Best regards,
Your pretix team
{% endblocktrans %}

View File

@@ -0,0 +1,35 @@
{% load i18n %}
{% load bootstrap3 %}
{% load money %}
{% load rich_text %}
{{ request.event.settings.payment_giftcard_public_description|rich_text }}
{% if customer_gift_cards %}
<p><strong>
<span class="sr-only">{% trans "Information" %}</span>
{% trans "The following gift cards are available in your customer account:" %}
</strong></p>
<form method="post">
{% csrf_token %}
<ul class="list-group">
{% for c in customer_gift_cards %}
<li class="list-group-item row row-no-gutters">
<div class="col-sm-8 col-md-9" id="gc-code-{{ forloop.counter }}">
{{ c }}
</div>
<div class="col-sm-2 text-right" id="gc-value-{{ forloop.counter }}">
{{ c.value|money:c.currency }}
</div>
<div class="col-sm-2 col-md-1 text-right">
<button name="use_giftcard" class="btn btn-primary btn-xs use_giftcard" data-value="{{ c.secret }}"
title="{% trans "Use gift card" %}"
aria-describedby="gc-code-{{ forloop.counter }} gc-value-{{ forloop.counter }}">
{% trans "Apply" %}
</button>
</div>
</li>
{% endfor %}
</ul>
</form>
{% endif %}
{% bootstrap_form form layout='checkout' %}

View File

@@ -769,12 +769,10 @@
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
<strong>{{ items.total|money:event.currency }}</strong>
{% if django_settings.DEBUG %}
<br/>
<small class="admin-only">
tax_rounding_mode = {{ order.tax_rounding_mode }}
</small>
{% endif %}
<br/>
<small class="admin-only">
tax_rounding_mode = {{ order.tax_rounding_mode }}
</small>
</div>
<div class="clearfix"></div>
</div>

View File

@@ -0,0 +1,29 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Change login email address" %}{% endblock %}
{% block content %}
<form action="" method="post" class="form centered-form">
<h1>
{% trans "Change login email address" %}
</h1>
{% csrf_token %}
{% bootstrap_form_errors form %}
<p class="text-muted">
{% trans "This changes the email address used to login to your account, as well as where we send email notifications." %}
</p>
{% bootstrap_field form.old_email %}
{% bootstrap_field form.new_email %}
<p>
{% trans "We will send a confirmation code to your new email address, which you need to enter in the next step to confirm the email address is correct." %}
</p>
<div class="form-group submit-group">
<a href="{% url "control:user.settings" %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary btn-save btn-lg">
{% trans "Continue" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Change password" %}{% endblock %}
{% block content %}
<form action="" method="post" class="form centered-form">
<h1>
{% trans "Change password" %}
</h1>
<br>
{% csrf_token %}
{% bootstrap_form_errors form %}
{% bootstrap_field form.email %}
{% bootstrap_field form.old_pw %}
{% bootstrap_field form.new_pw %}
{% bootstrap_field form.new_pw_repeat %}
<div class="form-group submit-group">
<a href="{% url "control:user.settings" %}" class="btn btn-default btn-cancel">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-primary btn-save btn-lg">
{% trans "Change password" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Enter confirmation code" %}{% endblock %}
{% block content %}
<form action="" method="post" class="form centered-form">
<h1>
{% trans "Enter confirmation code" %}
</h1>
{% csrf_token %}
{% bootstrap_form_errors form type='all' layout='inline' %}
<p>{{ message }}</p>
{% bootstrap_field form.code %}
<div class="form-group submit-group">
<a href="{{ cancel_url }}" class="btn btn-default btn-cancel">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-primary btn-save btn-lg">
{% trans "Continue" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -3,8 +3,26 @@
{% load bootstrap3 %}
{% block title %}{% trans "Account settings" %}{% endblock %}
{% block content %}
{% if not user.is_verified %}
<div class="alert alert-info">
<p>
{% blocktrans trimmed %}
Your email address is not confirmed yet. To secure your account, please confirm your email address using
a confirmation code we will send to your email address.
{% endblocktrans %}
</p>
<p>
<form action="{% url "control:user.settings.email.send_verification_code" %}" method="post" class="form-horizontal">
{% csrf_token %}
<button type="submit" class="btn btn-primary">
{% trans "Send confirmation email" %}
</button>
</form>
</p>
</div>
{% endif %}
<h1>{% trans "Account settings" %}</h1>
<form action="" method="post" class="form-horizontal">
<form action="" method="post" class="form-horizontal" data-testid="usersettingsform">
{% csrf_token %}
{% bootstrap_form_errors form %}
<fieldset>
@@ -13,7 +31,7 @@
{% bootstrap_field form.locale layout='horizontal' %}
{% bootstrap_field form.timezone layout='horizontal' %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_new_pw_repeat">{% trans "Notifications" %}</label>
<label class="col-md-3 control-label">{% trans "Notifications" %}</label>
<div class="col-md-9 static-form-row">
{% if request.user.notifications_send and request.user.notification_settings.exists %}
<span class="label label-success">
@@ -41,8 +59,18 @@
{% bootstrap_field form.new_pw layout='horizontal' %}
{% bootstrap_field form.new_pw_repeat layout='horizontal' %}
{% endif %}
{% if user.auth_backend == 'native' %}
<div class="form-group">
<label class="col-md-3 control-label">{% trans "Password" %}</label>
<div class="col-md-9 static-form-row">
<a href="{% url "control:user.settings.password.change" %}">
<span class="fa fa-edit"></span> {% trans "Change password" %}
</a>
</div>
</div>
{% endif %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_new_pw_repeat">{% trans "Two-factor authentication" %}</label>
<label class="col-md-3 control-label">{% trans "Two-factor authentication" %}</label>
<div class="col-md-9 static-form-row">
{% if user.require_2fa %}
<span class="label label-success">{% trans "Enabled" %}</span> &nbsp;
@@ -58,7 +86,7 @@
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="">{% trans "Authorized applications" %}</label>
<label class="col-md-3 control-label">{% trans "Authorized applications" %}</label>
<div class="col-md-9 static-form-row">
<a href="{% url "control:user.settings.oauth.list" %}">
<span class="fa fa-plug"></span>
@@ -67,7 +95,7 @@
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label" for="">{% trans "Account history" %}</label>
<label class="col-md-3 control-label">{% trans "Account history" %}</label>
<div class="col-md-9 static-form-row">
<a href="{% url "control:user.settings.history" %}">
<span class="fa fa-history"></span>

View File

@@ -56,6 +56,7 @@
{% if form.new_pw %}
{% bootstrap_field form.new_pw layout='control' %}
{% bootstrap_field form.new_pw_repeat layout='control' %}
{% bootstrap_field form.is_verified layout='control' %}
{% endif %}
{% bootstrap_field form.last_login layout='control' %}
{% bootstrap_field form.require_2fa layout='control' %}

View File

@@ -110,6 +110,10 @@ urlpatterns = [
name='user.settings.2fa.confirm.webauthn'),
re_path(r'^settings/2fa/(?P<devicetype>[^/]+)/(?P<device>[0-9]+)/delete', user.User2FADeviceDeleteView.as_view(),
name='user.settings.2fa.delete'),
re_path(r'^settings/email/confirm$', user.UserEmailConfirmView.as_view(), name='user.settings.email.confirm'),
re_path(r'^settings/email/change$', user.UserEmailChangeView.as_view(), name='user.settings.email.change'),
re_path(r'^settings/email/verify', user.UserEmailVerifyView.as_view(), name='user.settings.email.send_verification_code'),
re_path(r'^settings/password/change$', user.UserPasswordChangeView.as_view(), name='user.settings.password.change'),
re_path(r'^organizers/$', organizer.OrganizerList.as_view(), name='organizers'),
re_path(r'^organizers/add$', organizer.OrganizerCreate.as_view(), name='organizers.add'),
re_path(r'^organizers/select2$', typeahead.organizer_select2, name='organizers.select2'),

View File

@@ -44,11 +44,13 @@ from django.conf import settings
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import BadRequest, PermissionDenied
from django.db import transaction
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -60,8 +62,11 @@ from django_scopes import scopes_disabled
from webauthn.helpers import generate_challenge, generate_user_handle
from pretix.base.auth import get_auth_backends
from pretix.base.forms.auth import ReauthForm
from pretix.base.forms.user import User2FADeviceAddForm, UserSettingsForm
from pretix.base.forms.auth import ConfirmationCodeForm, ReauthForm
from pretix.base.forms.user import (
User2FADeviceAddForm, UserEmailChangeForm, UserPasswordChangeForm,
UserSettingsForm,
)
from pretix.base.models import (
Event, LogEntry, NotificationSetting, U2FDevice, User, WebAuthnDevice,
)
@@ -237,25 +242,7 @@ class UserSettings(UpdateView):
data = {}
for k in form.changed_data:
if k not in ('old_pw', 'new_pw_repeat'):
if 'new_pw' == k:
data['new_pw'] = True
else:
data[k] = form.cleaned_data[k]
msgs = []
if 'new_pw' in form.changed_data:
self.request.user.needs_password_change = False
msgs.append(_('Your password has been changed.'))
if 'email' in form.changed_data:
msgs.append(_('Your email address has been changed to {email}.').format(email=form.cleaned_data['email']))
if msgs:
self.request.user.send_security_notice(msgs, email=form.cleaned_data['email'])
if self._old_email != form.cleaned_data['email']:
self.request.user.send_security_notice(msgs, email=self._old_email)
data[k] = form.cleaned_data[k]
sup = super().form_valid(form)
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data=data)
@@ -834,3 +821,159 @@ class EditStaffSession(StaffMemberRequiredMixin, UpdateView):
return get_object_or_404(StaffSession, pk=self.kwargs['id'])
else:
return get_object_or_404(StaffSession, pk=self.kwargs['id'], user=self.request.user)
class UserPasswordChangeView(FormView):
max_time = 300
form_class = UserPasswordChangeForm
template_name = 'pretixcontrol/user/change_password.html'
def get_form_kwargs(self):
if self.request.user.auth_backend != 'native':
raise PermissionDenied
return {
**super().get_form_kwargs(),
"user": self.request.user,
}
def form_valid(self, form):
with transaction.atomic():
self.request.user.set_password(form.cleaned_data['new_pw'])
self.request.user.needs_password_change = False
self.request.user.save()
msgs = []
msgs.append(_('Your password has been changed.'))
self.request.user.send_security_notice(msgs)
self.request.user.log_action('pretix.user.settings.changed', user=self.request.user, data={'new_pw': True})
update_session_auth_hash(self.request, self.request.user)
messages.success(self.request, _('Your changes have been saved.'))
return redirect(self.get_success_url())
def form_invalid(self, form):
messages.error(self.request, _('We could not save your changes. See below for details.'))
return super().form_invalid(form)
def get_success_url(self):
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
return self.request.GET.get("next")
return reverse('control:user.settings')
class UserEmailChangeView(RecentAuthenticationRequiredMixin, FormView):
max_time = 300
form_class = UserEmailChangeForm
template_name = 'pretixcontrol/user/change_email.html'
def get_form_kwargs(self):
if self.request.user.auth_backend != 'native':
raise PermissionDenied
return {
**super().get_form_kwargs(),
"user": self.request.user,
}
def get_initial(self):
return {
"old_email": self.request.user.email
}
def form_valid(self, form):
self.request.user.send_confirmation_code(
session=self.request.session,
reason='email_change',
email=form.cleaned_data['new_email'],
state=form.cleaned_data['new_email'],
)
self.request.session['email_confirmation_destination'] = form.cleaned_data['new_email']
return redirect(reverse('control:user.settings.email.confirm', kwargs={}) + '?reason=email_change')
def form_invalid(self, form):
messages.error(self.request, _('We could not save your changes. See below for details.'))
return super().form_invalid(form)
class UserEmailVerifyView(View):
def post(self, request, *args, **kwargs):
if self.request.user.is_verified:
messages.success(self.request, _('Your email address was already verified.'))
return redirect(reverse('control:user.settings', kwargs={}))
self.request.user.send_confirmation_code(
session=self.request.session,
reason='email_verify',
email=self.request.user.email,
state=self.request.user.email,
)
self.request.session['email_confirmation_destination'] = self.request.user.email
return redirect(reverse('control:user.settings.email.confirm', kwargs={}) + '?reason=email_verify')
class UserEmailConfirmView(FormView):
form_class = ConfirmationCodeForm
template_name = 'pretixcontrol/user/confirmation_code_dialog.html'
def get_context_data(self, **kwargs):
return {
**super().get_context_data(**kwargs),
"cancel_url": reverse('control:user.settings', kwargs={}),
"message": format_html(
_("Please enter the confirmation code we sent to your email address <strong>{email}</strong>."),
email=self.request.session.get('email_confirmation_destination', ''),
),
}
@transaction.atomic()
def form_valid(self, form):
reason = self.request.GET['reason']
if reason not in ('email_change', 'email_verify'):
raise PermissionDenied
try:
new_email = self.request.user.check_confirmation_code(
session=self.request.session,
reason=reason,
code=form.cleaned_data['code'],
)
except PermissionDenied:
return self.form_invalid(form)
except BadRequest:
messages.error(self.request, _(
'We were unable to verify your confirmation code. Please try again.'
))
return redirect(reverse('control:user.settings', kwargs={}))
log_data = {
'email': new_email,
'email_verified': True,
}
if reason == 'email_change':
msgs = []
msgs.append(_('Your email address has been changed to {email}.').format(email=new_email))
log_data['old_email'] = old_email = self.request.user.email
self.request.user.send_security_notice(msgs, email=old_email)
self.request.user.send_security_notice(msgs, email=new_email)
log_action = 'pretix.user.email.changed'
else:
log_action = 'pretix.user.email.confirmed'
self.request.user.email = new_email
self.request.user.is_verified = True
self.request.user.save()
self.request.user.log_action(log_action, user=self.request.user, data=log_data)
update_session_auth_hash(self.request, self.request.user)
if reason == 'email_change':
messages.success(self.request, _('Your email address has been changed successfully.'))
else:
messages.success(self.request, _('Your email address has been confirmed successfully.'))
return redirect(reverse('control:user.settings', kwargs={}))
def form_invalid(self, form):
messages.error(self.request, _('The entered confirmation code is not correct. Please try again.'))
return super().form_invalid(form)

View File

@@ -8,10 +8,10 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-29 07:55+0000\n"
"PO-Revision-Date: 2025-10-31 17:00+0000\n"
"PO-Revision-Date: 2025-10-28 17:00+0000\n"
"Last-Translator: Núria Masclans <nuriamasclansserrat@gmail.com>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/"
"pretix-js/ca/>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
"js/ca/>\n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -651,8 +651,6 @@ msgid ""
"Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted."
msgstr ""
"El color no té prou contrast amb el blanc i pot afectar a l'accessibilitat "
"del lloc web."
#: pretix/static/pretixcontrol/js/ui/main.js:418
#: pretix/static/pretixcontrol/js/ui/main.js:438
@@ -714,226 +712,229 @@ msgstr[0] "(una data més)"
msgstr[1] "({num} dates més)"
#: pretix/static/pretixpresale/js/ui/cart.js:47
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"Ja no tens reservat el contingut de la cistella. Encara pots completar la "
"comanda si els articles seleccionats continuen disponibles."
msgstr "El contingut de la cistella ja no el teniu reservat."
#: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired"
msgstr "Cistella caducada"
msgstr "Cistella expirada"
#: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire."
msgstr "La teva cistella està a punt de caducar."
msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:62
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "El contingut de la teva cistella està reservat durant un minut."
msgstr[1] "El contingut de la teva cistella està reservat durant {num} minuts."
msgstr[0] "El contingut de la cistella ja no el teniu reservat."
msgstr[1] "El contingut de la cistella ja no el teniu reservat."
#: pretix/static/pretixpresale/js/ui/cart.js:83
#, fuzzy
#| msgid "Cart expired"
msgid "Your cart has expired."
msgstr "La teva cistella ha caducat."
msgstr "Cistella expirada"
#: pretix/static/pretixpresale/js/ui/cart.js:86
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as they're available."
msgstr ""
"Ja no teniu reservat el contingut de la cistella. Encara podeu completar la "
"comanda si els articles continuen disponibles."
msgstr "El contingut de la cistella ja no el teniu reservat."
#: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?"
msgstr "Vols ampliar el temps de reserva?"
msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation"
msgstr "Renovar reserva"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:194
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "L'entitat organitzadora es queda %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:202
msgid "You get %(currency)s %(amount)s back"
msgstr "Rebràs %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:218
msgid "Please enter the amount the organizer can keep."
msgstr "Introdueix limport que es queda lorganitzador."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Your local time:"
msgstr "La teva hora local:"
msgstr ""
#: pretix/static/pretixpresale/js/walletdetection.js:39
#, fuzzy
msgid "Google Pay"
msgstr "Google Pay"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:16
msgctxt "widget"
msgid "Quantity"
msgstr "Quantitat"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:17
msgctxt "widget"
msgid "Decrease quantity"
msgstr "Disminueix quantitat"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:18
msgctxt "widget"
msgid "Increase quantity"
msgstr "Augmenta quantitat"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget"
msgid "Filter events by"
msgstr "Filtra els esdeveniments per"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget"
msgid "Filter"
msgstr "Filtra"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget"
msgid "Price"
msgstr "Preu"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:22
#, javascript-format
msgctxt "widget"
msgid "Original price: %s"
msgstr "Preu original: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:23
#, javascript-format
msgctxt "widget"
msgid "New price: %s"
msgstr "Nou preu: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:24
msgctxt "widget"
msgid "Select"
msgstr "Selecciona"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:25
#, javascript-format
msgctxt "widget"
msgid "Select %s"
msgstr "Selecciona %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:26
#, javascript-format
msgctxt "widget"
msgid "Select variant %s"
msgstr "Selecciona la variant %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:27
msgctxt "widget"
msgid "Sold out"
msgstr "Esgotat"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:28
msgctxt "widget"
msgid "Buy"
msgstr "Compra"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:29
msgctxt "widget"
msgid "Register"
msgstr "Registrar-vos"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:30
msgctxt "widget"
msgid "Reserved"
msgstr "Reservat"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:31
msgctxt "widget"
msgid "FREE"
msgstr "GRATUÏT"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget"
msgid "from %(currency)s %(price)s"
msgstr "des de %(price)s %(currency)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:33
#, javascript-format
msgctxt "widget"
msgid "Image of %s"
msgstr "Imatge de %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget"
msgid "incl. %(rate)s% %(taxname)s"
msgstr "Inclòs %(rate)s% %(taxname)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget"
msgid "plus %(rate)s% %(taxname)s"
msgstr "més %(rate)s% %(taxname)s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget"
msgid "incl. taxes"
msgstr "incl. impostos"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:37
msgctxt "widget"
msgid "plus taxes"
msgstr "més impostos"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:38
#, javascript-format
msgctxt "widget"
msgid "currently available: %s"
msgstr "disponibles: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget"
msgid "Only available with a voucher"
msgstr "Només disponible amb un cupó"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget"
msgid "Not yet available"
msgstr "Encara no disponible"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget"
msgid "Not available anymore"
msgstr "Ja no està disponible"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget"
msgid "Currently not available"
msgstr "No disponible"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:44
#, javascript-format
msgctxt "widget"
msgid "minimum amount to order: %s"
msgstr "Comanda mínima: %s"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"
msgid "Close ticket shop"
msgstr "Tanca la botiga d'entrades"
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget"
msgid "The ticket shop could not be loaded."
msgstr "No s'ha pogut carregar la botiga d'entrades."
msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget"

View File

@@ -5,7 +5,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
"PO-Revision-Date: 2025-10-30 20:00+0000\n"
"PO-Revision-Date: 2025-10-29 09:24+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
">\n"
@@ -9664,15 +9664,15 @@ msgstr "Der Gutschein wurde an {recipient} verschickt."
#: pretix/base/settings.py:81
msgid "Compute taxes for every line individually"
msgstr "Berechne Steuern für jede Position einzeln"
msgstr ""
#: pretix/base/settings.py:82
msgid "Compute taxes based on net total"
msgstr "Berechne Steuern auf Basis der Nettosumme"
msgstr ""
#: pretix/base/settings.py:83
msgid "Compute taxes based on net total with stable gross prices"
msgstr "Berechne Steuern auf Basis der Nettosumme mit konstanten Bruttopreisen"
msgstr ""
#: pretix/base/settings.py:133
msgid "Allow usage of restricted plugins"
@@ -9762,8 +9762,13 @@ msgid "Add-on products will not be counted."
msgstr "Zusatzprodukte werden nicht mitgezählt."
#: pretix/base/settings.py:334
#, fuzzy
#| msgid ""
#| "Show net prices instead of gross prices in the product list (not "
#| "recommended!)"
msgid "Show net prices instead of gross prices in the product list"
msgstr "Zeigen Nettopreise statt Bruttopreisen in der Produktliste"
msgstr ""
"Zeige Netto- statt Bruttopreisen in den Produktlisten (nicht empfohlen)"
#: pretix/base/settings.py:335
msgid ""
@@ -9880,8 +9885,10 @@ msgid "Require a phone number per order"
msgstr "Telefonnummer pro Bestellung erfordern"
#: pretix/base/settings.py:481
#, fuzzy
#| msgid "including all taxes"
msgid "Rounding of taxes"
msgstr "Rundung der Steuern"
msgstr "inklusive aller Steuern"
#: pretix/base/settings.py:485
msgid ""
@@ -9889,10 +9896,6 @@ msgid ""
"for tax reporting, you need to make sure to account for possible rounding "
"differences if your external system rounds differently than pretix."
msgstr ""
"Bitte beachten Sie, dass bei der Übertragung der Verkaufsdaten in ein "
"externes Buchhaltungssystem darauf geachtet werden muss, dass potentielle "
"Rundungsabweichungen behandelt werden, wenn das externe System anders rundet "
"als pretix."
#: pretix/base/settings.py:500
msgid "Ask for invoice address"
@@ -14034,24 +14037,31 @@ msgstr ""
"Ausstellung ablaufen."
#: pretix/control/forms/event.py:806
#, fuzzy
#| msgid "including all taxes"
msgid "Prices including tax"
msgstr "Preis inklusive Steuern"
msgstr "inklusive aller Steuern"
#: pretix/control/forms/event.py:807
msgid "Recommended if you sell tickets at least partly to consumers."
msgstr "Empfohlen, wenn mindestens teilweise an Verbraucher verkauft wird."
msgstr ""
#: pretix/control/forms/event.py:811
#, fuzzy
#| msgctxt "reporting_timeframe"
#| msgid "All future (excluding today)"
msgid "Prices excluding tax"
msgstr "Preise exklusive Steuern"
msgstr "Zukunft (ab morgen)"
#: pretix/control/forms/event.py:812
msgid "Recommended only if you sell tickets primarily to business customers."
msgstr "Empfohlen, wenn primär an Geschäftskunden verkauft wird."
msgstr ""
#: pretix/control/forms/event.py:848
#, fuzzy
#| msgid "Date chosen by customer"
msgid "Prices shown to customer"
msgstr "Preisanzeige für Kunden"
msgstr "Wunschdatum des Kunden"
#: pretix/control/forms/event.py:852
msgid ""
@@ -14060,10 +14070,6 @@ msgid ""
"product, the total tax amount can differ from when it would be computed from "
"the order total."
msgstr ""
"Empfohlen, wenn keine E-Rechnungen benötigt werden. Jedes Produkt wird zum "
"beworbenen Netto- und Bruttopreis verkauft. Im Fall von Bestellungen mit "
"mehreren Produkten kann die Gesamt-Steuersumme höher oder niedriger sein als "
"die, die sich aus der Bestellsumme berechnen würde."
#: pretix/control/forms/event.py:857
msgid ""
@@ -14072,11 +14078,6 @@ msgid ""
"may be changed to ensure correct rounding, while the net prices will be kept "
"as configured. This may cause the actual payment amount to differ."
msgstr ""
"Empfohlen für E-Rechnungen, wenn primär an Geschäftskunden verkauft wird und "
"Kunden Nettopreise angezeigt werden. Der Bruttopreis einzelner Produkte kann "
"sich automatisch ändern, um ein korrektes Rundungsverhalten sicherzustellen, "
"während die Nettopreise immer den eingestellten Preisen entsprechen. Der "
"resultierende Zahlungsbetrag kann durch die Rundung abweichen."
#: pretix/control/forms/event.py:863
msgid ""
@@ -14086,13 +14087,6 @@ msgid ""
"configured whenever possible. Gross prices may still change if they are "
"impossible to derive from a rounded net price."
msgstr ""
"Empfohlen für E-Rechnungen, wenn primär an Verbraucher verkauft wird und "
"Kunden Bruttopreise angezeigt werden. Der Netto- oder Bruttopreis einzelner "
"Produkte kann sich automatisch ändern, um ein korrektes Rundungsverhalten "
"sicherzustellen. Dabei versucht das System, die Bruttopreise möglichst bei "
"den eingestellten Preisen zu belassen. Die Bruttopreise können sich dennoch "
"ändern, wenn sie durch Errechnung von einem gerundeten Nettopreis nicht "
"erreichbar sind."
#: pretix/control/forms/event.py:961
msgid "Generate invoices for Sales channels"
@@ -21284,10 +21278,6 @@ msgid ""
"optionally contain additional rules that depend on the customer's country "
"and type."
msgstr ""
"Steuer-Regeln definieren unterschiedliche Besteuerungsszenarien, die dann "
"den verschiedenen Produkten zugewiesen werden können. Jede Steuer-Regel "
"enthält einen Standard-Steuersatz und kann optional zusätzliche Regeln "
"enthalten, die auf die Art und Herkunft des Kunden Bezug nehmen."
#: pretix/control/templates/pretixcontrol/event/tax.html:21
msgid "You haven't created any tax rules yet."
@@ -21314,11 +21304,13 @@ msgid "Make default"
msgstr "Zum Standard machen"
#: pretix/control/templates/pretixcontrol/event/tax.html:67
#, python-format
#, fuzzy, python-format
#| msgid "%(count)s event"
#| msgid_plural "%(count)s events"
msgid "%(count)s product"
msgid_plural "%(count)s products"
msgstr[0] "%(count)s Produkt"
msgstr[1] "%(count)s Produkte"
msgstr[0] "%(count)s Veranstaltung"
msgstr[1] "%(count)s Veranstaltungen"
#: pretix/control/templates/pretixcontrol/event/tax.html:75
#, python-format
@@ -21331,12 +21323,16 @@ msgid "excl. %(rate)s %%"
msgstr "zzgl. %(rate)s %%"
#: pretix/control/templates/pretixcontrol/event/tax.html:80
#, fuzzy
#| msgid "Custom rules"
msgid "with custom rules"
msgstr "mit eigenen Regeln"
msgstr "Eigene Regeln"
#: pretix/control/templates/pretixcontrol/event/tax.html:110
#, fuzzy
#| msgid "Base settings"
msgid "Tax settings"
msgstr "Steuer-Einstellungen"
msgstr "Grundeinstellungen"
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
@@ -24153,7 +24149,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
#, python-format
msgid "incl. %(amount)s rounding correction"
msgstr "inkl. %(amount)s Rundungskorrektur"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7

View File

@@ -362,9 +362,6 @@ Reverse-Proxy
Revisionssicherheit
Revolut
Revolut-App
Rundungsabweichungen
Rundungskorrektur
Rundungsverhalten
rückabgewickelt
Sa
Sammlungsstücken

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
"PO-Revision-Date: 2025-10-30 20:00+0000\n"
"PO-Revision-Date: 2025-10-29 09:24+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix/de_Informal/>\n"
@@ -9651,15 +9651,15 @@ msgstr "Der Gutschein wurde an {recipient} verschickt."
#: pretix/base/settings.py:81
msgid "Compute taxes for every line individually"
msgstr "Berechne Steuern für jede Position einzeln"
msgstr ""
#: pretix/base/settings.py:82
msgid "Compute taxes based on net total"
msgstr "Berechne Steuern auf Basis der Nettosumme"
msgstr ""
#: pretix/base/settings.py:83
msgid "Compute taxes based on net total with stable gross prices"
msgstr "Berechne Steuern auf Basis der Nettosumme mit konstanten Bruttopreisen"
msgstr ""
#: pretix/base/settings.py:133
msgid "Allow usage of restricted plugins"
@@ -9749,8 +9749,13 @@ msgid "Add-on products will not be counted."
msgstr "Zusatzprodukte werden nicht mitgezählt."
#: pretix/base/settings.py:334
#, fuzzy
#| msgid ""
#| "Show net prices instead of gross prices in the product list (not "
#| "recommended!)"
msgid "Show net prices instead of gross prices in the product list"
msgstr "Zeigen Nettopreise statt Bruttopreisen in der Produktliste"
msgstr ""
"Zeige Netto- statt Bruttopreisen in den Produktlisten (nicht empfohlen)"
#: pretix/base/settings.py:335
msgid ""
@@ -9867,8 +9872,10 @@ msgid "Require a phone number per order"
msgstr "Telefonnummer pro Bestellung erfordern"
#: pretix/base/settings.py:481
#, fuzzy
#| msgid "including all taxes"
msgid "Rounding of taxes"
msgstr "Rundung der Steuern"
msgstr "inklusive aller Steuern"
#: pretix/base/settings.py:485
msgid ""
@@ -9876,10 +9883,6 @@ msgid ""
"for tax reporting, you need to make sure to account for possible rounding "
"differences if your external system rounds differently than pretix."
msgstr ""
"Bitte beachte, dass bei der Übertragung der Verkaufsdaten in ein externes "
"Buchhaltungssystem darauf geachtet werden muss, dass potentielle "
"Rundungsabweichungen behandelt werden, wenn das externe System anders rundet "
"als pretix."
#: pretix/base/settings.py:500
msgid "Ask for invoice address"
@@ -14010,24 +14013,31 @@ msgstr ""
"Ausstellung ablaufen."
#: pretix/control/forms/event.py:806
#, fuzzy
#| msgid "including all taxes"
msgid "Prices including tax"
msgstr "Preis inklusive Steuern"
msgstr "inklusive aller Steuern"
#: pretix/control/forms/event.py:807
msgid "Recommended if you sell tickets at least partly to consumers."
msgstr "Empfohlen, wenn mindestens teilweise an Verbraucher verkauft wird."
msgstr ""
#: pretix/control/forms/event.py:811
#, fuzzy
#| msgctxt "reporting_timeframe"
#| msgid "All future (excluding today)"
msgid "Prices excluding tax"
msgstr "Preise exklusive Steuern"
msgstr "Zukunft (ab morgen)"
#: pretix/control/forms/event.py:812
msgid "Recommended only if you sell tickets primarily to business customers."
msgstr "Empfohlen, wenn primär an Geschäftskunden verkauft wird."
msgstr ""
#: pretix/control/forms/event.py:848
#, fuzzy
#| msgid "Date chosen by customer"
msgid "Prices shown to customer"
msgstr "Preisanzeige für Kunden"
msgstr "Wunschdatum des Kunden"
#: pretix/control/forms/event.py:852
msgid ""
@@ -14036,10 +14046,6 @@ msgid ""
"product, the total tax amount can differ from when it would be computed from "
"the order total."
msgstr ""
"Empfohlen, wenn keine E-Rechnungen benötigt werden. Jedes Produkt wird zum "
"beworbenen Netto- und Bruttopreis verkauft. Im Fall von Bestellungen mit "
"mehreren Produkten kann die Gesamt-Steuersumme höher oder niedriger sein als "
"die, die sich aus der Bestellsumme berechnen würde."
#: pretix/control/forms/event.py:857
msgid ""
@@ -14048,11 +14054,6 @@ msgid ""
"may be changed to ensure correct rounding, while the net prices will be kept "
"as configured. This may cause the actual payment amount to differ."
msgstr ""
"Empfohlen für E-Rechnungen, wenn primär an Geschäftskunden verkauft wird und "
"Kunden Nettopreise angezeigt werden. Der Bruttopreis einzelner Produkte kann "
"sich automatisch ändern, um ein korrektes Rundungsverhalten sicherzustellen, "
"während die Nettopreise immer den eingestellten Preisen entsprechen. Der "
"resultierende Zahlungsbetrag kann durch die Rundung abweichen."
#: pretix/control/forms/event.py:863
msgid ""
@@ -14062,13 +14063,6 @@ msgid ""
"configured whenever possible. Gross prices may still change if they are "
"impossible to derive from a rounded net price."
msgstr ""
"Empfohlen für E-Rechnungen, wenn primär an Verbraucher verkauft wird und "
"Kunden Bruttopreise angezeigt werden. Der Netto- oder Bruttopreis einzelner "
"Produkte kann sich automatisch ändern, um ein korrektes Rundungsverhalten "
"sicherzustellen. Dabei versucht das System, die Bruttopreise möglichst bei "
"den eingestellten Preisen zu belassen. Die Bruttopreise können sich dennoch "
"ändern, wenn sie durch Errechnung von einem gerundeten Nettopreis nicht "
"erreichbar sind."
#: pretix/control/forms/event.py:961
msgid "Generate invoices for Sales channels"
@@ -21254,10 +21248,6 @@ msgid ""
"optionally contain additional rules that depend on the customer's country "
"and type."
msgstr ""
"Steuer-Regeln definieren unterschiedliche Besteuerungsszenarien, die dann "
"den verschiedenen Produkten zugewiesen werden können. Jede Steuer-Regel "
"enthält einen Standard-Steuersatz und kann optional zusätzliche Regeln "
"enthalten, die auf die Art und Herkunft des Kunden Bezug nehmen."
#: pretix/control/templates/pretixcontrol/event/tax.html:21
msgid "You haven't created any tax rules yet."
@@ -21284,11 +21274,13 @@ msgid "Make default"
msgstr "Zum Standard machen"
#: pretix/control/templates/pretixcontrol/event/tax.html:67
#, python-format
#, fuzzy, python-format
#| msgid "%(count)s event"
#| msgid_plural "%(count)s events"
msgid "%(count)s product"
msgid_plural "%(count)s products"
msgstr[0] "%(count)s Produkt"
msgstr[1] "%(count)s Produkte"
msgstr[0] "%(count)s Veranstaltung"
msgstr[1] "%(count)s Veranstaltungen"
#: pretix/control/templates/pretixcontrol/event/tax.html:75
#, python-format
@@ -21301,12 +21293,16 @@ msgid "excl. %(rate)s %%"
msgstr "zzgl. %(rate)s %%"
#: pretix/control/templates/pretixcontrol/event/tax.html:80
#, fuzzy
#| msgid "Custom rules"
msgid "with custom rules"
msgstr "mit eigenen Regeln"
msgstr "Eigene Regeln"
#: pretix/control/templates/pretixcontrol/event/tax.html:110
#, fuzzy
#| msgid "Base settings"
msgid "Tax settings"
msgstr "Steuer-Einstellungen"
msgstr "Grundeinstellungen"
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
@@ -24120,7 +24116,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
#, python-format
msgid "incl. %(amount)s rounding correction"
msgstr "inkl. %(amount)s Rundungskorrektur"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7

View File

@@ -362,9 +362,6 @@ Reverse-Proxy
Revisionssicherheit
Revolut
Revolut-App
Rundungsabweichungen
Rundungskorrektur
Rundungsverhalten
rückabgewickelt
Sa
Sammlungsstücken

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
"PO-Revision-Date: 2025-11-04 10:25+0000\n"
"PO-Revision-Date: 2025-10-22 16:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix/"
"es/>\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.14\n"
"X-Generator: Weblate 5.13.3\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -8346,8 +8346,10 @@ msgid "Event canceled"
msgstr "Evento cancelado"
#: pretix/base/services/cancelevent.py:392
#, fuzzy
#| msgid "Create configuration"
msgid "Bulk-refund confirmation"
msgstr "Confirmación de reembolso masivo"
msgstr "Crear configuración"
#: pretix/base/services/cart.py:102 pretix/base/services/modelimport.py:247
#: pretix/base/services/orders.py:155
@@ -9653,16 +9655,15 @@ msgstr "El vale de compra ha sido enviado a {recipient}."
#: pretix/base/settings.py:81
msgid "Compute taxes for every line individually"
msgstr "Calcular los impuestos para cada línea individualmente"
msgstr ""
#: pretix/base/settings.py:82
msgid "Compute taxes based on net total"
msgstr "Calcular los impuestos basándose en el total neto"
msgstr ""
#: pretix/base/settings.py:83
msgid "Compute taxes based on net total with stable gross prices"
msgstr ""
"Calcular los impuestos basándose en el total neto con precios brutos estables"
#: pretix/base/settings.py:133
msgid "Allow usage of restricted plugins"
@@ -9754,9 +9755,14 @@ msgid "Add-on products will not be counted."
msgstr "Los productos adicionales no se contarán."
#: pretix/base/settings.py:334
#, fuzzy
#| msgid ""
#| "Show net prices instead of gross prices in the product list (not "
#| "recommended!)"
msgid "Show net prices instead of gross prices in the product list"
msgstr ""
"Mostrar precios netos en lugar de precios brutos en la lista de productos"
"Mostrar precios netos en lugar de precios brutos en la lista de productos "
"(¡no recomendado!)"
#: pretix/base/settings.py:335
msgid ""
@@ -9877,8 +9883,10 @@ msgid "Require a phone number per order"
msgstr "Requerir un número de teléfono por pedido"
#: pretix/base/settings.py:481
#, fuzzy
#| msgid "including all taxes"
msgid "Rounding of taxes"
msgstr "Redondeo de impuestos"
msgstr "impuestos incluidos"
#: pretix/base/settings.py:485
msgid ""
@@ -9886,10 +9894,6 @@ msgid ""
"for tax reporting, you need to make sure to account for possible rounding "
"differences if your external system rounds differently than pretix."
msgstr ""
"Tenga en cuenta que si transfiere sus datos de ventas desde pretix a un "
"sistema externo para la declaración de impuestos, debe asegurarse de tener "
"en cuenta las posibles diferencias de redondeo si su sistema externo "
"redondea de forma diferente a pretix."
#: pretix/base/settings.py:500
msgid "Ask for invoice address"
@@ -13160,20 +13164,18 @@ msgstr ""
msgid ""
"You have requested us to cancel an event which includes a larger bulk-refund:"
msgstr ""
"Nos ha solicitado cancelar un evento que incluye un reembolso masivo de "
"mayor importe:"
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:6
#, fuzzy
#| msgid "Initiate a refund of %(amount)s"
msgid "Estimated refund amount"
msgstr "Importe estimado del reembolso"
msgstr "Iniciar un reembolso de %(amount)s"
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:8
msgid ""
"Please confirm that you want to proceed by coping the following confirmation "
"code into the cancellation form:"
msgstr ""
"Confirme que desea continuar copiando el siguiente código de confirmación en "
"el formulario de cancelación:"
#: pretix/base/templates/pretixbase/email/email_footer.html:3
#, python-format
@@ -14015,25 +14017,31 @@ msgstr ""
"año en el que se emite la tarjeta de regalo."
#: pretix/control/forms/event.py:806
#, fuzzy
#| msgid "including all taxes"
msgid "Prices including tax"
msgstr "Precios con impuestos incluidos"
msgstr "impuestos incluidos"
#: pretix/control/forms/event.py:807
msgid "Recommended if you sell tickets at least partly to consumers."
msgstr "Recomendado si se venden entradas, al menos en parte, a consumidores."
msgstr ""
#: pretix/control/forms/event.py:811
#, fuzzy
#| msgctxt "reporting_timeframe"
#| msgid "All future (excluding today)"
msgid "Prices excluding tax"
msgstr "Precios sin impuestos"
msgstr "Todo el futuro (excepto hoy)"
#: pretix/control/forms/event.py:812
msgid "Recommended only if you sell tickets primarily to business customers."
msgstr ""
"Recomendado solo si vende entradas principalmente a clientes empresariales."
#: pretix/control/forms/event.py:848
#, fuzzy
#| msgid "Date chosen by customer"
msgid "Prices shown to customer"
msgstr "Precios mostrados a los clientes"
msgstr "Fecha elegida por el cliente"
#: pretix/control/forms/event.py:852
msgid ""
@@ -14042,10 +14050,6 @@ msgid ""
"product, the total tax amount can differ from when it would be computed from "
"the order total."
msgstr ""
"Recomendado cuando no se requiere facturación electrónica. Cada producto se "
"venderá con el precio neto y bruto anunciado. Sin embargo, en pedidos de más "
"de un producto, el importe total de los impuestos puede diferir del que se "
"calcularía a partir del total del pedido."
#: pretix/control/forms/event.py:857
msgid ""
@@ -14054,12 +14058,6 @@ msgid ""
"may be changed to ensure correct rounding, while the net prices will be kept "
"as configured. This may cause the actual payment amount to differ."
msgstr ""
"Recomendado para la facturación electrónica cuando se vende principalmente a "
"clientes empresariales y se muestran los precios a los clientes sin "
"impuestos. El precio bruto de algunos productos puede modificarse para "
"garantizar un redondeo correcto, mientras que los precios netos se "
"mantendrán tal y como están configurados. Esto puede provocar que el importe "
"real del pago sea diferente."
#: pretix/control/forms/event.py:863
msgid ""
@@ -14069,12 +14067,6 @@ msgid ""
"configured whenever possible. Gross prices may still change if they are "
"impossible to derive from a rounded net price."
msgstr ""
"Recomendado para la facturación electrónica cuando se vende principalmente a "
"consumidores. El precio bruto o neto de algunos productos puede modificarse "
"automáticamente para garantizar el redondeo correcto del total del pedido. "
"El sistema intenta mantener los precios brutos tal y como se han configurado "
"siempre que sea posible. Los precios brutos pueden seguir cambiando si no es "
"posible obtenerlos a partir de un precio neto redondeado."
#: pretix/control/forms/event.py:961
msgid "Generate invoices for Sales channels"
@@ -16193,7 +16185,7 @@ msgstr "Confirme que desea cancelar TODAS las fechas de esta serie de eventos."
#: pretix/control/forms/orders.py:1037
msgid "I understand that this is not reversible and want to continue"
msgstr "Entiendo que esto no es reversible y deseo continuar"
msgstr ""
#: pretix/control/forms/orders.py:1041
#: pretix/control/templates/pretixcontrol/shredder/download.html:53
@@ -16204,12 +16196,12 @@ msgstr "Código de confirmación"
msgid ""
"We have just emailed you a confirmation code to enter to confirm this action"
msgstr ""
"Acabamos de enviarle por correo electrónico un código de confirmación que "
"deberá introducir para confirmar esta acción"
#: pretix/control/forms/orders.py:1055
#, fuzzy
#| msgid "The confirm code you entered was incorrect."
msgid "The confirmation code is incorrect."
msgstr "El código de confirmación es incorrecto."
msgstr "El código de confirmación que introdujo era incorrecto."
#: pretix/control/forms/organizer.py:93
msgid "This slug is already in use. Please choose a different one."
@@ -21276,10 +21268,6 @@ msgid ""
"optionally contain additional rules that depend on the customer's country "
"and type."
msgstr ""
"Las normas fiscales definen diferentes escenarios fiscales que luego se "
"pueden asignar a los productos individuales. Cada norma fiscal contiene un "
"tipo impositivo predeterminado y, opcionalmente, puede contener normas "
"adicionales que dependen del país y el tipo de cliente."
#: pretix/control/templates/pretixcontrol/event/tax.html:21
msgid "You haven't created any tax rules yet."
@@ -21306,11 +21294,13 @@ msgid "Make default"
msgstr "Establecer por defecto"
#: pretix/control/templates/pretixcontrol/event/tax.html:67
#, python-format
#, fuzzy, python-format
#| msgid "%(count)s event"
#| msgid_plural "%(count)s events"
msgid "%(count)s product"
msgid_plural "%(count)s products"
msgstr[0] "%(count)s producto"
msgstr[1] "%(count)s productos"
msgstr[0] "%(count)s elemento"
msgstr[1] "%(count)s elementos"
#: pretix/control/templates/pretixcontrol/event/tax.html:75
#, python-format
@@ -21323,12 +21313,16 @@ msgid "excl. %(rate)s %%"
msgstr "excluido %(rate)s %%"
#: pretix/control/templates/pretixcontrol/event/tax.html:80
#, fuzzy
#| msgid "Custom rules"
msgid "with custom rules"
msgstr "con reglas personalizadas"
msgstr "Reglas personalizadas"
#: pretix/control/templates/pretixcontrol/event/tax.html:110
#, fuzzy
#| msgid "Base settings"
msgid "Tax settings"
msgstr "Configuración de impuestos"
msgstr "Ajustes básicos"
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
@@ -24147,7 +24141,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
#, python-format
msgid "incl. %(amount)s rounding correction"
msgstr "incl. %(amount)s corrección por redondeo"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
@@ -24253,77 +24247,84 @@ msgstr ""
"los que estén en la lista de espera recibirán varios correos electrónicos."
#: pretix/control/templates/pretixcontrol/orders/cancel.html:84
#, fuzzy
#| msgid "Refund amount"
msgid "Preview refund amount"
msgstr "Previsualizar el importe del reembolso"
msgstr "Importe del reembolso"
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
msgid "Cancel all orders"
msgstr "Cancelar todos los pedidos"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
#, fuzzy
#| msgid "If you contact us, please send us the following code:"
msgid "If you proceed, the system will do the following:"
msgstr "Si continúa, el sistema hará lo siguiente:"
msgstr ""
"Si se pone en contacto con nosotros, por favor envíenos el siguiente código:"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:19
#, python-format
msgid "%(count)s order will be canceled fully"
msgid_plural "%(count)s orders will be canceled fully"
msgstr[0] "%(count)s pedido se cancelará por completo"
msgstr[1] "%(count)s pedidos se cancelarán por completo"
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:26
#, python-format
msgid "%(count)s order will be canceled partially"
msgid_plural "%(count)s orders will be canceled partially"
msgstr[0] "%(count)s pedido se cancelará parcialmente"
msgstr[1] "%(count)s pedidos se cancelarán parcialmente"
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:36
#, python-format
msgid "%(amount)s are eligible for refunds."
msgstr "%(amount)s son reembolsables."
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:40
msgid ""
"The system will attempt to refund the money automatically if supported by "
"the payment method."
msgstr ""
"El sistema intentará reembolsar el dinero automáticamente si el método de "
"pago lo permite."
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:42
msgid "The system will create manual refunds that you need to execute."
msgstr "El sistema creará reembolsos manuales que deberá ejecutar."
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:44
#, fuzzy
#| msgid "Text (if order will not expire automatically)"
msgid "Refunds will not happen automatically."
msgstr "Los reembolsos no se realizarán automáticamente."
msgstr "Texto (si el pedido no caducará automáticamente)"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:52
#, fuzzy
#| msgid "Send information via email"
msgid "Inform all customers via email."
msgstr "Informar a todos los clientes por correo electrónico."
msgstr "Enviar información por correo electrónico"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:57
msgid "Inform all waiting list contacts via email."
msgstr ""
"Informar a todos los contactos de la lista de espera por correo electrónico."
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:62
msgid ""
"These numbers are estimates and may change if the data in your event "
"recently changed."
msgstr ""
"Estas cifras son estimaciones y pueden variar si los datos del evento han "
"cambiado recientemente."
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:76
#, python-format
#, fuzzy, python-format
#| msgid "Initiate a refund of %(amount)s"
msgid "Proceed and refund approx. %(amount)s"
msgstr "Proseguir y reembolsar aprox. %(amount)s"
msgstr "Iniciar un reembolso de %(amount)s"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:80
#, fuzzy
#| msgid "Yes, cancel order"
msgid "Proceed and cancel orders"
msgstr "Proceder y cancelar pedidos"
msgstr "Sí, cancelar pedido"
#: pretix/control/templates/pretixcontrol/orders/export.html:5
#: pretix/control/templates/pretixcontrol/orders/export.html:8

View File

@@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
"PO-Revision-Date: 2025-11-04 10:25+0000\n"
"PO-Revision-Date: 2025-10-22 16:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
">\n"
@@ -13,7 +13,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.14\n"
"X-Generator: Weblate 5.13.3\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -8397,8 +8397,10 @@ msgid "Event canceled"
msgstr "Événement annulé"
#: pretix/base/services/cancelevent.py:392
#, fuzzy
#| msgid "Create configuration"
msgid "Bulk-refund confirmation"
msgstr "Confirmation de remboursement en bloc"
msgstr "Créer une configuration"
#: pretix/base/services/cart.py:102 pretix/base/services/modelimport.py:247
#: pretix/base/services/orders.py:155
@@ -9718,15 +9720,15 @@ msgstr "Le bon a été envoyé à {recipient}."
#: pretix/base/settings.py:81
msgid "Compute taxes for every line individually"
msgstr "Calculer les taxes individuellement pour chaque ligne"
msgstr ""
#: pretix/base/settings.py:82
msgid "Compute taxes based on net total"
msgstr "Calculer les taxes sur la base du total net"
msgstr ""
#: pretix/base/settings.py:83
msgid "Compute taxes based on net total with stable gross prices"
msgstr "Calculer les taxes sur la base du total net avec des prix bruts stables"
msgstr ""
#: pretix/base/settings.py:133
msgid "Allow usage of restricted plugins"
@@ -9819,9 +9821,14 @@ msgid "Add-on products will not be counted."
msgstr "Les Add-Ons e sont pas comptabilisés."
#: pretix/base/settings.py:334
#, fuzzy
#| msgid ""
#| "Show net prices instead of gross prices in the product list (not "
#| "recommended!)"
msgid "Show net prices instead of gross prices in the product list"
msgstr ""
"Afficher les prix nets au lieu des prix bruts dans la liste des produits"
"Afficher les prix nets au lieu des prix bruts dans la liste de produits (pas "
"recommandé!)"
#: pretix/base/settings.py:335
msgid ""
@@ -9940,8 +9947,10 @@ msgid "Require a phone number per order"
msgstr "Exiger un numéro de téléphone par commande"
#: pretix/base/settings.py:481
#, fuzzy
#| msgid "including all taxes"
msgid "Rounding of taxes"
msgstr "Arrondi des taxes"
msgstr "toutes taxes comprises"
#: pretix/base/settings.py:485
msgid ""
@@ -9949,10 +9958,6 @@ msgid ""
"for tax reporting, you need to make sure to account for possible rounding "
"differences if your external system rounds differently than pretix."
msgstr ""
"Veuillez noter que si vous transférez vos données de vente depuis pretix "
"vers un système externe à des fins de déclaration fiscale, vous devez tenir "
"compte des éventuelles différences d'arrondi si votre système externe "
"arrondit différemment de pretix."
#: pretix/base/settings.py:500
msgid "Ask for invoice address"
@@ -13285,20 +13290,18 @@ msgstr ""
msgid ""
"You have requested us to cancel an event which includes a larger bulk-refund:"
msgstr ""
"Vous nous avez demandé d'annuler un événement qui implique un remboursement "
"groupé important :"
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:6
#, fuzzy
#| msgid "Initiate a refund of %(amount)s"
msgid "Estimated refund amount"
msgstr "Montant estimé du remboursement"
msgstr "Initier un remboursement de %(amount)s"
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:8
msgid ""
"Please confirm that you want to proceed by coping the following confirmation "
"code into the cancellation form:"
msgstr ""
"Veuillez confirmer que vous souhaitez poursuivre en copiant le code de "
"confirmation suivant dans le formulaire d'annulation :"
#: pretix/base/templates/pretixbase/email/email_footer.html:3
#, python-format
@@ -14148,27 +14151,31 @@ msgstr ""
"plus lannée démission de la carte-cadeau."
#: pretix/control/forms/event.py:806
#, fuzzy
#| msgid "including all taxes"
msgid "Prices including tax"
msgstr "Prix TTC"
msgstr "toutes taxes comprises"
#: pretix/control/forms/event.py:807
msgid "Recommended if you sell tickets at least partly to consumers."
msgstr ""
"Recommandé si vous vendez des billets au moins en partie à des consommateurs."
#: pretix/control/forms/event.py:811
#, fuzzy
#| msgctxt "reporting_timeframe"
#| msgid "All future (excluding today)"
msgid "Prices excluding tax"
msgstr "Prix hors taxes"
msgstr "Tout futur (sauf aujourdhui)"
#: pretix/control/forms/event.py:812
msgid "Recommended only if you sell tickets primarily to business customers."
msgstr ""
"Recommandé uniquement si vous vendez principalement des billets à des "
"clients professionnels."
#: pretix/control/forms/event.py:848
#, fuzzy
#| msgid "Date chosen by customer"
msgid "Prices shown to customer"
msgstr "Prix indiqués aux clients"
msgstr "Date choisie par le client"
#: pretix/control/forms/event.py:852
msgid ""
@@ -14177,11 +14184,6 @@ msgid ""
"product, the total tax amount can differ from when it would be computed from "
"the order total."
msgstr ""
"Recommandé lorsque la facturation électronique n'est pas requise. Chaque "
"produit sera vendu au prix net et brut annoncé. Cependant, dans le cas de "
"commandes comprenant plusieurs produits, le montant total des taxes peut "
"différer de celui qui serait calculé à partir du montant total de la "
"commande."
#: pretix/control/forms/event.py:857
msgid ""
@@ -14190,12 +14192,6 @@ msgid ""
"may be changed to ensure correct rounding, while the net prices will be kept "
"as configured. This may cause the actual payment amount to differ."
msgstr ""
"Recommandé pour la facturation électronique lorsque vous vendez "
"principalement à des clients professionnels et affichez les prix hors taxes "
"à vos clients. Le prix brut de certains produits peut être modifié afin "
"d'assurer un arrondi correct, tandis que les prix nets resteront tels qu'ils "
"ont été configurés. Cela peut entraîner une différence entre le montant réel "
"du paiement et le montant indiqué."
#: pretix/control/forms/event.py:863
msgid ""
@@ -14205,13 +14201,6 @@ msgid ""
"configured whenever possible. Gross prices may still change if they are "
"impossible to derive from a rounded net price."
msgstr ""
"Recommandé pour la facturation électronique lorsque vous vendez "
"principalement à des consommateurs. Le prix brut ou net de certains produits "
"peut être modifié automatiquement afin d'assurer un arrondi correct du total "
"de la commande. Le système tente de conserver les prix bruts tels qu'ils ont "
"été configurés dans la mesure du possible. Les prix bruts peuvent toutefois "
"être modifiés s'il est impossible de les calculer à partir d'un prix net "
"arrondi."
#: pretix/control/forms/event.py:961
msgid "Generate invoices for Sales channels"
@@ -16343,7 +16332,6 @@ msgstr ""
#: pretix/control/forms/orders.py:1037
msgid "I understand that this is not reversible and want to continue"
msgstr ""
"Je comprends que cette opération est irréversible et je souhaite continuer"
#: pretix/control/forms/orders.py:1041
#: pretix/control/templates/pretixcontrol/shredder/download.html:53
@@ -16354,12 +16342,12 @@ msgstr "Code de confirmation"
msgid ""
"We have just emailed you a confirmation code to enter to confirm this action"
msgstr ""
"Nous venons de vous envoyer par e-mail un code de confirmation à saisir pour "
"confirmer cette action"
#: pretix/control/forms/orders.py:1055
#, fuzzy
#| msgid "The confirm code you entered was incorrect."
msgid "The confirmation code is incorrect."
msgstr "Le code de confirmation est incorrect."
msgstr "Le code de confirmation que vous avez entré était incorrect."
#: pretix/control/forms/organizer.py:93
msgid "This slug is already in use. Please choose a different one."
@@ -21431,10 +21419,6 @@ msgid ""
"optionally contain additional rules that depend on the customer's country "
"and type."
msgstr ""
"Les règles fiscales définissent différents scénarios d'imposition qui "
"peuvent ensuite être attribués à chaque produit. Chaque règle fiscale "
"contient un taux d'imposition par défaut et peut éventuellement contenir des "
"règles supplémentaires qui dépendent du pays et du type de client."
#: pretix/control/templates/pretixcontrol/event/tax.html:21
msgid "You haven't created any tax rules yet."
@@ -21461,11 +21445,13 @@ msgid "Make default"
msgstr "Définir par défaut"
#: pretix/control/templates/pretixcontrol/event/tax.html:67
#, python-format
#, fuzzy, python-format
#| msgid "%(count)s event"
#| msgid_plural "%(count)s events"
msgid "%(count)s product"
msgid_plural "%(count)s products"
msgstr[0] "%(count)s produit"
msgstr[1] "%(count)s produits"
msgstr[0] "%(count)s élement"
msgstr[1] "%(count)s élements"
#: pretix/control/templates/pretixcontrol/event/tax.html:75
#, python-format
@@ -21478,12 +21464,16 @@ msgid "excl. %(rate)s %%"
msgstr "hors %(rate)s %%"
#: pretix/control/templates/pretixcontrol/event/tax.html:80
#, fuzzy
#| msgid "Custom rules"
msgid "with custom rules"
msgstr "avec des règles personnalisées"
msgstr "Règles personnalisées"
#: pretix/control/templates/pretixcontrol/event/tax.html:110
#, fuzzy
#| msgid "Base settings"
msgid "Tax settings"
msgstr "Paramètres relatifs aux taxes"
msgstr "Réglages de base"
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
@@ -24324,7 +24314,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
#, python-format
msgid "incl. %(amount)s rounding correction"
msgstr "y compris %(amount)s de correction d'arrondi"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
@@ -24435,77 +24425,83 @@ msgstr ""
"plusieurs courriels."
#: pretix/control/templates/pretixcontrol/orders/cancel.html:84
#, fuzzy
#| msgid "Refund amount"
msgid "Preview refund amount"
msgstr "Prévisualiser le montant du remboursement"
msgstr "Montant du remboursement"
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
msgid "Cancel all orders"
msgstr "Annuler toutes les commandes"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
#, fuzzy
#| msgid "If you contact us, please send us the following code:"
msgid "If you proceed, the system will do the following:"
msgstr "Si vous continuez, le système effectuera les opérations suivantes :"
msgstr "Si vous nous contactez, veuillez nous envoyer le code suivant:"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:19
#, python-format
msgid "%(count)s order will be canceled fully"
msgid_plural "%(count)s orders will be canceled fully"
msgstr[0] "%(count)s commande sera totalement annulée"
msgstr[1] "%(count)s commandes seront totalement annulées"
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:26
#, python-format
msgid "%(count)s order will be canceled partially"
msgid_plural "%(count)s orders will be canceled partially"
msgstr[0] "%(count)s commande sera partiellement annulé"
msgstr[1] "%(count)s commandes seront partiellement annulées"
msgstr[0] ""
msgstr[1] ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:36
#, python-format
msgid "%(amount)s are eligible for refunds."
msgstr "%(amount)s sont susceptibles d'être remboursés."
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:40
msgid ""
"The system will attempt to refund the money automatically if supported by "
"the payment method."
msgstr ""
"Le système tentera de rembourser automatiquement l'argent si le mode de "
"paiement le permet."
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:42
msgid "The system will create manual refunds that you need to execute."
msgstr "Le système créera des remboursements manuels que vous devrez exécuter."
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:44
#, fuzzy
#| msgid "Text (if order will not expire automatically)"
msgid "Refunds will not happen automatically."
msgstr "Les remboursements ne se feront pas automatiquement."
msgstr "Texte (si la commande nexpire pas automatiquement)"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:52
#, fuzzy
#| msgid "Send information via email"
msgid "Inform all customers via email."
msgstr "Informez tous les clients par e-mail."
msgstr "Envoyer des informations par e-mail"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:57
msgid "Inform all waiting list contacts via email."
msgstr ""
"Informez toutes les personnes inscrites sur la liste d'attente par e-mail."
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:62
msgid ""
"These numbers are estimates and may change if the data in your event "
"recently changed."
msgstr ""
"Ces chiffres sont des estimations et peuvent changer si les données "
"relatives à votre événement ont récemment été modifiées."
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:76
#, python-format
#, fuzzy, python-format
#| msgid "Initiate a refund of %(amount)s"
msgid "Proceed and refund approx. %(amount)s"
msgstr "Poursuivre et rembourser environ %(amount)s"
msgstr "Initier un remboursement de %(amount)s"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:80
#, fuzzy
#| msgid "Yes, cancel order"
msgid "Proceed and cancel orders"
msgstr "Poursuivre et annuler les commandes"
msgstr "Oui, annuler la commande"
#: pretix/control/templates/pretixcontrol/orders/export.html:5
#: pretix/control/templates/pretixcontrol/orders/export.html:8

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-30 10:55+0000\n"
"PO-Revision-Date: 2025-11-04 10:25+0000\n"
"PO-Revision-Date: 2025-10-27 12:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
@@ -8190,8 +8190,10 @@ msgid "Event canceled"
msgstr "イベントがキャンセルされました"
#: pretix/base/services/cancelevent.py:392
#, fuzzy
#| msgid "Add confirmation text"
msgid "Bulk-refund confirmation"
msgstr "一括払い戻しの確認"
msgstr "確認テキストを追加"
#: pretix/base/services/cart.py:102 pretix/base/services/modelimport.py:247
#: pretix/base/services/orders.py:155
@@ -9429,15 +9431,15 @@ msgstr "バウチャーは、{recipient}あてに送信されました。"
#: pretix/base/settings.py:81
msgid "Compute taxes for every line individually"
msgstr "各行ごとに税額を計算する"
msgstr ""
#: pretix/base/settings.py:82
msgid "Compute taxes based on net total"
msgstr "税抜合計に基づいて税額を計算する"
msgstr ""
#: pretix/base/settings.py:83
msgid "Compute taxes based on net total with stable gross prices"
msgstr "税込価格を固定して税抜合計に基づいて税額を計算する"
msgstr ""
#: pretix/base/settings.py:133
msgid "Allow usage of restricted plugins"
@@ -9525,8 +9527,12 @@ msgid "Add-on products will not be counted."
msgstr "アドオン製品はカウントされません。"
#: pretix/base/settings.py:334
#, fuzzy
#| msgid ""
#| "Show net prices instead of gross prices in the product list (not "
#| "recommended!)"
msgid "Show net prices instead of gross prices in the product list"
msgstr "製品リストに税込価格ではなく税抜価格を表示する"
msgstr "製品リストには、総額ではなく純額を表示してください(お勧めしません!)"
#: pretix/base/settings.py:335
msgid ""
@@ -9640,8 +9646,10 @@ msgid "Require a phone number per order"
msgstr "1注文につき1つの電話番号が必要"
#: pretix/base/settings.py:481
#, fuzzy
#| msgid "including all taxes"
msgid "Rounding of taxes"
msgstr "税額の端数処理"
msgstr "すべての税金を含めて"
#: pretix/base/settings.py:485
msgid ""
@@ -9649,9 +9657,6 @@ msgid ""
"for tax reporting, you need to make sure to account for possible rounding "
"differences if your external system rounds differently than pretix."
msgstr ""
"pretixから外部システムに販売データを転送して税務申告を行う場合、外部システム"
"の端数処理がpretixと異なる場合は、端数処理の差異を考慮する必要があることに注"
"意してください。"
#: pretix/base/settings.py:500
msgid "Ask for invoice address"
@@ -12786,18 +12791,19 @@ msgstr "数分以上かかる場合は、このページを更新するか、お
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:2
msgid ""
"You have requested us to cancel an event which includes a larger bulk-refund:"
msgstr "大量の払い戻しを伴うイベントのキャンセルをリクエストされました:"
msgstr ""
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:6
#, fuzzy
#| msgid "Initiate a refund of %(amount)s"
msgid "Estimated refund amount"
msgstr "推定払い戻し金額"
msgstr "%(amount)sの払い戻しを開始する"
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:8
msgid ""
"Please confirm that you want to proceed by coping the following confirmation "
"code into the cancellation form:"
msgstr "以下の確認コードをキャンセルフォームにコピーして、続行することを確認してくだ"
"さい:"
msgstr ""
#: pretix/base/templates/pretixbase/email/email_footer.html:3
#, python-format
@@ -13618,24 +13624,31 @@ msgid ""
msgstr "ギフトカードの有効期限を発行年を含めた{}年に設定しました。"
#: pretix/control/forms/event.py:806
#, fuzzy
#| msgid "including all taxes"
msgid "Prices including tax"
msgstr "税込価格"
msgstr "すべての税金を含めて"
#: pretix/control/forms/event.py:807
msgid "Recommended if you sell tickets at least partly to consumers."
msgstr "少なくとも一部を消費者に販売する場合に推奨されます。"
msgstr ""
#: pretix/control/forms/event.py:811
#, fuzzy
#| msgctxt "reporting_timeframe"
#| msgid "All future (excluding today)"
msgid "Prices excluding tax"
msgstr "税込価格"
msgstr "すべての未来(今日を除く)"
#: pretix/control/forms/event.py:812
msgid "Recommended only if you sell tickets primarily to business customers."
msgstr "主に法人顧客にチケットを販売する場合のみ推奨されます。"
msgstr ""
#: pretix/control/forms/event.py:848
#, fuzzy
#| msgid "Date chosen by customer"
msgid "Prices shown to customer"
msgstr "顧客に表示される価格"
msgstr "お客様が選んだ日付"
#: pretix/control/forms/event.py:852
msgid ""
@@ -13644,9 +13657,6 @@ msgid ""
"product, the total tax amount can differ from when it would be computed from "
"the order total."
msgstr ""
"電子インボイスが不要な場合に推奨されます。各製品は表示された税抜価格と税込価"
"格で販売されます。ただし、複数の製品を含む注文では、合計税額が注文合計から計"
"算した場合と異なる可能性があります。"
#: pretix/control/forms/event.py:857
msgid ""
@@ -13655,10 +13665,6 @@ msgid ""
"may be changed to ensure correct rounding, while the net prices will be kept "
"as configured. This may cause the actual payment amount to differ."
msgstr ""
"主に法人顧客に販売し、顧客に税抜価格を表示する場合の電子インボイスに推奨され"
"ます。正確な端数処理を確保するために、一部の製品の税込価格が変更される場合が"
"ありますが、税抜価格は設定どおりに保たれます。これにより実際の支払い金額が異"
"なる場合があります。"
#: pretix/control/forms/event.py:863
msgid ""
@@ -13668,11 +13674,6 @@ msgid ""
"configured whenever possible. Gross prices may still change if they are "
"impossible to derive from a rounded net price."
msgstr ""
"主に消費者に販売する場合の電子インボイスに推奨されます。注文合計の正確な端数"
"処理を確保するために、一部の製品の税込価格または税抜価格が自動的に変更される"
"場合があります。システムは可能な限り設定どおりの税込価格を維持しようとします"
"。ただし、端数処理された税抜価格から算出できない場合は、税込価格が変更される"
"ことがあります。"
#: pretix/control/forms/event.py:961
msgid "Generate invoices for Sales channels"
@@ -14126,7 +14127,7 @@ msgstr "有効な注文"
#: pretix/control/forms/filter.py:220
msgid "Paid (or canceled with paid fee)"
msgstr "支払い済み (または支払い済み手数料でキャンセルされた)"
msgstr "支払われた(または支払われた手数料でキャンセルされた"
#: pretix/control/forms/filter.py:221
#: pretix/control/templates/pretixcontrol/items/question.html:27
@@ -14153,7 +14154,7 @@ msgstr "キャンセル"
#: pretix/control/forms/filter.py:226
msgid "Canceled (fully)"
msgstr "キャンセル済み (全額)"
msgstr "キャンセルされました(完全に)"
#: pretix/control/forms/filter.py:227
msgid "Canceled (fully or with paid fee)"
@@ -14165,11 +14166,11 @@ msgstr "キャンセル済み少なくとも1つのポジション"
#: pretix/control/forms/filter.py:229
msgid "Cancellation requested"
msgstr "キャンセル申請済み"
msgstr "キャンセルがリクエストされました"
#: pretix/control/forms/filter.py:230
msgid "Fully canceled but invoice not canceled"
msgstr "キャンセル済み(請求書はキャンセル)"
msgstr "完全にキャンセルされましたが、請求書はキャンセルされていません"
#: pretix/control/forms/filter.py:232
msgid "Payment process"
@@ -14187,11 +14188,11 @@ msgstr "保留中(期限切れ)"
#: pretix/control/forms/filter.py:236
msgid "Overpaid"
msgstr "過払い済み"
msgstr "過剰な給料"
#: pretix/control/forms/filter.py:237
msgid "Partially paid"
msgstr "一部支払い済み"
msgstr "部分的に支払われました"
#: pretix/control/forms/filter.py:238
msgid "Underpaid (but confirmed)"
@@ -15747,7 +15748,7 @@ msgstr ""
#: pretix/control/forms/orders.py:1037
msgid "I understand that this is not reversible and want to continue"
msgstr "これは元に戻せないことを理解し、続行します"
msgstr ""
#: pretix/control/forms/orders.py:1041
#: pretix/control/templates/pretixcontrol/shredder/download.html:53
@@ -15757,11 +15758,13 @@ msgstr "確認コード"
#: pretix/control/forms/orders.py:1042
msgid ""
"We have just emailed you a confirmation code to enter to confirm this action"
msgstr "この操作を確認するための確認コードをメールで送信しました"
msgstr ""
#: pretix/control/forms/orders.py:1055
#, fuzzy
#| msgid "The confirm code you entered was incorrect."
msgid "The confirmation code is incorrect."
msgstr "確認コードが正しくありません。"
msgstr "入力した確認コードが間違っています。"
#: pretix/control/forms/organizer.py:93
msgid "This slug is already in use. Please choose a different one."
@@ -20690,9 +20693,6 @@ msgid ""
"optionally contain additional rules that depend on the customer's country "
"and type."
msgstr ""
"税ルールは、個々の製品に割り当てることができるさまざまな課税シナリオを定義し"
"ます。各税ルールには既定の税率が含まれ、オプションで顧客の国や種別に応じた追"
"加ルールを含めることができます。"
#: pretix/control/templates/pretixcontrol/event/tax.html:21
msgid "You haven't created any tax rules yet."
@@ -20719,10 +20719,12 @@ msgid "Make default"
msgstr "デフォルトに設定"
#: pretix/control/templates/pretixcontrol/event/tax.html:67
#, python-format
#, fuzzy, python-format
#| msgid "%(count)s event"
#| msgid_plural "%(count)s events"
msgid "%(count)s product"
msgid_plural "%(count)s products"
msgstr[0] "%(count)s個の製品"
msgstr[0] "%(count)s 件のイベント"
#: pretix/control/templates/pretixcontrol/event/tax.html:75
#, python-format
@@ -20735,12 +20737,16 @@ msgid "excl. %(rate)s %%"
msgstr "%(rate)s %%を除く"
#: pretix/control/templates/pretixcontrol/event/tax.html:80
#, fuzzy
#| msgid "Custom rules"
msgid "with custom rules"
msgstr "カスタムルールあり"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/tax.html:110
#, fuzzy
#| msgid "Base settings"
msgid "Tax settings"
msgstr "の設定"
msgstr "ベースの設定"
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:4
#: pretix/control/templates/pretixcontrol/event/tax_delete.html:6
@@ -23481,7 +23487,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/transactions.html:73
#, python-format
msgid "incl. %(amount)s rounding correction"
msgstr "%(amount)sの端数処理調整を含む"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:5
#: pretix/control/templates/pretixcontrol/orders/bulk_action.html:7
@@ -23584,71 +23590,81 @@ msgstr ""
"空席待ちリストのすべての人が複数のメールを受け取ることになります。"
#: pretix/control/templates/pretixcontrol/orders/cancel.html:84
#, fuzzy
#| msgid "Refund amount"
msgid "Preview refund amount"
msgstr "払い戻し金額のプレビュー"
msgstr "払い戻し金額"
#: pretix/control/templates/pretixcontrol/orders/cancel.html:88
msgid "Cancel all orders"
msgstr "すべての注文をキャンセルします"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:13
#, fuzzy
#| msgid "If you contact us, please send us the following code:"
msgid "If you proceed, the system will do the following:"
msgstr "続行すると、システムは以下を実行します:"
msgstr "お問い合わせの際は、以下のコードをお送りください:"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:19
#, python-format
msgid "%(count)s order will be canceled fully"
msgid_plural "%(count)s orders will be canceled fully"
msgstr[0] "%(count)s件の注文が全額キャンセルされます"
msgstr[0] ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:26
#, python-format
msgid "%(count)s order will be canceled partially"
msgid_plural "%(count)s orders will be canceled partially"
msgstr[0] "%(count)s件の注文が一部キャンセルされます"
msgstr[0] ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:36
#, python-format
msgid "%(amount)s are eligible for refunds."
msgstr "%(amount)sが払い戻し対象です。"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:40
msgid ""
"The system will attempt to refund the money automatically if supported by "
"the payment method."
msgstr "当該の決済方法に対応している場合、システムは自動的に払い戻しを試みます。"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:42
msgid "The system will create manual refunds that you need to execute."
msgstr "システムは手動払い戻しを作成しますので、ご自身で実行する必要があります。"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:44
#, fuzzy
#| msgid "Text (if order will not expire automatically)"
msgid "Refunds will not happen automatically."
msgstr "払い戻しは自動的には実行されません。"
msgstr "テキスト(注文が自動的に期限切れにならない場合)"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:52
#, fuzzy
#| msgid "Send information via email"
msgid "Inform all customers via email."
msgstr "メールで情報を送信する"
msgstr "メールで情報を送信する"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:57
msgid "Inform all waiting list contacts via email."
msgstr "すべての空席待ちリストの連絡先にメールで通知する。"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:62
msgid ""
"These numbers are estimates and may change if the data in your event "
"recently changed."
msgstr "これらの数値は推定値であり、イベントのデータが最近変更された場合は変わる可能"
"性があります。"
msgstr ""
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:76
#, python-format
#, fuzzy, python-format
#| msgid "Initiate a refund of %(amount)s"
msgid "Proceed and refund approx. %(amount)s"
msgstr "続行して約%(amount)s払い戻"
msgstr "%(amount)s払い戻しを開始する"
#: pretix/control/templates/pretixcontrol/orders/cancel_confirm.html:80
#, fuzzy
#| msgid "Yes, cancel order"
msgid "Proceed and cancel orders"
msgstr "続行して注文をキャンセルす"
msgstr "はい、注文をキャンセルします"
#: pretix/control/templates/pretixcontrol/orders/export.html:5
#: pretix/control/templates/pretixcontrol/orders/export.html:8
@@ -24043,7 +24059,7 @@ msgstr "請求書はキャンセルされていません"
#: pretix/control/templates/pretixcontrol/orders/index.html:251
msgid "Sum over all pages"
msgstr "全ページの合計"
msgstr "すべてのページに渡り合計する"
#: pretix/control/templates/pretixcontrol/orders/index.html:254
#, python-format
@@ -30197,7 +30213,7 @@ msgstr "手動処理用の完全にカスタマイズ可能な支払い方法。
#: pretix/plugins/paypal2/payment.py:1097
#: pretix/plugins/paypal2/payment.py:1098 pretix/plugins/stripe/payment.py:1816
msgid "PayPal"
msgstr "PayPal"
msgstr "ペイパル"
#: pretix/plugins/paypal/apps.py:53
msgid ""
@@ -30217,7 +30233,7 @@ msgstr ""
#: pretix/plugins/paypal/payment.py:104
msgid "PayPal account"
msgstr "PayPalアカウント"
msgstr "ペイパルアカウント"
#: pretix/plugins/paypal/payment.py:117 pretix/plugins/paypal2/payment.py:114
#, python-brace-format
@@ -30314,7 +30330,7 @@ msgstr "PayPalの支払いID"
#: pretix/plugins/paypal/payment.py:711 pretix/plugins/paypal2/payment.py:1080
msgid "PayPal sale ID"
msgstr "PayPalの売上ID"
msgstr "ペイパルの売上ID"
#: pretix/plugins/paypal/templates/pretixplugins/paypal/checkout_payment_confirm.html:3
#: pretix/plugins/paypal2/templates/pretixplugins/paypal2/checkout_payment_confirm.html:19

View File

@@ -26,7 +26,6 @@ from django.dispatch import receiver
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from pretix.base.logentrytype_registry import LogEntryType, log_entry_types
from pretix.base.models import Checkin, OrderPayment
from pretix.base.signals import (
checkin_created, event_copy_data, item_copy_data, logentry_display,
@@ -65,13 +64,19 @@ def nav_event_receiver(sender, request, **kwargs):
]
@log_entry_types.new_from_dict({
"pretix.plugins.autocheckin.rule.added": _("An auto check-in rule was created"),
"pretix.plugins.autocheckin.rule.changed": _("An auto check-in rule was updated"),
"pretix.plugins.autocheckin.rule.deleted": _("An auto check-in rule was deleted"),
})
class AutocheckinLogEntryType(LogEntryType):
pass
@receiver(signal=logentry_display)
def logentry_display_receiver(sender, logentry, **kwargs):
plains = {
"pretix.plugins.autocheckin.rule.added": _("An auto check-in rule was created"),
"pretix.plugins.autocheckin.rule.changed": _(
"An auto check-in rule was updated"
),
"pretix.plugins.autocheckin.rule.deleted": _(
"An auto check-in rule was deleted"
),
}
if logentry.action_type in plains:
return plains[logentry.action_type]
@receiver(item_copy_data, dispatch_uid="autocheckin_item_copy")

View File

@@ -31,7 +31,6 @@ from django.utils.translation import gettext_lazy as _
from paypalhttp import HttpResponse
from pretix.base.forms import SecretKeySettingsField
from pretix.base.logentrytype_registry import LogEntryType, log_entry_types
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
from pretix.base.settings import settings_hierarkey
from pretix.base.signals import (
@@ -81,36 +80,37 @@ def html_head_presale(sender, request=None, **kwargs):
return ""
@log_entry_types.new()
class StripeEvent(LogEntryType):
action_type = 'pretix.plugins.stripe.event'
@receiver(signal=logentry_display, dispatch_uid="stripe_logentry_display")
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
if logentry.action_type != 'pretix.plugins.stripe.event':
return
def display(self, logentry, data):
event_type = data.get('type')
text = None
plains = {
'charge.succeeded': _('Charge succeeded.'),
'charge.refunded': _('Charge refunded.'),
'charge.updated': _('Charge updated.'),
'charge.pending': _('Charge pending'),
'source.chargeable': _('Payment authorized.'),
'source.canceled': _('Payment authorization canceled.'),
'source.failed': _('Payment authorization failed.')
}
data = json.loads(logentry.data)
event_type = data.get('type')
text = None
plains = {
'charge.succeeded': _('Charge succeeded.'),
'charge.refunded': _('Charge refunded.'),
'charge.updated': _('Charge updated.'),
'charge.pending': _('Charge pending'),
'source.chargeable': _('Payment authorized.'),
'source.canceled': _('Payment authorization canceled.'),
'source.failed': _('Payment authorization failed.')
}
if event_type in plains:
text = plains[event_type]
elif event_type == 'charge.failed':
text = _('Charge failed. Reason: {}').format(data['data']['object']['failure_message'])
elif event_type == 'charge.dispute.created':
text = _('Dispute created. Reason: {}').format(data['data']['object']['reason'])
elif event_type == 'charge.dispute.updated':
text = _('Dispute updated. Reason: {}').format(data['data']['object']['reason'])
elif event_type == 'charge.dispute.closed':
text = _('Dispute closed. Status: {}').format(data['data']['object']['status'])
if event_type in plains:
text = plains[event_type]
elif event_type == 'charge.failed':
text = _('Charge failed. Reason: {}').format(data['data']['object']['failure_message'])
elif event_type == 'charge.dispute.created':
text = _('Dispute created. Reason: {}').format(data['data']['object']['reason'])
elif event_type == 'charge.dispute.updated':
text = _('Dispute updated. Reason: {}').format(data['data']['object']['reason'])
elif event_type == 'charge.dispute.closed':
text = _('Dispute closed. Status: {}').format(data['data']['object']['status'])
if text:
return _('Stripe reported an event: {}').format(text)
if text:
return _('Stripe reported an event: {}').format(text)
settings_hierarkey.add_default('payment_stripe_method_card', True, bool)

View File

@@ -9,6 +9,34 @@
{% endblock %}
{% block inner %}
<h3 class="sr-only">{% trans "Payment" %}</h3>
{% if customer_gift_cards %}
<p><strong>
<span class="sr-only">{% trans "Information" %}</span>
{% trans "The following gift cards are available in your customer account:" %}
</strong></p>
<form method="post">
{% csrf_token %}
<ul class="list-group">
{% for c in customer_gift_cards %}
<li class="list-group-item row">
<div class="col-xs-9" id="gc-code-{{ forloop.counter }}">
{{ c }}
</div>
<div class="col-xs-2 text-right" id="gc-value-{{ forloop.counter }}">
{{ c.value|money:c.currency }}
</div>
<div class="col-xs-1 text-right">
<button name="use_giftcard" value="{{ c.secret }}" title="{% trans "Use gift card" %}"
aria-describedby="gc-code-{{ forloop.counter }} gc-value-{{ forloop.counter }}"
class="btn btn-primary btn-xs">
{% trans "Apply" %}
</button>
</div>
</li>
{% endfor %}
</ul>
</form>
{% endif %}
{% if current_payments %}
<p>{% trans "You already selected the following payment methods:" %}</p>
<form method="post">

View File

@@ -1,33 +0,0 @@
{% load i18n %}
{% load bootstrap3 %}
{% load money %}
{% load rich_text %}
{{ request.event.settings.payment_giftcard_public_description|rich_text }}
{% if customer_gift_cards %}
<p><strong>
<span class="sr-only">{% trans "Information" %}</span>
{% trans "The following gift cards are available in your customer account:" %}
</strong></p>
{% csrf_token %}
<ul class="list-group">
{% for c in customer_gift_cards %}
<li class="list-group-item row row-no-gutters">
<div class="col-sm-8 col-md-9" id="gc-code-{{ forloop.counter }}">
{{ c }}
</div>
<div class="col-sm-2 text-right" id="gc-value-{{ forloop.counter }}">
{{ c.value|money:c.currency }}
</div>
<div class="col-sm-2 col-md-1 text-right">
<button name="use_giftcard" class="btn btn-primary btn-xs use_giftcard" data-value="{{ c.secret }}"
title="{% trans "Use gift card" %}"
aria-describedby="gc-code-{{ forloop.counter }} gc-value-{{ forloop.counter }}">
{% trans "Apply" %}
</button>
</div>
</li>
{% endfor %}
</ul>
{% endif %}
{% bootstrap_form form layout='checkout' %}

View File

@@ -8,10 +8,10 @@
"name": "pretix",
"version": "0.0.0",
"dependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
"@rollup/plugin-babel": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@babel/core": "^7.28.4",
"@babel/preset-env": "^7.28.3",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^16.0.1",
"rollup": "^2.79.1",
"rollup-plugin-vue": "^5.0.1",
"vue": "^2.7.16",
@@ -33,27 +33,27 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
"integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
"@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.5",
"@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5",
"@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.4",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -89,12 +89,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"dependencies": {
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"@babel/parser": "^7.28.3",
"@babel/types": "^7.28.2",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -348,9 +348,10 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -391,11 +392,11 @@
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"dependencies": {
"@babel/types": "^7.28.5"
"@babel/types": "^7.28.4"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -405,12 +406,13 @@
}
},
"node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz",
"integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz",
"integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.28.5"
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -601,9 +603,9 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz",
"integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz",
"integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -646,16 +648,16 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
"integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/traverse": "^7.28.4"
"@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
@@ -681,12 +683,12 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
"integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz",
"integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.28.5"
"@babel/traverse": "^7.28.0"
},
"engines": {
"node": ">=6.9.0"
@@ -773,9 +775,10 @@
}
},
"node_modules/@babel/plugin-transform-exponentiation-operator": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz",
"integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz",
"integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -865,9 +868,10 @@
}
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz",
"integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz",
"integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -926,14 +930,15 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz",
"integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
"integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helper-module-transforms": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/traverse": "^7.28.5"
"@babel/helper-validator-identifier": "^7.27.1",
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1020,15 +1025,15 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz",
"integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz",
"integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==",
"dependencies": {
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/plugin-transform-destructuring": "^7.28.0",
"@babel/plugin-transform-parameters": "^7.27.7",
"@babel/traverse": "^7.28.4"
"@babel/traverse": "^7.28.0"
},
"engines": {
"node": ">=6.9.0"
@@ -1069,9 +1074,10 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
"integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
@@ -1146,9 +1152,9 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
"integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz",
"integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -1330,15 +1336,15 @@
}
},
"node_modules/@babel/preset-env": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz",
"integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
"integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
"dependencies": {
"@babel/compat-data": "^7.28.5",
"@babel/compat-data": "^7.28.0",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
@@ -1351,42 +1357,42 @@
"@babel/plugin-transform-async-generator-functions": "^7.28.0",
"@babel/plugin-transform-async-to-generator": "^7.27.1",
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
"@babel/plugin-transform-block-scoping": "^7.28.5",
"@babel/plugin-transform-block-scoping": "^7.28.0",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-class-static-block": "^7.28.3",
"@babel/plugin-transform-classes": "^7.28.4",
"@babel/plugin-transform-classes": "^7.28.3",
"@babel/plugin-transform-computed-properties": "^7.27.1",
"@babel/plugin-transform-destructuring": "^7.28.5",
"@babel/plugin-transform-destructuring": "^7.28.0",
"@babel/plugin-transform-dotall-regex": "^7.27.1",
"@babel/plugin-transform-duplicate-keys": "^7.27.1",
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
"@babel/plugin-transform-dynamic-import": "^7.27.1",
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
"@babel/plugin-transform-exponentiation-operator": "^7.28.5",
"@babel/plugin-transform-exponentiation-operator": "^7.27.1",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-for-of": "^7.27.1",
"@babel/plugin-transform-function-name": "^7.27.1",
"@babel/plugin-transform-json-strings": "^7.27.1",
"@babel/plugin-transform-literals": "^7.27.1",
"@babel/plugin-transform-logical-assignment-operators": "^7.28.5",
"@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
"@babel/plugin-transform-modules-amd": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-modules-systemjs": "^7.28.5",
"@babel/plugin-transform-modules-systemjs": "^7.27.1",
"@babel/plugin-transform-modules-umd": "^7.27.1",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
"@babel/plugin-transform-new-target": "^7.27.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
"@babel/plugin-transform-numeric-separator": "^7.27.1",
"@babel/plugin-transform-object-rest-spread": "^7.28.4",
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
"@babel/plugin-transform-object-super": "^7.27.1",
"@babel/plugin-transform-optional-catch-binding": "^7.27.1",
"@babel/plugin-transform-optional-chaining": "^7.28.5",
"@babel/plugin-transform-optional-chaining": "^7.27.1",
"@babel/plugin-transform-parameters": "^7.27.7",
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-transform-property-literals": "^7.27.1",
"@babel/plugin-transform-regenerator": "^7.28.4",
"@babel/plugin-transform-regenerator": "^7.28.3",
"@babel/plugin-transform-regexp-modifiers": "^7.27.1",
"@babel/plugin-transform-reserved-words": "^7.27.1",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
@@ -1448,16 +1454,16 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
"@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.5",
"@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.5",
"@babel/types": "^7.28.4",
"debug": "^4.3.1"
},
"engines": {
@@ -1465,12 +1471,12 @@
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
"@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1517,9 +1523,9 @@
}
},
"node_modules/@rollup/plugin-babel": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz",
"integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==",
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
"integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==",
"dependencies": {
"@babel/helper-module-imports": "^7.18.6",
"@rollup/pluginutils": "^5.0.1"
@@ -1542,9 +1548,10 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
"integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
@@ -3783,24 +3790,24 @@
}
},
"@babel/compat-data": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
"integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz",
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="
},
"@babel/core": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"requires": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
"@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.5",
"@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5",
"@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.4",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -3822,12 +3829,12 @@
}
},
"@babel/generator": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"requires": {
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"@babel/parser": "^7.28.3",
"@babel/types": "^7.28.2",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -4004,9 +4011,9 @@
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
},
"@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
},
"@babel/helper-validator-option": {
"version": "7.27.1",
@@ -4033,20 +4040,20 @@
}
},
"@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"requires": {
"@babel/types": "^7.28.5"
"@babel/types": "^7.28.4"
}
},
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz",
"integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz",
"integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.28.5"
"@babel/traverse": "^7.27.1"
}
},
"@babel/plugin-bugfix-safari-class-field-initializer-scope": {
@@ -4152,9 +4159,9 @@
}
},
"@babel/plugin-transform-block-scoping": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz",
"integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz",
"integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1"
}
@@ -4178,16 +4185,16 @@
}
},
"@babel/plugin-transform-classes": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
"integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/traverse": "^7.28.4"
"@babel/traverse": "^7.28.3"
}
},
"@babel/plugin-transform-computed-properties": {
@@ -4200,12 +4207,12 @@
}
},
"@babel/plugin-transform-destructuring": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
"integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz",
"integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.28.5"
"@babel/traverse": "^7.28.0"
}
},
"@babel/plugin-transform-dotall-regex": {
@@ -4252,9 +4259,9 @@
}
},
"@babel/plugin-transform-exponentiation-operator": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz",
"integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz",
"integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1"
}
@@ -4303,9 +4310,9 @@
}
},
"@babel/plugin-transform-logical-assignment-operators": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz",
"integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz",
"integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1"
}
@@ -4337,14 +4344,14 @@
}
},
"@babel/plugin-transform-modules-systemjs": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz",
"integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
"integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
"requires": {
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helper-module-transforms": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/traverse": "^7.28.5"
"@babel/helper-validator-identifier": "^7.27.1",
"@babel/traverse": "^7.27.1"
}
},
"@babel/plugin-transform-modules-umd": {
@@ -4390,15 +4397,15 @@
}
},
"@babel/plugin-transform-object-rest-spread": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz",
"integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==",
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz",
"integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==",
"requires": {
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/plugin-transform-destructuring": "^7.28.0",
"@babel/plugin-transform-parameters": "^7.27.7",
"@babel/traverse": "^7.28.4"
"@babel/traverse": "^7.28.0"
}
},
"@babel/plugin-transform-object-super": {
@@ -4419,9 +4426,9 @@
}
},
"@babel/plugin-transform-optional-chaining": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
"integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
@@ -4463,9 +4470,9 @@
}
},
"@babel/plugin-transform-regenerator": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
"integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz",
"integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1"
}
@@ -4564,15 +4571,15 @@
}
},
"@babel/preset-env": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz",
"integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
"integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
"requires": {
"@babel/compat-data": "^7.28.5",
"@babel/compat-data": "^7.28.0",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
@@ -4585,42 +4592,42 @@
"@babel/plugin-transform-async-generator-functions": "^7.28.0",
"@babel/plugin-transform-async-to-generator": "^7.27.1",
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
"@babel/plugin-transform-block-scoping": "^7.28.5",
"@babel/plugin-transform-block-scoping": "^7.28.0",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-class-static-block": "^7.28.3",
"@babel/plugin-transform-classes": "^7.28.4",
"@babel/plugin-transform-classes": "^7.28.3",
"@babel/plugin-transform-computed-properties": "^7.27.1",
"@babel/plugin-transform-destructuring": "^7.28.5",
"@babel/plugin-transform-destructuring": "^7.28.0",
"@babel/plugin-transform-dotall-regex": "^7.27.1",
"@babel/plugin-transform-duplicate-keys": "^7.27.1",
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
"@babel/plugin-transform-dynamic-import": "^7.27.1",
"@babel/plugin-transform-explicit-resource-management": "^7.28.0",
"@babel/plugin-transform-exponentiation-operator": "^7.28.5",
"@babel/plugin-transform-exponentiation-operator": "^7.27.1",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-for-of": "^7.27.1",
"@babel/plugin-transform-function-name": "^7.27.1",
"@babel/plugin-transform-json-strings": "^7.27.1",
"@babel/plugin-transform-literals": "^7.27.1",
"@babel/plugin-transform-logical-assignment-operators": "^7.28.5",
"@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
"@babel/plugin-transform-modules-amd": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-modules-systemjs": "^7.28.5",
"@babel/plugin-transform-modules-systemjs": "^7.27.1",
"@babel/plugin-transform-modules-umd": "^7.27.1",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
"@babel/plugin-transform-new-target": "^7.27.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
"@babel/plugin-transform-numeric-separator": "^7.27.1",
"@babel/plugin-transform-object-rest-spread": "^7.28.4",
"@babel/plugin-transform-object-rest-spread": "^7.28.0",
"@babel/plugin-transform-object-super": "^7.27.1",
"@babel/plugin-transform-optional-catch-binding": "^7.27.1",
"@babel/plugin-transform-optional-chaining": "^7.28.5",
"@babel/plugin-transform-optional-chaining": "^7.27.1",
"@babel/plugin-transform-parameters": "^7.27.7",
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-transform-property-literals": "^7.27.1",
"@babel/plugin-transform-regenerator": "^7.28.4",
"@babel/plugin-transform-regenerator": "^7.28.3",
"@babel/plugin-transform-regexp-modifiers": "^7.27.1",
"@babel/plugin-transform-reserved-words": "^7.27.1",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
@@ -4668,26 +4675,26 @@
}
},
"@babel/traverse": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
"requires": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
"@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.5",
"@babel/parser": "^7.28.4",
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.5",
"@babel/types": "^7.28.4",
"debug": "^4.3.1"
}
},
"@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"requires": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
"@babel/helper-validator-identifier": "^7.27.1"
}
},
"@jridgewell/gen-mapping": {
@@ -4728,18 +4735,18 @@
}
},
"@rollup/plugin-babel": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz",
"integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==",
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz",
"integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==",
"requires": {
"@babel/helper-module-imports": "^7.18.6",
"@rollup/pluginutils": "^5.0.1"
}
},
"@rollup/plugin-node-resolve": {
"version": "16.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
"integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
"requires": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",

View File

@@ -4,10 +4,10 @@
"private": true,
"scripts": {},
"dependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
"@rollup/plugin-babel": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.3",
"@babel/core": "^7.28.4",
"@babel/preset-env": "^7.28.3",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^16.0.1",
"vue": "^2.7.16",
"rollup": "^2.79.1",
"rollup-plugin-vue": "^5.0.1",

View File

@@ -263,3 +263,11 @@ svg.svg-icon {
@include table-row-variant('warning', var(--pretix-brand-warning-lighten-40), var(--pretix-brand-warning-lighten-35));
@include table-row-variant('danger', var(--pretix-brand-danger-lighten-30), var(--pretix-brand-danger-lighten-25));
.confirmation-code-input {
font-size: 200%;
font-family: monospace;
font-stretch: expanded;
text-align: center;
height: 50px;
margin: 10px 0;
}

View File

@@ -938,3 +938,25 @@ details {
}
}
}
@media (min-width: $screen-lg-min) {
.centered-form {
margin: 80px auto;
max-width: 800px;
border: 1px solid #ddd;
padding: 20px 40px 0;
border-radius: 4px;
box-shadow: 2px 2px 2px #eee;
}
.form.centered-form .submit-group {
margin: 25px -40px 0 !important;
padding-right: 40px;
padding-left: 40px;
}
.centered-form p {
margin: 20px 0;
}
}

View File

@@ -1134,7 +1134,7 @@ class PasswordChangeRequiredTest(TestCase):
super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
def test_redirect_to_settings(self):
def test_redirect_to_password_change(self):
self.user.needs_password_change = True
self.user.save()
self.client.login(email='dummy@dummy.dummy', password='dummy')
@@ -1143,9 +1143,9 @@ class PasswordChangeRequiredTest(TestCase):
self.assertEqual(response.status_code, 302)
assert self.user.needs_password_change is True
self.assertIn('/control/settings?next=/control/events/', response['Location'])
self.assertIn('/control/settings/password/change?next=/control/events/', response['Location'])
def test_redirect_to_2fa_to_settings(self):
def test_redirect_to_2fa_to_password_change(self):
self.user.require_2fa = True
self.user.needs_password_change = True
self.user.save()
@@ -1168,4 +1168,4 @@ class PasswordChangeRequiredTest(TestCase):
response = self.client.get('/control/events/')
self.assertEqual(response.status_code, 302)
self.assertIn('/control/settings?next=/control/events/', response['Location'])
self.assertIn('/control/settings/password/change?next=/control/events/', response['Location'])

View File

@@ -19,22 +19,11 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
#
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
#
# This file contains Apache-licensed contributions copyrighted by: Jason Estibeiro
#
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import re
import time
import pytest
from django.core import mail as djmail
from django.utils.timezone import now
from django_otp.oath import TOTP
from django_otp.plugins.otp_static.models import StaticDevice
@@ -56,7 +45,7 @@ class UserSettingsTest(SoupTest):
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
doc = self.get_doc('/control/settings')
self.form_data = extract_form_fields(doc.select('.container-fluid form')[0])
self.form_data = extract_form_fields(doc.select('form[data-testid="usersettingsform"]')[0])
def save(self, data):
form_data = self.form_data.copy()
@@ -71,33 +60,107 @@ class UserSettingsTest(SoupTest):
self.user = User.objects.get(pk=self.user.pk)
assert self.user.fullname == 'Peter Miller'
def test_change_email_require_password(self):
def test_set_locale_and_timezone(self):
doc = self.save({
'email': 'foo@example.com',
'locale': 'fr',
'timezone': 'Europe/Paris',
})
assert doc.select(".alert-danger")
assert doc.select(".alert-success")
self.user = User.objects.get(pk=self.user.pk)
assert self.user.email == 'dummy@dummy.dummy'
assert self.user.locale == 'fr'
assert self.user.timezone == 'Europe/Paris'
class UserEmailChangeTest(SoupTest):
def setUp(self):
super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
session = self.client.session
session['pretix_auth_login_time'] = int(time.time())
session.save()
doc = self.get_doc('/control/settings/email/change')
self.form_data = extract_form_fields(doc.select('.container-fluid form')[0])
def test_require_reauth(self):
session = self.client.session
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 2
session.save()
response = self.client.get('/control/settings/email/change')
self.assertIn('/control/reauth', response['Location'])
self.assertEqual(response.status_code, 302)
response = self.client.post('/control/reauth/?next=/control/settings/email/change', {
'password': 'barfoofoo'
})
self.assertIn('/control/settings/email/change', response['Location'])
self.assertEqual(response.status_code, 302)
def submit_step_1(self, data):
form_data = self.form_data.copy()
form_data.update(data)
return self.post_doc('/control/settings/email/change', form_data)
def submit_step_2(self, data):
form_data = self.form_data.copy()
form_data.update(data)
return self.post_doc('/control/settings/email/confirm?reason=email_change', form_data)
def test_change_email_success(self):
doc = self.save({
'email': 'foo@example.com',
'old_pw': 'barfoofoo'
djmail.outbox = []
doc = self.submit_step_1({
'new_email': 'foo@example.com',
})
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == ['foo@example.com']
code = re.search("[0-9]{7}", djmail.outbox[0].body).group(0)
doc = self.submit_step_2({
'code': code,
})
assert doc.select(".alert-success")
self.user = User.objects.get(pk=self.user.pk)
assert self.user.email == 'foo@example.com'
def test_change_email_no_duplicates(self):
User.objects.create_user('foo@example.com', 'foo')
doc = self.save({
'email': 'foo@example.com',
'old_pw': 'barfoofoo'
def test_change_email_wrong_code(self):
djmail.outbox = []
doc = self.submit_step_1({
'new_email': 'foo@example.com',
})
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == ['foo@example.com']
code = re.search("[0-9]{7}", djmail.outbox[0].body).group(0)
wrong_code = '0000000' if code == '1234567' else '1234567'
doc = self.submit_step_2({
'code': wrong_code,
})
assert doc.select(".alert-danger")
self.user = User.objects.get(pk=self.user.pk)
assert self.user.email == 'dummy@dummy.dummy'
def test_change_email_no_duplicates(self):
User.objects.create_user('foo@example.com', 'foo')
doc = self.submit_step_1({
'new_email': 'foo@example.com',
})
assert doc.select(".alert-danger")
self.user = User.objects.get(pk=self.user.pk)
assert self.user.email == 'dummy@dummy.dummy'
class UserPasswordChangeTest(SoupTest):
def setUp(self):
super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
doc = self.get_doc('/control/settings/password/change')
self.form_data = extract_form_fields(doc.select('.container-fluid form')[0])
def save(self, data):
form_data = self.form_data.copy()
form_data.update(data)
return self.post_doc('/control/settings/password/change', form_data)
def test_change_password_require_password(self):
doc = self.save({
'new_pw': 'foo',
@@ -193,18 +256,6 @@ class UserSettingsTest(SoupTest):
})
assert doc.select(".alert-danger")
def test_needs_password_change(self):
self.user.needs_password_change = True
self.user.save()
doc = self.save({
'email': 'foo@example.com',
'old_pw': 'barfoofoo'
})
assert doc.select(".alert-success")
assert doc.select(".alert-warning")
self.user.refresh_from_db()
assert self.user.needs_password_change is True
def test_needs_password_change_changed(self):
self.user.needs_password_change = True
self.user.save()