Merge branch 'pretix:master' into vite-vue3

This commit is contained in:
rash
2026-02-19 13:40:30 +01:00
committed by GitHub
128 changed files with 44625 additions and 10533 deletions
-14
View File
@@ -208,20 +208,6 @@ Additionally, when creating a device through the user interface or API, a user c
the device. These include an allow list of specific API calls that may be made by the device. pretix ships with security the device. These include an allow list of specific API calls that may be made by the device. pretix ships with security
policies for official pretix apps like pretixSCAN and pretixPOS. policies for official pretix apps like pretixSCAN and pretixPOS.
Removing a device
-----------------
If you want implement a way to to deprovision a device in your software, you can call the ``revoke`` endpoint to
invalidate your API key. There is no way to reverse this operation.
.. sourcecode:: http
POST /api/v1/device/revoke HTTP/1.1
Host: pretix.eu
Authorization: Device 1kcsh572fonm3hawalrncam4l1gktr2rzx25a22l8g9hx108o9oi0rztpcvwnfnd
This can also be done by the user through the web interface.
Event selection Event selection
--------------- ---------------
+3
View File
@@ -60,6 +60,9 @@ The following values for ``action_types`` are valid with pretix core:
* ``pretix.event.added`` * ``pretix.event.added``
* ``pretix.event.changed`` * ``pretix.event.changed``
* ``pretix.event.deleted`` * ``pretix.event.deleted``
* ``pretix.giftcards.created``
* ``pretix.giftcards.modified``
* ``pretix.giftcards.transaction.*``
* ``pretix.voucher.added`` * ``pretix.voucher.added``
* ``pretix.voucher.changed`` * ``pretix.voucher.changed``
* ``pretix.voucher.deleted`` * ``pretix.voucher.deleted``
+1 -1
View File
@@ -1,6 +1,6 @@
sphinx==9.1.* sphinx==9.1.*
sphinx-rtd-theme~=3.1.0 sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=1.8.1 sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-images~=1.0.1 sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1 sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-spelling~=8.0.2
+1 -1
View File
@@ -1,7 +1,7 @@
-e ../ -e ../
sphinx==9.1.* sphinx==9.1.*
sphinx-rtd-theme~=3.1.0 sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=1.8.1 sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-images~=1.0.1 sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1 sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-spelling~=8.0.2
+5 -5
View File
@@ -33,7 +33,7 @@ dependencies = [
"celery==5.6.*", "celery==5.6.*",
"chardet==5.2.*", "chardet==5.2.*",
"cryptography>=44.0.0", "cryptography>=44.0.0",
"css-inline==0.19.*", "css-inline==0.20.*",
"defusedcsv>=1.1.0", "defusedcsv>=1.1.0",
"dnspython==2.*", "dnspython==2.*",
"Django[argon2]==4.2.*,>=4.2.26", "Django[argon2]==4.2.*,>=4.2.26",
@@ -65,7 +65,7 @@ dependencies = [
"kombu==5.6.*", "kombu==5.6.*",
"libsass==0.23.*", "libsass==0.23.*",
"lxml", "lxml",
"markdown==3.10.1", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3. "markdown==3.10.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7 # We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*", "mt-940==4.30.*",
"oauthlib==3.3.*", "oauthlib==3.3.*",
@@ -73,7 +73,7 @@ dependencies = [
"packaging", "packaging",
"paypalrestsdk==1.13.*", "paypalrestsdk==1.13.*",
"paypal-checkout-serversdk==1.0.*", "paypal-checkout-serversdk==1.0.*",
"PyJWT==2.10.*", "PyJWT==2.11.*",
"phonenumberslite==9.0.*", "phonenumberslite==9.0.*",
"Pillow==12.1.*", "Pillow==12.1.*",
"pretix-plugin-build", "pretix-plugin-build",
@@ -92,7 +92,7 @@ dependencies = [
"redis==7.1.*", "redis==7.1.*",
"reportlab==4.4.*", "reportlab==4.4.*",
"requests==2.32.*", "requests==2.32.*",
"sentry-sdk==2.51.*", "sentry-sdk==2.53.*",
"sepaxml==2.7.*", "sepaxml==2.7.*",
"stripe==7.9.*", "stripe==7.9.*",
"text-unidecode==1.*", "text-unidecode==1.*",
@@ -110,7 +110,7 @@ dev = [
"aiohttp==3.13.*", "aiohttp==3.13.*",
"coverage", "coverage",
"coveralls", "coveralls",
"fakeredis==2.33.*", "fakeredis==2.34.*",
"flake8==7.3.*", "flake8==7.3.*",
"freezegun", "freezegun",
"isort==7.0.*", "isort==7.0.*",
+1
View File
@@ -1743,6 +1743,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
rounding_mode = self.context["event"].settings.tax_rounding rounding_mode = self.context["event"].settings.tax_rounding
changed = apply_rounding( changed = apply_rounding(
rounding_mode, rounding_mode,
ia,
self.context["event"].currency, self.context["event"].currency,
[*pos_map.values(), *fees] [*pos_map.values(), *fees]
) )
+16 -19
View File
@@ -49,7 +49,7 @@ from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID, PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
PLUGIN_LEVEL_ORGANIZER, PLUGIN_LEVEL_ORGANIZER,
) )
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import mail
from pretix.base.settings import validate_organizer_settings from pretix.base.settings import validate_organizer_settings
from pretix.helpers.urls import build_absolute_uri as build_global_uri from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
@@ -363,24 +363,21 @@ class TeamInviteSerializer(serializers.ModelSerializer):
) )
def _send_invite(self, instance): def _send_invite(self, instance):
try: mail(
mail( instance.email,
instance.email, _('pretix account invitation'),
_('pretix account invitation'), 'pretixcontrol/email/invitation.txt',
'pretixcontrol/email/invitation.txt', {
{ 'user': self,
'user': self, 'organizer': self.context['organizer'].name,
'organizer': self.context['organizer'].name, 'team': instance.team.name,
'team': instance.team.name, 'url': build_global_uri('control:auth.invite', kwargs={
'url': build_global_uri('control:auth.invite', kwargs={ 'token': instance.token
'token': instance.token })
}) },
}, event=None,
event=None, locale=get_language_without_region() # TODO: expose?
locale=get_language_without_region() # TODO: expose? )
)
except SendMailException:
pass # Already logged
def create(self, validated_data): def create(self, validated_data):
if 'email' in validated_data: if 'email' in validated_data:
+5 -1
View File
@@ -188,11 +188,15 @@ class CheckinListViewSet(viewsets.ModelViewSet):
clist = self.get_object() clist = self.get_object()
if serializer.validated_data.get('nonce'): if serializer.validated_data.get('nonce'):
if kwargs.get('position'): if kwargs.get('position'):
prev = kwargs['position'].all_checkins.filter(nonce=serializer.validated_data['nonce']).first() prev = kwargs['position'].all_checkins.filter(
nonce=serializer.validated_data['nonce'],
successful=False
).first()
else: else:
prev = clist.checkins.filter( prev = clist.checkins.filter(
nonce=serializer.validated_data['nonce'], nonce=serializer.validated_data['nonce'],
raw_barcode=serializer.validated_data['raw_barcode'], raw_barcode=serializer.validated_data['raw_barcode'],
successful=False
).first() ).first()
if prev: if prev:
# Ignore because nonce is already handled # Ignore because nonce is already handled
+1 -11
View File
@@ -90,7 +90,6 @@ from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified, generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
regenerate_invoice, transmit_invoice, regenerate_invoice, transmit_invoice,
) )
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import ( from pretix.base.services.orders import (
OrderChangeManager, OrderError, _order_placed_email, OrderChangeManager, OrderError, _order_placed_email,
_order_placed_email_attendee, approve_order, cancel_order, deny_order, _order_placed_email_attendee, approve_order, cancel_order, deny_order,
@@ -439,8 +438,6 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e: except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
return self.retrieve(request, [], **kwargs) return self.retrieve(request, [], **kwargs)
return Response( return Response(
@@ -634,10 +631,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
order = self.get_object() order = self.get_object()
if not order.email: if not order.email:
return Response({'detail': 'There is no email address associated with this order.'}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': 'There is no email address associated with this order.'}, status=status.HTTP_400_BAD_REQUEST)
try: order.resend_link(user=self.request.user, auth=self.request.auth)
order.resend_link(user=self.request.user, auth=self.request.auth)
except SendMailException:
return Response({'detail': _('There was an error sending the mail. Please try again later.')}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
return Response( return Response(
status=status.HTTP_204_NO_CONTENT status=status.HTTP_204_NO_CONTENT
@@ -1616,8 +1610,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
) )
except Quota.QuotaExceededException: except Quota.QuotaExceededException:
pass pass
except SendMailException:
pass
serializer = OrderPaymentSerializer(r, context=serializer.context) serializer = OrderPaymentSerializer(r, context=serializer.context)
@@ -1655,8 +1647,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e: except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
return self.retrieve(request, [], **kwargs) return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST']) @action(detail=True, methods=['POST'])
+29 -8
View File
@@ -249,12 +249,24 @@ class GiftCardViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
value = serializer.validated_data.pop('value') value = serializer.validated_data.pop('value')
inst = serializer.save(issuer=self.request.organizer) inst = serializer.save(issuer=self.request.organizer)
inst.transactions.create(value=value, acceptor=self.request.organizer)
inst.log_action( inst.log_action(
'pretix.giftcards.transaction.manual', action='pretix.giftcards.created',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': inst.pk}) )
inst.transactions.create(value=value, acceptor=self.request.organizer)
inst.log_action(
action='pretix.giftcards.transaction.manual',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(
self.request.data,
{
'id': inst.pk,
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
)
) )
@transaction.atomic() @transaction.atomic()
@@ -269,7 +281,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency, inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
testmode=serializer.instance.testmode) testmode=serializer.instance.testmode)
inst.log_action( inst.log_action(
'pretix.giftcards.modified', action='pretix.giftcards.modified',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data=self.request.data, data=self.request.data,
@@ -282,10 +294,14 @@ class GiftCardViewSet(viewsets.ModelViewSet):
diff = value - old_value diff = value - old_value
inst.transactions.create(value=diff, acceptor=self.request.organizer) inst.transactions.create(value=diff, acceptor=self.request.organizer)
inst.log_action( inst.log_action(
'pretix.giftcards.transaction.manual', action='pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data={'value': diff} data={
'value': diff,
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
) )
return inst return inst
@@ -309,10 +325,15 @@ class GiftCardViewSet(viewsets.ModelViewSet):
}, status=status.HTTP_409_CONFLICT) }, status=status.HTTP_409_CONFLICT)
gc.transactions.create(value=value, text=text, info=info, acceptor=self.request.organizer) gc.transactions.create(value=value, text=text, info=info, acceptor=self.request.organizer)
gc.log_action( gc.log_action(
'pretix.giftcards.transaction.manual', action='pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data={'value': value, 'text': text} data={
'value': value,
'text': text,
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
) )
return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK) return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK)
+42
View File
@@ -174,6 +174,36 @@ class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
} }
class ParametrizedGiftcardWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
giftcard = logentry.content_object
if not giftcard:
return None
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'giftcard': giftcard.pk,
'action': logentry.action_type,
}
class ParametrizedGiftcardTransactionWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
giftcard = logentry.content_object
if not giftcard:
return None
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'acceptor_id': logentry.parsed_data.get('acceptor_id'),
'acceptor_slug': logentry.parsed_data.get('acceptor_slug'),
'giftcard': giftcard.pk,
'action': logentry.action_type,
}
class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent): class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry): def build_payload(self, logentry: LogEntry):
@@ -433,6 +463,18 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.customer.anonymized', 'pretix.customer.anonymized',
_('Customer account anonymized'), _('Customer account anonymized'),
), ),
ParametrizedGiftcardWebhookEvent(
'pretix.giftcards.created',
_('Gift card added'),
),
ParametrizedGiftcardWebhookEvent(
'pretix.giftcards.modified',
_('Gift card modified'),
),
ParametrizedGiftcardTransactionWebhookEvent(
'pretix.giftcards.transaction.*',
_('Gift card used in transcation'),
)
) )
+9 -7
View File
@@ -39,7 +39,7 @@ from pretix.base.templatetags.rich_text import (
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback, DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
markdown_compile_email, truelink_callback, markdown_compile_email, truelink_callback,
) )
from pretix.helpers.format import SafeFormatter, format_map from pretix.helpers.format import FormattedString, SafeFormatter, format_map
from pretix.base.services.placeholders import ( # noqa from pretix.base.services.placeholders import ( # noqa
get_available_placeholders, PlaceholderContext get_available_placeholders, PlaceholderContext
@@ -141,6 +141,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
return markdown_compile_email(plaintext, context=context) return markdown_compile_email(plaintext, context=context)
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str: def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
apply_format_map = not isinstance(plain_body, FormattedString)
body_md = self.compile_markdown(plain_body, context) body_md = self.compile_markdown(plain_body, context)
if context: if context:
linker = bleach.Linker( linker = bleach.Linker(
@@ -149,12 +150,13 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback], callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True parse_email=True
) )
body_md = format_map( if apply_format_map:
body_md, body_md = format_map(
context=context, body_md,
mode=SafeFormatter.MODE_RICH_TO_HTML, context=context,
linkifier=linker mode=SafeFormatter.MODE_RICH_TO_HTML,
) linkifier=linker
)
htmlctx = { htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME, 'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL, 'site_url': settings.SITE_URL,
+2
View File
@@ -651,6 +651,7 @@ class OrderListExporter(MultiSheetListExporter):
pgettext('address', 'State'), pgettext('address', 'State'),
_('Voucher'), _('Voucher'),
_('Voucher budget usage'), _('Voucher budget usage'),
_('Voucher tag'),
_('Pseudonymization ID'), _('Pseudonymization ID'),
_('Ticket secret'), _('Ticket secret'),
_('Seat ID'), _('Seat ID'),
@@ -769,6 +770,7 @@ class OrderListExporter(MultiSheetListExporter):
op.state_for_address or '', op.state_for_address or '',
op.voucher.code if op.voucher else '', op.voucher.code if op.voucher else '',
op.voucher_budget_use if op.voucher_budget_use else '', op.voucher_budget_use if op.voucher_budget_use else '',
op.voucher.tag if op.voucher else '',
op.pseudonymization_id, op.pseudonymization_id,
op.secret, op.secret,
] ]
+6 -6
View File
@@ -890,18 +890,18 @@ class BaseQuestionsForm(forms.Form):
if not help_text: if not help_text:
if q.valid_date_min and q.valid_date_max: if q.valid_date_min and q.valid_date_max:
help_text = format_lazy( help_text = format_lazy(
'Please enter a date between {min} and {max}.', _('Please enter a date between {min} and {max}.'),
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"), min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"), max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
) )
elif q.valid_date_min: elif q.valid_date_min:
help_text = format_lazy( help_text = format_lazy(
'Please enter a date no earlier than {min}.', _('Please enter a date no earlier than {min}.'),
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"), min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
) )
elif q.valid_date_max: elif q.valid_date_max:
help_text = format_lazy( help_text = format_lazy(
'Please enter a date no later than {max}.', _('Please enter a date no later than {max}.'),
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"), max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
) )
if initial and initial.answer: if initial and initial.answer:
@@ -939,18 +939,18 @@ class BaseQuestionsForm(forms.Form):
if not help_text: if not help_text:
if q.valid_datetime_min and q.valid_datetime_max: if q.valid_datetime_min and q.valid_datetime_max:
help_text = format_lazy( help_text = format_lazy(
'Please enter a date and time between {min} and {max}.', _('Please enter a date and time between {min} and {max}.'),
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"), min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"), max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
) )
elif q.valid_datetime_min: elif q.valid_datetime_min:
help_text = format_lazy( help_text = format_lazy(
'Please enter a date and time no earlier than {min}.', _('Please enter a date and time no earlier than {min}.'),
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"), min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
) )
elif q.valid_datetime_max: elif q.valid_datetime_max:
help_text = format_lazy( help_text = format_lazy(
'Please enter a date and time no later than {max}.', _('Please enter a date and time no later than {max}.'),
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"), max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
) )
+35 -39
View File
@@ -33,7 +33,7 @@ from pretix.base.invoicing.transmission import (
transmission_types, transmission_types,
) )
from pretix.base.models import Invoice, InvoiceAddress from pretix.base.models import Invoice, InvoiceAddress
from pretix.base.services.mail import SendMailException, mail, render_mail from pretix.base.services.mail import mail, render_mail
from pretix.helpers.format import format_map from pretix.helpers.format import format_map
@@ -133,41 +133,37 @@ class EmailTransmissionProvider(TransmissionProvider):
template = invoice.order.event.settings.get('mail_text_order_invoice', as_type=LazyI18nString) template = invoice.order.event.settings.get('mail_text_order_invoice', as_type=LazyI18nString)
subject = invoice.order.event.settings.get('mail_subject_order_invoice', as_type=LazyI18nString) subject = invoice.order.event.settings.get('mail_subject_order_invoice', as_type=LazyI18nString)
try: # Do not set to completed because that is done by the email sending task
# Do not set to completed because that is done by the email sending task subject = format_map(subject, context)
subject = format_map(subject, context) email_content = render_mail(template, context)
email_content = render_mail(template, context) mail(
mail( [recipient],
[recipient], subject,
subject, template,
template, context=context,
context=context, event=invoice.order.event,
event=invoice.order.event, locale=invoice.order.locale,
locale=invoice.order.locale, order=invoice.order,
order=invoice.order, invoices=[invoice],
invoices=[invoice], attach_tickets=False,
attach_tickets=False, auto_email=True,
auto_email=True, attach_ical=False,
attach_ical=False, plain_text_only=True,
plain_text_only=True, no_order_links=True,
no_order_links=True, )
) invoice.order.log_action(
except SendMailException: 'pretix.event.order.email.invoice',
raise user=None,
else: auth=None,
invoice.order.log_action( data={
'pretix.event.order.email.invoice', 'subject': subject,
user=None, 'message': email_content,
auth=None, 'position': None,
data={ 'recipient': recipient,
'subject': subject, 'invoices': [invoice.pk],
'message': email_content, 'attach_tickets': False,
'position': None, 'attach_ical': False,
'recipient': recipient, 'attach_other_files': [],
'invoices': [invoice.pk], 'attach_cached_files': [],
'attach_tickets': False, }
'attach_ical': False, )
'attach_other_files': [],
'attach_cached_files': [],
}
)
+17
View File
@@ -21,6 +21,7 @@
# #
from typing import Optional from typing import Optional
from django.utils.translation import gettext_lazy as _
from django_countries.fields import Country from django_countries.fields import Country
from pretix.base.models import Invoice, InvoiceAddress from pretix.base.models import Invoice, InvoiceAddress
@@ -106,6 +107,22 @@ class TransmissionType:
def transmission_info_to_form_data(self, transmission_info: dict) -> dict: def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
return transmission_info return transmission_info
def describe_info(self, transmission_info: dict, country: Country, is_business: bool):
form_data = self.transmission_info_to_form_data(transmission_info)
data = []
visible_field_keys = self.invoice_address_form_fields_visible(country, is_business)
for k, f in self.invoice_address_form_fields.items():
if k not in visible_field_keys:
continue
v = form_data.get(k)
if v is True:
v = _("Yes")
elif v is False:
v = _("No")
if v:
data.append((f.label, v))
return data
def pdf_watermark(self) -> Optional[str]: def pdf_watermark(self) -> Optional[str]:
""" """
Return a watermark that should be rendered across the PDF file. Return a watermark that should be rendered across the PDF file.
+21 -7
View File
@@ -294,14 +294,28 @@ def metric_values():
channel = app.broker_connection().channel() channel = app.broker_connection().channel()
if hasattr(channel, 'client') and channel.client is not None: if hasattr(channel, 'client') and channel.client is not None:
client = channel.client client = channel.client
priority_steps = settings.CELERY_BROKER_TRANSPORT_OPTIONS.get("priority_steps", [0])
sep = settings.CELERY_BROKER_TRANSPORT_OPTIONS.get("sep", ":")
for q in settings.CELERY_TASK_QUEUES: for q in settings.CELERY_TASK_QUEUES:
llen = client.llen(q.name) queue_lengths = []
lfirst = client.lindex(q.name, -1) queue_delays = []
metrics['pretix_celery_tasks_queued_count']['{queue="%s"}' % q.name] = llen for prio in priority_steps:
if lfirst: if prio:
ldata = json.loads(lfirst) qname = f"{q.name}{sep}{prio}"
dt = time.time() - ldata.get('created', 0) else:
metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = dt qname = q.name
queue_length = client.llen(qname)
queue_lengths.append(queue_length)
oldest_queue_item = client.lindex(qname, -1)
if oldest_queue_item:
ldata = json.loads(oldest_queue_item)
oldest_item_age = time.time() - ldata.get('created', 0)
queue_delays.append(oldest_item_age)
metrics['pretix_celery_tasks_queued_count']['{queue="%s"}' % q.name] = sum(queue_lengths)
if queue_delays:
metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = max(queue_delays)
else: else:
metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = 0 metrics['pretix_celery_tasks_queued_age_seconds']['{queue="%s"}' % q.name] = 0
@@ -0,0 +1,120 @@
# Generated by Django 4.2.26 on 2026-01-22 13:44
import uuid
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import pretix.base.models.mail
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0296_invoice_invoice_from_state"),
]
operations = [
migrations.CreateModel(
name="OutgoingMail",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("guid", models.UUIDField(db_index=True, default=uuid.uuid4)),
("status", models.CharField(default="queued", max_length=200)),
("created", models.DateTimeField(auto_now_add=True)),
("sent", models.DateTimeField(blank=True, null=True)),
("inflight_since", models.DateTimeField(blank=True, null=True)),
("retry_after", models.DateTimeField(blank=True, null=True)),
("error", models.TextField(null=True)),
("error_detail", models.TextField(null=True)),
("sensitive", models.BooleanField(default=False)),
("subject", models.TextField()),
("body_plain", models.TextField()),
("body_html", models.TextField(null=True)),
("sender", models.CharField(max_length=500)),
("headers", models.JSONField(default=dict)),
("to", models.JSONField(default=list)),
("cc", models.JSONField(default=list)),
("bcc", models.JSONField(default=list)),
("recipient_count", models.IntegerField()),
("should_attach_tickets", models.BooleanField(default=False)),
("should_attach_ical", models.BooleanField(default=False)),
("should_attach_other_files", models.JSONField(default=list)),
("actual_attachments", models.JSONField(default=list)),
(
"customer",
models.ForeignKey(
null=True,
on_delete=pretix.base.models.mail.CASCADE_IF_QUEUED,
related_name="outgoing_mails",
to="pretixbase.customer",
),
),
(
"event",
models.ForeignKey(
null=True,
on_delete=pretix.base.models.mail.CASCADE_IF_QUEUED,
related_name="outgoing_mails",
to="pretixbase.event",
),
),
(
"order",
models.ForeignKey(
null=True,
on_delete=pretix.base.models.mail.CASCADE_IF_QUEUED,
related_name="outgoing_mails",
to="pretixbase.order",
),
),
(
"orderposition",
models.ForeignKey(
null=True,
on_delete=pretix.base.models.mail.CASCADE_IF_QUEUED,
related_name="outgoing_mails",
to="pretixbase.orderposition",
),
),
(
"organizer",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="outgoing_mails",
to="pretixbase.organizer",
),
),
(
"should_attach_cached_files",
models.ManyToManyField(
related_name="outgoing_mails", to="pretixbase.cachedfile"
),
),
(
"should_attach_invoices",
models.ManyToManyField(
related_name="outgoing_mails", to="pretixbase.invoice"
),
),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="outgoing_mails",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ("-created",),
},
),
]
+3 -3
View File
@@ -132,7 +132,7 @@ class AllowIgnoreQuotaColumn(BooleanColumnMixin, ImportColumn):
class PriceModeColumn(ImportColumn): class PriceModeColumn(ImportColumn):
identifier = 'price_mode' identifier = 'price_mode'
verbose_name = gettext_lazy('Price mode') verbose_name = gettext_lazy('Price effect')
default_value = None default_value = None
initial = 'static:none' initial = 'static:none'
@@ -147,7 +147,7 @@ class PriceModeColumn(ImportColumn):
elif value in reverse: elif value in reverse:
return reverse[value] return reverse[value]
else: else:
raise ValidationError(_("Could not parse {value} as a price mode, use one of {options}.").format( raise ValidationError(_("Could not parse {value} as a price effect, use one of {options}.").format(
value=value, options=', '.join(d.keys()) value=value, options=', '.join(d.keys())
)) ))
@@ -162,7 +162,7 @@ class ValueColumn(DecimalColumnMixin, ImportColumn):
def clean(self, value, previous_values): def clean(self, value, previous_values):
value = super().clean(value, previous_values) value = super().clean(value, previous_values)
if value and previous_values.get("price_mode") == "none": if value and previous_values.get("price_mode") == "none":
raise ValidationError(_("It is pointless to set a value without a price mode.")) raise ValidationError(_("It is pointless to set a value without a price effect."))
return value return value
def assign(self, value, obj: Voucher, **kwargs): def assign(self, value, obj: Voucher, **kwargs):
+1
View File
@@ -41,6 +41,7 @@ from .items import (
itempicture_upload_to, itempicture_upload_to,
) )
from .log import LogEntry from .log import LogEntry
from .mail import OutgoingMail
from .media import ReusableMedium from .media import ReusableMedium
from .memberships import Membership, MembershipType from .memberships import Membership, MembershipType
from .notifications import NotificationSetting from .notifications import NotificationSetting
+16 -19
View File
@@ -334,27 +334,24 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
return self.email return self.email
def send_security_notice(self, messages, email=None): def send_security_notice(self, messages, email=None):
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import mail
try: with language(self.locale):
with language(self.locale): msg = '- ' + '\n- '.join(str(m) for m in messages)
msg = '- ' + '\n- '.join(str(m) for m in messages)
mail( mail(
email or self.email, email or self.email,
_('Account information changed'), _('Account information changed'),
'pretixcontrol/email/security_notice.txt', 'pretixcontrol/email/security_notice.txt',
{ {
'user': self, 'user': self,
'messages': msg, 'messages': msg,
'url': build_absolute_uri('control:user.settings') 'url': build_absolute_uri('control:user.settings')
}, },
event=None, event=None,
user=self, user=self,
locale=self.locale locale=self.locale
) )
except SendMailException:
pass # Already logged
def send_confirmation_code(self, session, reason, email=None, state=None): def send_confirmation_code(self, session, reason, email=None, state=None):
""" """
+2
View File
@@ -130,6 +130,8 @@ class LoggingMixin:
organizer_id = self.event.organizer_id organizer_id = self.event.organizer_id
elif hasattr(self, 'organizer_id'): elif hasattr(self, 'organizer_id'):
organizer_id = self.organizer_id organizer_id = self.organizer_id
elif hasattr(self, 'issuer_id'):
organizer_id = self.issuer_id
if user and not user.is_authenticated: if user and not user.is_authenticated:
user = None user = None
+24
View File
@@ -40,6 +40,7 @@ from i18nfield.fields import I18nCharField
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.banlist import banned from pretix.base.banlist import banned
from pretix.base.i18n import language
from pretix.base.models.base import LoggedModel from pretix.base.models.base import LoggedModel
from pretix.base.models.fields import MultiStringField from pretix.base.models.fields import MultiStringField
from pretix.base.models.giftcards import GiftCardTransaction from pretix.base.models.giftcards import GiftCardTransaction
@@ -164,6 +165,28 @@ class Customer(LoggedModel):
self.attendee_profiles.all().delete() self.attendee_profiles.all().delete()
self.invoice_addresses.all().delete() self.invoice_addresses.all().delete()
def send_security_notice(self, message, email=None):
from pretix.base.services.mail import SendMailException, mail
from pretix.multidomain.urlreverse import build_absolute_uri
try:
with language(self.locale):
mail(
email or self.email,
self.organizer.settings.mail_subject_customer_security_notice,
self.organizer.settings.mail_text_customer_security_notice,
{
**self.get_email_context(),
'message': str(message),
'url': build_absolute_uri(self.organizer, 'presale:organizer.customer.index')
},
customer=self,
organizer=self.organizer,
locale=self.locale
)
except SendMailException:
pass # Already logged
@scopes_disabled() @scopes_disabled()
def assign_identifier(self): def assign_identifier(self):
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ23456789') charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ23456789')
@@ -293,6 +316,7 @@ class Customer(LoggedModel):
locale=self.locale, locale=self.locale,
customer=self, customer=self,
organizer=self.organizer, organizer=self.organizer,
sensitive=True,
) )
def usable_gift_cards(self, used_cards=[]): def usable_gift_cards(self, used_cards=[]):
+222
View File
@@ -0,0 +1,222 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# 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 uuid
from django.core.mail import get_connection
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_scopes import scope, scopes_disabled
def CASCADE_IF_QUEUED(collector, field, sub_objs, using):
# If the email is still queued and the thing it is related to vanishes, the email can vanish as well
cascade_objs = [
o for o in sub_objs if o.status == OutgoingMail.STATUS_QUEUED
]
if cascade_objs:
models.CASCADE(collector, field, cascade_objs, using)
# In all other cases, set to NULL to keep the email on record
models.SET_NULL(collector, field, [o for o in sub_objs if o not in cascade_objs], using)
class OutgoingMail(models.Model):
STATUS_QUEUED = "queued"
STATUS_WITHHELD = "withheld"
STATUS_INFLIGHT = "inflight"
STATUS_AWAITING_RETRY = "awaiting_retry"
STATUS_FAILED = "failed"
STATUS_SENT = "sent"
STATUS_BOUNCED = "bounced"
STATUS_ABORTED = "aborted"
STATUS_CHOICES = (
(STATUS_QUEUED, _("queued")),
(STATUS_INFLIGHT, _("being sent")),
(STATUS_AWAITING_RETRY, _("awaiting retry")),
(STATUS_WITHHELD, _("withheld")), # for plugin use
(STATUS_FAILED, _("failed")),
(STATUS_ABORTED, _("aborted")),
(STATUS_SENT, _("sent")),
(STATUS_BOUNCED, _("bounced")), # for plugin use
)
STATUS_LIST_ABORTABLE = {
STATUS_QUEUED,
STATUS_WITHHELD,
STATUS_AWAITING_RETRY,
}
STATUS_LIST_RETRYABLE = {
STATUS_FAILED,
STATUS_WITHHELD,
}
# The GUID is a globally unique ID for the email added to a header of the email for later tracing
# in bug reports etc. We could theoretically also use this as a basis for the Message-ID header, but
# we currently don't since we are unsure if some intermediary SMTP servers have opinions on setting
# their own Message-ID headers.
guid = models.UUIDField(db_index=True, default=uuid.uuid4)
status = models.CharField(max_length=200, choices=STATUS_CHOICES, default=STATUS_QUEUED)
created = models.DateTimeField(auto_now_add=True)
# sent will be the time the email was sent or the email failed
sent = models.DateTimeField(null=True, blank=True)
inflight_since = models.DateTimeField(null=True, blank=True)
retry_after = models.DateTimeField(null=True, blank=True)
error = models.TextField(null=True, blank=True)
error_detail = models.TextField(null=True, blank=True)
# There is a conflict here between the different purposes of the model. As a system administrator,
# one wants *all* emails to be persisted as long as possible to debug issues. This means that if
# e.g. the event or order is deleted, we want SET_NULL behavior. However, in that case, the email
# would be an "orphan" forever and there's no way to remove the personal information.
# We try to find a middle-ground with the following behaviour:
# - The email is always deleted if the entire organizer or user is deleted
# - The email is always deleted if it has not yet been sent
# - The email is kept in all other cases
# This is only an acceptable trade-off since emails are stored for a short period only, and because
# orders and customers are never deleted during normal operation. If we ever make this a long-term
# storage / email archive, we'd need to find another way to make sure personal information is removed
# if personal information of orders etc is removed.
organizer = models.ForeignKey(
'pretixbase.Organizer',
on_delete=models.CASCADE,
related_name='outgoing_mails',
null=True, blank=True,
)
event = models.ForeignKey(
'pretixbase.Event',
on_delete=CASCADE_IF_QUEUED,
related_name='outgoing_mails',
null=True, blank=True,
)
order = models.ForeignKey(
'pretixbase.Order',
on_delete=CASCADE_IF_QUEUED,
related_name='outgoing_mails',
null=True, blank=True,
)
orderposition = models.ForeignKey(
'pretixbase.OrderPosition',
on_delete=CASCADE_IF_QUEUED,
related_name='outgoing_mails',
null=True, blank=True,
)
customer = models.ForeignKey(
'pretixbase.Customer',
on_delete=CASCADE_IF_QUEUED,
related_name='outgoing_mails',
null=True, blank=True,
)
user = models.ForeignKey(
'pretixbase.User',
on_delete=models.CASCADE,
related_name='outgoing_mails',
null=True, blank=True,
)
sensitive = models.BooleanField(default=False)
subject = models.TextField()
body_plain = models.TextField()
body_html = models.TextField(null=True)
sender = models.CharField(max_length=500)
headers = models.JSONField(default=dict)
to = models.JSONField(default=list)
cc = models.JSONField(default=list)
bcc = models.JSONField(default=list)
recipient_count = models.IntegerField()
# We don't store the actual invoices, tickets or calendar invites, so if the email is re-sent at a later time, a
# newer version of the files might be used. We accept that risk to save on storage and also because the new
# version might actually be more useful.
should_attach_invoices = models.ManyToManyField(
'pretixbase.Invoice',
related_name='outgoing_mails'
)
should_attach_tickets = models.BooleanField(default=False)
should_attach_ical = models.BooleanField(default=False)
# clean_cached_files makes sure not to delete these as long as the email is in a retryable state
should_attach_cached_files = models.ManyToManyField(
'pretixbase.CachedFile',
related_name='outgoing_mails',
)
# This is used to send files stored in settings. In most cases, these aren't short-lived and should still be there
# if the email is sent. Otherwise, they will be skipped. We accept that risk.
should_attach_other_files = models.JSONField(default=list)
# [{name, type size}] of the attachments we actually setn
actual_attachments = models.JSONField(default=list)
class Meta:
ordering = ('-created',)
def get_mail_backend(self):
if self.event:
return self.event.get_mail_backend()
elif self.organizer:
return self.organizer.get_mail_backend()
else:
return get_connection(fail_silently=False)
def scope_manager(self):
if self.organizer:
return scope(organizer=self.organizer) # noqa
else:
return scopes_disabled() # noqa
@property
def is_failed(self):
return self.status in (
OutgoingMail.STATUS_FAILED,
OutgoingMail.STATUS_AWAITING_RETRY,
OutgoingMail.STATUS_BOUNCED,
)
def save(self, *args, **kwargs):
if self.orderposition_id and not self.order_id:
self.order = self.orderposition.order
if self.order_id and not self.event_id:
self.event = self.order.event
if self.event_id and not self.organizer_id:
self.organizer = self.event.organizer
if self.customer_id and not self.organizer_id:
self.organizer = self.customer.organizer
self.recipient_count = len(self.to) + len(self.cc) + len(self.bcc)
super().save(*args, **kwargs)
def log_parameters(self):
if self.order:
error_log_action_type = 'pretix.event.order.email.error'
log_target = self.order
elif self.customer:
error_log_action_type = 'pretix.customer.email.error'
log_target = self.customer
elif self.user:
error_log_action_type = 'pretix.user.email.error'
log_target = self.user
else:
error_log_action_type = 'pretix.email.error'
log_target = None
return log_target, error_log_action_type
+70 -98
View File
@@ -87,7 +87,7 @@ from pretix.base.timemachine import time_machine_now
from ...helpers import OF_SELF from ...helpers import OF_SELF
from ...helpers.countries import CachedCountries, FastCountryField from ...helpers.countries import CachedCountries, FastCountryField
from ...helpers.format import format_map from ...helpers.format import FormattedString, format_map
from ...helpers.names import build_name from ...helpers.names import build_name
from ...testutils.middleware import debugflags_var from ...testutils.middleware import debugflags_var
from ._transactions import ( from ._transactions import (
@@ -1167,9 +1167,7 @@ class Order(LockModel, LoggedModel):
only be attached for this position and child positions, the link will only point to the only be attached for this position and child positions, the link will only point to the
position and the attendee email will be used if available. position and the attendee email will be used if available.
""" """
from pretix.base.services.mail import ( from pretix.base.services.mail import mail, render_mail
SendMailException, mail, render_mail,
)
if not self.email and not (position and position.attendee_email): if not self.email and not (position and position.attendee_email):
return return
@@ -1179,35 +1177,32 @@ class Order(LockModel, LoggedModel):
if position and position.attendee_email: if position and position.attendee_email:
recipient = position.attendee_email recipient = position.attendee_email
try: email_content = render_mail(template, context)
email_content = render_mail(template, context) if not isinstance(subject, FormattedString):
subject = format_map(subject, context) subject = format_map(subject, context)
mail( mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.locale, self, headers=headers, sender=sender, self.event, self.locale, self, headers=headers, sender=sender,
invoices=invoices, attach_tickets=attach_tickets, invoices=invoices, attach_tickets=attach_tickets,
position=position, auto_email=auto_email, attach_ical=attach_ical, position=position, auto_email=auto_email, attach_ical=attach_ical,
attach_other_files=attach_other_files, attach_cached_files=attach_cached_files, attach_other_files=attach_other_files, attach_cached_files=attach_cached_files,
) )
except SendMailException: self.log_action(
raise log_entry_type,
else: user=user,
self.log_action( auth=auth,
log_entry_type, data={
user=user, 'subject': subject,
auth=auth, 'message': email_content,
data={ 'position': position.positionid if position else None,
'subject': subject, 'recipient': recipient,
'message': email_content, 'invoices': [i.pk for i in invoices] if invoices else [],
'position': position.positionid if position else None, 'attach_tickets': attach_tickets,
'recipient': recipient, 'attach_ical': attach_ical,
'invoices': [i.pk for i in invoices] if invoices else [], 'attach_other_files': attach_other_files,
'attach_tickets': attach_tickets, 'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files else [],
'attach_ical': attach_ical, }
'attach_other_files': attach_other_files, )
'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files else [],
}
)
def resend_link(self, user=None, auth=None): def resend_link(self, user=None, auth=None):
with language(self.locale, self.event.settings.region): with language(self.locale, self.event.settings.region):
@@ -2024,40 +2019,30 @@ class OrderPayment(models.Model):
transmit_invoice.apply_async(args=(self.order.event_id, invoice.pk, False)) transmit_invoice.apply_async(args=(self.order.event_id, invoice.pk, False))
def _send_paid_mail_attendee(self, position, user): def _send_paid_mail_attendee(self, position, user):
from pretix.base.services.mail import SendMailException
with language(self.order.locale, self.order.event.settings.region): with language(self.order.locale, self.order.event.settings.region):
email_template = self.order.event.settings.mail_text_order_paid_attendee email_template = self.order.event.settings.mail_text_order_paid_attendee
email_subject = self.order.event.settings.mail_subject_order_paid_attendee email_subject = self.order.event.settings.mail_subject_order_paid_attendee
email_context = get_email_context(event=self.order.event, order=self.order, position=position) email_context = get_email_context(event=self.order.event, order=self.order, position=position)
try: position.send_mail(
position.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.order_paid', user,
'pretix.event.order.email.order_paid', user, invoices=[],
invoices=[], attach_tickets=True,
attach_tickets=True, attach_ical=self.order.event.settings.mail_attach_ical
attach_ical=self.order.event.settings.mail_attach_ical )
)
except SendMailException:
logger.exception('Order paid email could not be sent')
def _send_paid_mail(self, invoice, user, mail_text): def _send_paid_mail(self, invoice, user, mail_text):
from pretix.base.services.mail import SendMailException
with language(self.order.locale, self.order.event.settings.region): with language(self.order.locale, self.order.event.settings.region):
email_template = self.order.event.settings.mail_text_order_paid email_template = self.order.event.settings.mail_text_order_paid
email_subject = self.order.event.settings.mail_subject_order_paid email_subject = self.order.event.settings.mail_subject_order_paid
email_context = get_email_context(event=self.order.event, order=self.order, payment_info=mail_text) email_context = get_email_context(event=self.order.event, order=self.order, payment_info=mail_text)
try: self.order.send_mail(
self.order.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.order_paid', user,
'pretix.event.order.email.order_paid', user, invoices=[invoice] if invoice else [],
invoices=[invoice] if invoice else [], attach_tickets=True,
attach_tickets=True, attach_ical=self.order.event.settings.mail_attach_ical
attach_ical=self.order.event.settings.mail_attach_ical )
)
except SendMailException:
logger.exception('Order paid email could not be sent')
@property @property
def refunded_amount(self): def refunded_amount(self):
@@ -2915,45 +2900,40 @@ class OrderPosition(AbstractPosition):
:param attach_tickets: Attach tickets of this order, if they are existing and ready to download :param attach_tickets: Attach tickets of this order, if they are existing and ready to download
:param attach_ical: Attach relevant ICS files :param attach_ical: Attach relevant ICS files
""" """
from pretix.base.services.mail import ( from pretix.base.services.mail import mail, render_mail
SendMailException, mail, render_mail,
)
if not self.attendee_email: if not self.attendee_email:
return return
with language(self.order.locale, self.order.event.settings.region): with language(self.order.locale, self.order.event.settings.region):
recipient = self.attendee_email recipient = self.attendee_email
try: email_content = render_mail(template, context)
email_content = render_mail(template, context) if not isinstance(subject, FormattedString):
subject = format_map(subject, context) subject = format_map(subject, context)
mail( mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender, self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
position=self, position=self,
invoices=invoices, invoices=invoices,
attach_tickets=attach_tickets, attach_tickets=attach_tickets,
attach_ical=attach_ical, attach_ical=attach_ical,
attach_other_files=attach_other_files, attach_other_files=attach_other_files,
) )
except SendMailException: self.order.log_action(
raise log_entry_type,
else: user=user,
self.order.log_action( auth=auth,
log_entry_type, data={
user=user, 'subject': subject,
auth=auth, 'message': email_content,
data={ 'recipient': recipient,
'subject': subject, 'invoices': [i.pk for i in invoices] if invoices else [],
'message': email_content, 'attach_tickets': attach_tickets,
'recipient': recipient, 'attach_ical': attach_ical,
'invoices': [i.pk for i in invoices] if invoices else [], 'attach_other_files': attach_other_files,
'attach_tickets': attach_tickets, 'attach_cached_files': [],
'attach_ical': attach_ical, }
'attach_other_files': attach_other_files, )
'attach_cached_files': [],
}
)
def resend_link(self, user=None, auth=None): def resend_link(self, user=None, auth=None):
@@ -3529,18 +3509,10 @@ class InvoiceAddress(models.Model):
def describe_transmission(self): def describe_transmission(self):
from pretix.base.invoicing.transmission import transmission_types from pretix.base.invoicing.transmission import transmission_types
data = [] data = []
t, __ = transmission_types.get(identifier=self.transmission_type) t, __ = transmission_types.get(identifier=self.transmission_type)
data.append((_("Transmission type"), t.public_name)) data.append((_("Transmission type"), t.public_name))
form_data = t.transmission_info_to_form_data(self.transmission_info or {}) if self.transmission_info:
for k, f in t.invoice_address_form_fields.items(): data += t.describe_info(self.transmission_info, self.country, self.is_business)
v = form_data.get(k)
if v is True:
v = _("Yes")
elif v is False:
v = _("No")
if v:
data.append((f.label, v))
return data return data
+1 -1
View File
@@ -239,7 +239,7 @@ class Voucher(LoggedModel):
) )
) )
price_mode = models.CharField( price_mode = models.CharField(
verbose_name=_("Price mode"), verbose_name=_("Price effect"),
max_length=100, max_length=100,
choices=PRICE_MODES, choices=PRICE_MODES,
default='none' default='none'
+25 -29
View File
@@ -34,7 +34,7 @@ from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.email import get_email_context from pretix.base.email import get_email_context
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import User, Voucher from pretix.base.models import User, Voucher
from pretix.base.services.mail import SendMailException, mail, render_mail from pretix.base.services.mail import mail, render_mail
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from ...helpers.format import format_map from ...helpers.format import format_map
@@ -272,34 +272,30 @@ class WaitingListEntry(LoggedModel):
with language(self.locale, self.event.settings.region): with language(self.locale, self.event.settings.region):
recipient = self.email recipient = self.email
try: email_content = render_mail(template, context)
email_content = render_mail(template, context) subject = format_map(subject, context)
subject = format_map(subject, context) mail(
mail( recipient, subject, template, context,
recipient, subject, template, context, self.event,
self.event, self.locale,
self.locale, headers=headers,
headers=headers, sender=sender,
sender=sender, auto_email=auto_email,
auto_email=auto_email, attach_other_files=attach_other_files,
attach_other_files=attach_other_files, attach_cached_files=attach_cached_files,
attach_cached_files=attach_cached_files, )
) self.log_action(
except SendMailException: log_entry_type,
raise user=user,
else: auth=auth,
self.log_action( data={
log_entry_type, 'subject': subject,
user=user, 'message': email_content,
auth=auth, 'recipient': recipient,
data={ 'attach_other_files': attach_other_files,
'subject': subject, 'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files else [],
'message': email_content, }
'recipient': recipient, )
'attach_other_files': attach_other_files,
'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files else [],
}
)
@staticmethod @staticmethod
def clean_itemvar(event, item, variation): def clean_itemvar(event, item, variation):
+19 -2
View File
@@ -1231,8 +1231,8 @@ class ManualPayment(BasePaymentProvider):
def is_allowed(self, request: HttpRequest, total: Decimal=None): def is_allowed(self, request: HttpRequest, total: Decimal=None):
return 'pretix.plugins.manualpayment' in self.event.plugins and super().is_allowed(request, total) return 'pretix.plugins.manualpayment' in self.event.plugins and super().is_allowed(request, total)
def order_change_allowed(self, order: Order): def order_change_allowed(self, order: Order, request=None):
return 'pretix.plugins.manualpayment' in self.event.plugins and super().order_change_allowed(order) return 'pretix.plugins.manualpayment' in self.event.plugins and super().order_change_allowed(order, request)
@property @property
def public_name(self): def public_name(self):
@@ -1646,6 +1646,14 @@ class GiftCardPayment(BasePaymentProvider):
'transaction_id': trans.pk, 'transaction_id': trans.pk,
} }
payment.confirm(send_mail=not is_early_special_case, generate_invoice=not is_early_special_case) payment.confirm(send_mail=not is_early_special_case, generate_invoice=not is_early_special_case)
gc.log_action(
action='pretix.giftcards.transaction.payment',
data={
'value': trans.value,
'acceptor_id': self.event.organizer.id,
'acceptor_slug': self.event.organizer.slug
}
)
except PaymentException as e: except PaymentException as e:
payment.fail(info={'error': str(e)}) payment.fail(info={'error': str(e)})
raise e raise e
@@ -1670,6 +1678,15 @@ class GiftCardPayment(BasePaymentProvider):
'transaction_id': trans.pk, 'transaction_id': trans.pk,
} }
refund.done() refund.done()
gc.log_action(
action='pretix.giftcards.transaction.refund',
data={
'value': refund.amount,
'acceptor_id': self.event.organizer.id,
'acceptor_slug': self.event.organizer.slug,
'text': refund.comment,
}
)
@receiver(register_payment_providers, dispatch_uid="payment_free") @receiver(register_payment_providers, dispatch_uid="payment_free")
+1 -1
View File
@@ -65,7 +65,7 @@ def get_all_plugins(*, event=None, organizer=None) -> List[type]:
if app.name in settings.PRETIX_PLUGINS_EXCLUDE: if app.name in settings.PRETIX_PLUGINS_EXCLUDE:
continue continue
level = getattr(app, "level", PLUGIN_LEVEL_EVENT) level = getattr(meta, "level", PLUGIN_LEVEL_EVENT)
if level == PLUGIN_LEVEL_EVENT: if level == PLUGIN_LEVEL_EVENT:
if event and hasattr(app, 'is_available'): if event and hasattr(app, 'is_available'):
if not app.is_available(event): if not app.is_available(event):
+20 -29
View File
@@ -36,7 +36,7 @@ from pretix.base.models import (
SubEvent, TaxRule, User, WaitingListEntry, SubEvent, TaxRule, User, WaitingListEntry,
) )
from pretix.base.services.locking import LockTimeoutException from pretix.base.services.locking import LockTimeoutException
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import mail
from pretix.base.services.orders import ( from pretix.base.services.orders import (
OrderChangeManager, OrderError, _cancel_order, _try_auto_refund, OrderChangeManager, OrderError, _cancel_order, _try_auto_refund,
) )
@@ -53,17 +53,14 @@ logger = logging.getLogger(__name__)
def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent): def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent):
with language(wle.locale, wle.event.settings.region): with language(wle.locale, wle.event.settings.region):
email_context = get_email_context(event_or_subevent=subevent or wle.event, event=wle.event) email_context = get_email_context(event_or_subevent=subevent or wle.event, event=wle.event)
try: mail(
mail( wle.email,
wle.email, format_map(subject, email_context),
format_map(subject, email_context), message,
message, email_context,
email_context, wle.event,
wle.event, locale=wle.locale
locale=wle.locale )
)
except SendMailException:
logger.exception('Waiting list canceled email could not be sent')
def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent, def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent,
@@ -77,14 +74,11 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
email_context = get_email_context(event_or_subevent=subevent or order.event, refund_amount=refund_amount, email_context = get_email_context(event_or_subevent=subevent or order.event, refund_amount=refund_amount,
order=order, position_or_address=ia, event=order.event) order=order, position_or_address=ia, event=order.event)
real_subject = format_map(subject, email_context) real_subject = format_map(subject, email_context)
try: order.send_mail(
order.send_mail( real_subject, message, email_context,
real_subject, message, email_context, 'pretix.event.order.email.event_canceled',
'pretix.event.order.email.event_canceled', user,
user, )
)
except SendMailException:
logger.exception('Order canceled email could not be sent')
for p in positions: for p in positions:
if subevent and p.subevent_id != subevent.id: if subevent and p.subevent_id != subevent.id:
@@ -97,15 +91,12 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
refund_amount=refund_amount, refund_amount=refund_amount,
position_or_address=p, position_or_address=p,
order=order, position=p) order=order, position=p)
try: order.send_mail(
order.send_mail( real_subject, message, email_context,
real_subject, message, email_context, 'pretix.event.order.email.event_canceled',
'pretix.event.order.email.event_canceled', position=p,
position=p, user=user
user=user )
)
except SendMailException:
logger.exception('Order canceled email could not be sent to attendee')
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,)) @app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
+2 -2
View File
@@ -1639,7 +1639,7 @@ def get_fees(event, request, _total_ignored_=None, invoice_address=None, payment
if fee.tax_rule and not fee.tax_rule.pk: if fee.tax_rule and not fee.tax_rule.pk:
fee.tax_rule = None # TODO: deprecate fee.tax_rule = None # TODO: deprecate
apply_rounding(event.settings.tax_rounding, event.currency, [*positions, *fees]) apply_rounding(event.settings.tax_rounding, invoice_address, event.currency, [*positions, *fees])
total = sum([c.price for c in positions]) + sum([f.value for f in fees]) total = sum([c.price for c in positions]) + sum([f.value for f in fees])
if total != 0 and payments: if total != 0 and payments:
@@ -1679,7 +1679,7 @@ def get_fees(event, request, _total_ignored_=None, invoice_address=None, payment
fees.append(pf) fees.append(pf)
# Re-apply rounding as grand total has changed # Re-apply rounding as grand total has changed
apply_rounding(event.settings.tax_rounding, event.currency, [*positions, *fees]) apply_rounding(event.settings.tax_rounding, invoice_address, event.currency, [*positions, *fees])
total = sum([c.price for c in positions]) + sum([f.value for f in fees]) total = sum([c.price for c in positions]) + sum([f.value for f in fees])
# Re-calculate to_pay as grand total has changed # Re-calculate to_pay as grand total has changed
+14 -2
View File
@@ -23,11 +23,12 @@ from datetime import timedelta
from django.conf import settings from django.conf import settings
from django.core.management import call_command from django.core.management import call_command
from django.db.models import Exists, OuterRef
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import now from django.utils.timezone import now
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pretix.base.models import CachedCombinedTicket, CachedTicket from pretix.base.models import CachedCombinedTicket, CachedTicket, OutgoingMail
from pretix.base.models.customers import CustomerSSOGrant from pretix.base.models.customers import CustomerSSOGrant
from ..models import CachedFile, CartPosition, InvoiceAddress from ..models import CachedFile, CartPosition, InvoiceAddress
@@ -49,7 +50,18 @@ def clean_cart_positions(sender, **kwargs):
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@scopes_disabled() @scopes_disabled()
def clean_cached_files(sender, **kwargs): def clean_cached_files(sender, **kwargs):
for cf in CachedFile.objects.filter(expires__isnull=False, expires__lt=now()): has_queued_email = Exists(
OutgoingMail.objects.filter(
should_attach_cached_files__pk=OuterRef("pk"),
status__in=(
OutgoingMail.STATUS_QUEUED,
OutgoingMail.STATUS_INFLIGHT,
OutgoingMail.STATUS_AWAITING_RETRY,
OutgoingMail.STATUS_FAILED,
),
)
)
for cf in CachedFile.objects.filter(expires__isnull=False, expires__lt=now()).exclude(has_queued_email):
cf.delete() cf.delete()
File diff suppressed because it is too large Load Diff
+23 -9
View File
@@ -19,6 +19,8 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # 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/>. # <https://www.gnu.org/licenses/>.
# #
import uuid
import css_inline import css_inline
from django.conf import settings from django.conf import settings
from django.template.loader import get_template from django.template.loader import get_template
@@ -26,7 +28,9 @@ from django.utils.timezone import override
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import LogEntry, NotificationSetting, User from pretix.base.models import (
LogEntry, NotificationSetting, OutgoingMail, User,
)
from pretix.base.notifications import Notification, get_all_notification_types from pretix.base.notifications import Notification, get_all_notification_types
from pretix.base.services.mail import mail_send_task from pretix.base.services.mail import mail_send_task
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
@@ -153,16 +157,26 @@ def send_notification_mail(notification: Notification, user: User):
tpl_plain = get_template('pretixbase/email/notification.txt') tpl_plain = get_template('pretixbase/email/notification.txt')
body_plain = tpl_plain.render(ctx) body_plain = tpl_plain.render(ctx)
mail_send_task.apply_async(kwargs={ guid = uuid.uuid4()
'to': [user.email], m = OutgoingMail.objects.create(
'subject': '[{}] {}: {}'.format( guid=guid,
user=user,
to=[user.email],
subject='[{}] {}: {}'.format(
settings.PRETIX_INSTANCE_NAME, settings.PRETIX_INSTANCE_NAME,
notification.event.settings.mail_prefix or notification.event.slug.upper(), notification.event.settings.mail_prefix or notification.event.slug.upper(),
notification.title notification.title
), ),
'body': body_plain, body_plain=body_plain,
'html': body_html, body_html=body_html,
'sender': settings.MAIL_FROM_NOTIFICATIONS, sender=settings.MAIL_FROM_NOTIFICATIONS,
'headers': {}, headers={
'user': user.pk 'X-Auto-Response-Suppress': 'OOF, NRN, AutoReply, RN',
'Auto-Submitted': 'auto-generated',
'X-Mailer': 'pretix',
'X-PX-Correlation': str(guid),
},
)
mail_send_task.apply_async(kwargs={
'outgoing_mail': m.pk,
}) })
+191 -146
View File
@@ -90,7 +90,6 @@ from pretix.base.services.invoices import (
from pretix.base.services.locking import ( from pretix.base.services.locking import (
LOCK_TRUST_WINDOW, LockTimeoutException, lock_objects, LOCK_TRUST_WINDOW, LockTimeoutException, lock_objects,
) )
from pretix.base.services.mail import SendMailException
from pretix.base.services.memberships import ( from pretix.base.services.memberships import (
create_membership, validate_memberships_in_order, create_membership, validate_memberships_in_order,
) )
@@ -248,6 +247,16 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
for gc in position.issued_gift_cards.all(): for gc in position.issued_gift_cards.all():
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=gc.pk) gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=gc.pk)
gc.transactions.create(value=position.price, order=order, acceptor=order.event.organizer) gc.transactions.create(value=position.price, order=order, acceptor=order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=user,
auth=auth,
data={
'value': position.price,
'acceptor_id': order.event.organizer.id,
'acceptor_slug': order.event.organizer.slug
}
)
break break
for m in position.granted_memberships.all(): for m in position.granted_memberships.all():
@@ -438,33 +447,27 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
email_attendee_subject = order.event.settings.mail_subject_order_approved_attendee email_attendee_subject = order.event.settings.mail_subject_order_approved_attendee
email_context = get_email_context(event=order.event, order=order) email_context = get_email_context(event=order.event, order=order)
try: order.send_mail(
order.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.order_approved', user,
'pretix.event.order.email.order_approved', user, attach_tickets=True,
attach_tickets=True, attach_ical=order.event.settings.mail_attach_ical and (
attach_ical=order.event.settings.mail_attach_ical and ( not order.event.settings.mail_attach_ical_paid_only or
not order.event.settings.mail_attach_ical_paid_only or order.total == Decimal('0.00') or
order.total == Decimal('0.00') or order.valid_if_pending
order.valid_if_pending ),
), invoices=[invoice] if invoice and transmit_invoice_mail else []
invoices=[invoice] if invoice and transmit_invoice_mail else [] )
)
except SendMailException:
logger.exception('Order approved email could not be sent')
if email_attendees: if email_attendees:
for p in order.positions.all(): for p in order.positions.all():
if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email: if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email:
email_attendee_context = get_email_context(event=order.event, order=order, position=p) email_attendee_context = get_email_context(event=order.event, order=order, position=p)
try: p.send_mail(
p.send_mail( email_attendee_subject, email_attendee_template, email_attendee_context,
email_attendee_subject, email_attendee_template, email_attendee_context, 'pretix.event.order.email.order_approved', user,
'pretix.event.order.email.order_approved', user, attach_tickets=True,
attach_tickets=True, )
)
except SendMailException:
logger.exception('Order approved email could not be sent to attendee')
return order.pk return order.pk
@@ -501,13 +504,10 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None):
email_template = order.event.settings.mail_text_order_denied email_template = order.event.settings.mail_text_order_denied
email_subject = order.event.settings.mail_subject_order_denied email_subject = order.event.settings.mail_subject_order_denied
email_context = get_email_context(event=order.event, order=order, comment=comment) email_context = get_email_context(event=order.event, order=order, comment=comment)
try: order.send_mail(
order.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.order_denied', user
'pretix.event.order.email.order_denied', user )
)
except SendMailException:
logger.exception('Order denied email could not be sent')
return order.pk return order.pk
@@ -558,6 +558,15 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
) )
else: else:
gc.transactions.create(value=-position.price, order=order, acceptor=order.event.organizer) gc.transactions.create(value=-position.price, order=order, acceptor=order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=user,
data={
'value': -position.price,
'acceptor_id': order.event.organizer.id,
'acceptor_slug': order.event.organizer.slug
}
)
for m in position.granted_memberships.all(): for m in position.granted_memberships.all():
m.canceled = True m.canceled = True
@@ -660,14 +669,11 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
email_template = order.event.settings.mail_text_order_canceled email_template = order.event.settings.mail_text_order_canceled
email_subject = order.event.settings.mail_subject_order_canceled email_subject = order.event.settings.mail_subject_order_canceled
email_context = get_email_context(event=order.event, order=order, comment=comment or "") email_context = get_email_context(event=order.event, order=order, comment=comment or "")
try: order.send_mail(
order.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.order_canceled', user,
'pretix.event.order.email.order_canceled', user, invoices=transmit_invoices_mail,
invoices=transmit_invoices_mail, )
)
except SendMailException:
logger.exception('Order canceled email could not be sent')
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING)): for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING)):
try: try:
@@ -968,7 +974,7 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
fee.tax_rule = None # TODO: deprecate fee.tax_rule = None # TODO: deprecate
# Apply rounding to get final total in case no payment fees will be added # Apply rounding to get final total in case no payment fees will be added
apply_rounding(event.settings.tax_rounding, event.currency, [*positions, *fees]) apply_rounding(event.settings.tax_rounding, address, event.currency, [*positions, *fees])
total = sum([c.price for c in positions]) + sum([f.value for f in fees]) total = sum([c.price for c in positions]) + sum([f.value for f in fees])
payments_assigned = Decimal("0.00") payments_assigned = Decimal("0.00")
@@ -995,7 +1001,7 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
p['fee'] = pf p['fee'] = pf
# Re-apply rounding as grand total has changed # Re-apply rounding as grand total has changed
apply_rounding(event.settings.tax_rounding, event.currency, [*positions, *fees]) apply_rounding(event.settings.tax_rounding, address, event.currency, [*positions, *fees])
total = sum([c.price for c in positions]) + sum([f.value for f in fees]) total = sum([c.price for c in positions]) + sum([f.value for f in fees])
# Re-calculate to_pay as grand total has changed # Re-calculate to_pay as grand total has changed
@@ -1108,46 +1114,40 @@ def _order_placed_email(event: Event, order: Order, email_template, subject_temp
log_entry: str, invoice, payments: List[OrderPayment], is_free=False): log_entry: str, invoice, payments: List[OrderPayment], is_free=False):
email_context = get_email_context(event=event, order=order, payments=payments) email_context = get_email_context(event=event, order=order, payments=payments)
try: order.send_mail(
order.send_mail( subject_template, email_template, email_context,
subject_template, email_template, email_context, log_entry,
log_entry, invoices=[invoice] if invoice else [],
invoices=[invoice] if invoice else [], attach_tickets=True,
attach_tickets=True, attach_ical=event.settings.mail_attach_ical and (
attach_ical=event.settings.mail_attach_ical and ( not event.settings.mail_attach_ical_paid_only or
not event.settings.mail_attach_ical_paid_only or is_free or
is_free or order.valid_if_pending
order.valid_if_pending ),
), attach_other_files=[a for a in [
attach_other_files=[a for a in [ event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):] ] if a],
] if a], )
)
except SendMailException:
logger.exception('Order received email could not be sent')
def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosition, email_template, subject_template, def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosition, email_template, subject_template,
log_entry: str, is_free=False): log_entry: str, is_free=False):
email_context = get_email_context(event=event, order=order, position=position) email_context = get_email_context(event=event, order=order, position=position)
try: position.send_mail(
position.send_mail( subject_template, email_template, email_context,
subject_template, email_template, email_context, log_entry,
log_entry, invoices=[],
invoices=[], attach_tickets=True,
attach_tickets=True, attach_ical=event.settings.mail_attach_ical and (
attach_ical=event.settings.mail_attach_ical and ( not event.settings.mail_attach_ical_paid_only or
not event.settings.mail_attach_ical_paid_only or is_free or
is_free or order.valid_if_pending
order.valid_if_pending ),
), attach_other_files=[a for a in [
attach_other_files=[a for a in [ event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):] ] if a],
] if a], )
)
except SendMailException:
logger.exception('Order received email could not be sent to attendee')
def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str], def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str],
@@ -1476,13 +1476,10 @@ def send_expiry_warnings(sender, **kwargs):
email_template = settings.mail_text_order_pending_warning email_template = settings.mail_text_order_pending_warning
email_subject = settings.mail_subject_order_pending_warning email_subject = settings.mail_subject_order_pending_warning
try: o.send_mail(
o.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.expire_warning_sent'
'pretix.event.order.email.expire_warning_sent' )
)
except SendMailException:
logger.exception('Reminder email could not be sent')
@receiver(signal=periodic_task) @receiver(signal=periodic_task)
@@ -1543,14 +1540,11 @@ def send_download_reminders(sender, **kwargs):
email_template = event.settings.mail_text_download_reminder email_template = event.settings.mail_text_download_reminder
email_subject = event.settings.mail_subject_download_reminder email_subject = event.settings.mail_subject_download_reminder
email_context = get_email_context(event=event, order=o) email_context = get_email_context(event=event, order=o)
try: o.send_mail(
o.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.download_reminder_sent',
'pretix.event.order.email.download_reminder_sent', attach_tickets=True
attach_tickets=True )
)
except SendMailException:
logger.exception('Reminder email could not be sent')
if event.settings.mail_send_download_reminder_attendee: if event.settings.mail_send_download_reminder_attendee:
for p in positions: for p in positions:
@@ -1564,14 +1558,11 @@ def send_download_reminders(sender, **kwargs):
email_template = event.settings.mail_text_download_reminder_attendee email_template = event.settings.mail_text_download_reminder_attendee
email_subject = event.settings.mail_subject_download_reminder_attendee email_subject = event.settings.mail_subject_download_reminder_attendee
email_context = get_email_context(event=event, order=o, position=p) email_context = get_email_context(event=event, order=o, position=p)
try: o.send_mail(
o.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.download_reminder_sent',
'pretix.event.order.email.download_reminder_sent', attach_tickets=True, position=p
attach_tickets=True, position=p )
)
except SendMailException:
logger.exception('Reminder email could not be sent to attendee')
def notify_user_changed_order(order, user=None, auth=None, invoices=[]): def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
@@ -1579,13 +1570,10 @@ def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
email_template = order.event.settings.mail_text_order_changed email_template = order.event.settings.mail_text_order_changed
email_context = get_email_context(event=order.event, order=order) email_context = get_email_context(event=order.event, order=order)
email_subject = order.event.settings.mail_subject_order_changed email_subject = order.event.settings.mail_subject_order_changed
try: order.send_mail(
order.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.order_changed', user, auth=auth, invoices=invoices, attach_tickets=True,
'pretix.event.order.email.order_changed', user, auth=auth, invoices=invoices, attach_tickets=True, )
)
except SendMailException:
logger.exception('Order changed email could not be sent')
class OrderChangeManager: class OrderChangeManager:
@@ -1641,6 +1629,7 @@ class OrderChangeManager:
ChangeValidUntilOperation = namedtuple('ChangeValidUntilOperation', ('position', 'valid_until')) ChangeValidUntilOperation = namedtuple('ChangeValidUntilOperation', ('position', 'valid_until'))
AddBlockOperation = namedtuple('AddBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked')) AddBlockOperation = namedtuple('AddBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked'))
RemoveBlockOperation = namedtuple('RemoveBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked')) RemoveBlockOperation = namedtuple('RemoveBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked'))
ForceRecomputeOperation = namedtuple('ForceRecomputeOperation', tuple())
class AddPositionResult: class AddPositionResult:
_position: Optional[OrderPosition] _position: Optional[OrderPosition]
@@ -1804,6 +1793,7 @@ class OrderChangeManager:
positions = self.order.positions.select_related('item', 'item__tax_rule') positions = self.order.positions.select_related('item', 'item__tax_rule')
ia = self._invoice_address ia = self._invoice_address
tax_rules = self._current_tax_rules() tax_rules = self._current_tax_rules()
self._operations.append(self.ForceRecomputeOperation())
for pos in positions: for pos in positions:
tax_rule = tax_rules.get(pos.pk, pos.tax_rule) tax_rule = tax_rules.get(pos.pk, pos.tax_rule)
@@ -2094,6 +2084,43 @@ class OrderChangeManager:
) )
item_counts[item] += 1 item_counts[item] += 1
# Detect removed add-ons and create RemoveOperations
for cp, al in list(current_addons.items()):
for k, v in al.items():
input_num = input_addons[cp.id].get(k, 0)
current_num = len(current_addons[cp].get(k, []))
if input_num < current_num:
for a in current_addons[cp][k][:current_num - input_num]:
if a.canceled:
continue
is_unavailable = (
# If an item is no longer available due to time, it should usually also be no longer
# user-removable, because e.g. the stock has already been ordered.
# We always pass has_voucher=True because if a product now requires a voucher, it usually does
# not mean it should be unremovable for others.
# This also prevents accidental removal through the UI because a hidden product will no longer
# be part of the input.
(a.variation and a.variation.unavailability_reason(has_voucher=True, subevent=a.subevent))
or (a.variation and not a.variation.all_sales_channels and not a.variation.limit_sales_channels.contains(self.order.sales_channel))
or a.item.unavailability_reason(has_voucher=True, subevent=a.subevent)
or (
not a.item.all_sales_channels and
not a.item.limit_sales_channels.contains(self.order.sales_channel)
)
)
if is_unavailable:
# "Re-select" add-on
selected_addons[cp.id, a.item.category_id][a.item_id, a.variation_id] += 1
continue
if a.checkins.filter(list__consider_tickets_used=True).exists():
raise OrderError(
error_messages['addon_already_checked_in'] % {
'addon': str(a.item.name),
}
)
self.cancel(a)
item_counts[a.item] -= 1
# Check constraints on the add-on combinations # Check constraints on the add-on combinations
for op in toplevel_op: for op in toplevel_op:
item = op.item item = op.item
@@ -2126,41 +2153,6 @@ class OrderChangeManager:
} }
) )
# Detect removed add-ons and create RemoveOperations
for cp, al in list(current_addons.items()):
for k, v in al.items():
input_num = input_addons[cp.id].get(k, 0)
current_num = len(current_addons[cp].get(k, []))
if input_num < current_num:
for a in current_addons[cp][k][:current_num - input_num]:
if a.canceled:
continue
is_unavailable = (
# If an item is no longer available due to time, it should usually also be no longer
# user-removable, because e.g. the stock has already been ordered.
# We always pass has_voucher=True because if a product now requires a voucher, it usually does
# not mean it should be unremovable for others.
# This also prevents accidental removal through the UI because a hidden product will no longer
# be part of the input.
(a.variation and a.variation.unavailability_reason(has_voucher=True, subevent=a.subevent))
or (a.variation and not a.variation.all_sales_channels and not a.variation.limit_sales_channels.contains(self.order.sales_channel))
or a.item.unavailability_reason(has_voucher=True, subevent=a.subevent)
or (
not item.all_sales_channels and
not item.limit_sales_channels.contains(self.order.sales_channel)
)
)
if is_unavailable:
continue
if a.checkins.filter(list__consider_tickets_used=True).exists():
raise OrderError(
error_messages['addon_already_checked_in'] % {
'addon': str(a.item.name),
}
)
self.cancel(a)
item_counts[a.item] -= 1
for item, count in item_counts.items(): for item, count in item_counts.items():
if count == 0: if count == 0:
continue continue
@@ -2461,6 +2453,16 @@ class OrderChangeManager:
)) ))
else: else:
gc.transactions.create(value=-position.price, order=self.order, acceptor=self.order.event.organizer) gc.transactions.create(value=-position.price, order=self.order, acceptor=self.order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=self.user,
auth=self.auth,
data={
'value': -position.price,
'acceptor_id': self.order.event.organizer.id,
'acceptor_slug': self.order.event.organizer.slug
}
)
for m in position.granted_memberships.with_usages().all(): for m in position.granted_memberships.with_usages().all():
m.canceled = True m.canceled = True
@@ -2478,6 +2480,16 @@ class OrderChangeManager:
)) ))
else: else:
gc.transactions.create(value=-opa.position.price, order=self.order, acceptor=self.order.event.organizer) gc.transactions.create(value=-opa.position.price, order=self.order, acceptor=self.order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=self.user,
auth=self.auth,
data={
'value': -opa.position.price,
'acceptor_id': self.order.event.organizer.id,
'acceptor_slug': self.order.event.organizer.slug
}
)
for m in opa.granted_memberships.with_usages().all(): for m in opa.granted_memberships.with_usages().all():
m.canceled = True m.canceled = True
@@ -2640,6 +2652,10 @@ class OrderChangeManager:
except BlockedTicketSecret.DoesNotExist: except BlockedTicketSecret.DoesNotExist:
pass pass
# todo: revoke list handling # todo: revoke list handling
elif isinstance(op, self.ForceRecomputeOperation):
self.order.log_action('pretix.event.order.changed.recomputed', user=self.user, auth=self.auth, data={})
else:
raise TypeError(f"Unknown operation {type(op)}")
for p in secret_dirty: for p in secret_dirty:
assign_ticket_secret( assign_ticket_secret(
@@ -2694,7 +2710,10 @@ class OrderChangeManager:
fees.append(new_fee) fees.append(new_fee)
changed_by_rounding = set(apply_rounding( changed_by_rounding = set(apply_rounding(
self.order.tax_rounding_mode, self.event.currency, [p for p in split_positions if not p.canceled] + fees self.order.tax_rounding_mode,
self._invoice_address,
self.event.currency,
[p for p in split_positions if not p.canceled] + fees
)) ))
split_order.total = sum([p.price for p in split_positions if not p.canceled]) split_order.total = sum([p.price for p in split_positions if not p.canceled])
@@ -2716,7 +2735,10 @@ class OrderChangeManager:
fee.delete() fee.delete()
changed_by_rounding |= set(apply_rounding( changed_by_rounding |= set(apply_rounding(
self.order.tax_rounding_mode, self.event.currency, [p for p in split_positions if not p.canceled] + fees self.order.tax_rounding_mode,
self._invoice_address,
self.event.currency,
[p for p in split_positions if not p.canceled] + fees
)) ))
split_order.total = sum([p.price for p in split_positions if not p.canceled]) + sum([f.value for f in fees]) split_order.total = sum([p.price for p in split_positions if not p.canceled]) + sum([f.value for f in fees])
@@ -2833,7 +2855,12 @@ class OrderChangeManager:
if fee_changed: if fee_changed:
fees = list(self.order.fees.all()) fees = list(self.order.fees.all())
changed = apply_rounding(self.order.tax_rounding_mode, self.order.event.currency, [*positions, *fees]) changed = apply_rounding(
self.order.tax_rounding_mode,
self._invoice_address,
self.order.event.currency,
[*positions, *fees]
)
for l in changed: for l in changed:
if isinstance(l, OrderPosition): if isinstance(l, OrderPosition):
l.save(update_fields=[ l.save(update_fields=[
@@ -3131,7 +3158,10 @@ def _try_auto_refund(order, auto_refund=True, manual_refund=False, allow_partial
customer=order.customer, customer=order.customer,
testmode=order.testmode testmode=order.testmode
) )
giftcard.log_action('pretix.giftcards.created', data={}) giftcard.log_action(
action='pretix.giftcards.created',
data={}
)
r = order.refunds.create( r = order.refunds.create(
order=order, order=order,
payment=None, payment=None,
@@ -3269,8 +3299,12 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
positions = list(order.positions.all()) positions = list(order.positions.all())
fees = list(order.fees.all()) fees = list(order.fees.all())
try:
ia = order.invoice_address
except InvoiceAddress.DoesNotExist:
ia = None
rounding_changed = set(apply_rounding( rounding_changed = set(apply_rounding(
order.tax_rounding_mode, order.event.currency, [*positions, *[f for f in fees if f.pk != fee.pk]] order.tax_rounding_mode, ia, order.event.currency, [*positions, *[f for f in fees if f.pk != fee.pk]]
)) ))
total_without_fee = sum(c.price for c in positions) + sum(f.value for f in fees if f.pk != fee.pk) total_without_fee = sum(c.price for c in positions) + sum(f.value for f in fees if f.pk != fee.pk)
pending_sum_without_fee = max(Decimal("0.00"), total_without_fee - already_paid) pending_sum_without_fee = max(Decimal("0.00"), total_without_fee - already_paid)
@@ -3295,7 +3329,7 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
fee = None fee = None
rounding_changed |= set(apply_rounding( rounding_changed |= set(apply_rounding(
order.tax_rounding_mode, order.event.currency, [*positions, *fees] order.tax_rounding_mode, ia, order.event.currency, [*positions, *fees]
)) ))
for l in rounding_changed: for l in rounding_changed:
if isinstance(l, OrderPosition): if isinstance(l, OrderPosition):
@@ -3414,7 +3448,18 @@ def signal_listener_issue_giftcards(sender: Event, order: Order, **kwargs):
currency=sender.currency, issued_in=p, testmode=order.testmode, currency=sender.currency, issued_in=p, testmode=order.testmode,
expires=sender.organizer.default_gift_card_expiry, expires=sender.organizer.default_gift_card_expiry,
) )
gc.transactions.create(value=p.price - issued, order=order, acceptor=sender.organizer) gc.log_action(
action='pretix.giftcards.created',
)
trans = gc.transactions.create(value=p.price - issued, order=order, acceptor=sender.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
data={
'value': trans.value,
'acceptor_id': order.event.organizer.id,
'acceptor_slug': order.event.organizer.slug
}
)
any_giftcards = True any_giftcards = True
p.secret = gc.secret p.secret = gc.secret
p.save(update_fields=['secret']) p.save(update_fields=['secret'])
+9 -2
View File
@@ -211,7 +211,8 @@ def apply_discounts(event: Event, sales_channel: Union[str, SalesChannel],
return [new_prices.get(idx, (p[3], None)) for idx, p in enumerate(positions)] return [new_prices.get(idx, (p[3], None)) for idx, p in enumerate(positions)]
def apply_rounding(rounding_mode: Literal["line", "sum_by_net", "sum_by_net_keep_gross"], currency: str, def apply_rounding(rounding_mode: Literal["line", "sum_by_net", "sum_by_net_only_business", "sum_by_net_keep_gross"],
invoice_address: Optional[InvoiceAddress], currency: str,
lines: List[Union[OrderPosition, CartPosition, OrderFee]]) -> list: lines: List[Union[OrderPosition, CartPosition, OrderFee]]) -> list:
""" """
Given a list of order positions / cart positions / order fees (may be mixed), applies the given rounding mode Given a list of order positions / cart positions / order fees (may be mixed), applies the given rounding mode
@@ -226,11 +227,17 @@ def apply_rounding(rounding_mode: Literal["line", "sum_by_net", "sum_by_net_keep
When rounding mode is set to ``"sum_by_net"``, the gross prices and tax values of the individual lines will be When rounding mode is set to ``"sum_by_net"``, the gross prices and tax values of the individual lines will be
adjusted such that the per-taxrate/taxcode subtotal is rounded correctly. The net prices will stay constant. adjusted such that the per-taxrate/taxcode subtotal is rounded correctly. The net prices will stay constant.
:param rounding_mode: One of ``"line"``, ``"sum_by_net"``, or ``"sum_by_net_keep_gross"``. :param rounding_mode: One of ``"line"``, ``"sum_by_net"``, ``"sum_by_net_only_business"``, or ``"sum_by_net_keep_gross"``.
:param invoice_address: The invoice address, or ``None``
:param currency: Currency that will be used to determine rounding precision :param currency: Currency that will be used to determine rounding precision
:param lines: List of order/cart contents :param lines: List of order/cart contents
:return: Collection of ``lines`` members that have been changed and may need to be persisted to the database. :return: Collection of ``lines`` members that have been changed and may need to be persisted to the database.
""" """
if rounding_mode == "sum_by_net_only_business":
if invoice_address and invoice_address.is_business:
rounding_mode = "sum_by_net"
else:
rounding_mode = "line"
def _key(line): def _key(line):
return (line.tax_rate, line.tax_code or "") return (line.tax_rate, line.tax_code or "")
+16 -19
View File
@@ -48,7 +48,7 @@ from django.utils.translation import gettext_lazy as _
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import CachedFile, Event, User, cachedfile_name from pretix.base.models import CachedFile, Event, User, cachedfile_name
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import mail
from pretix.base.services.tasks import ProfiledEventTask from pretix.base.services.tasks import ProfiledEventTask
from pretix.base.shredder import ShredError from pretix.base.shredder import ShredError
from pretix.celery_app import app from pretix.celery_app import app
@@ -171,21 +171,18 @@ def shred(self, event: Event, fileid: str, confirm_code: str, user: int=None, lo
if user: if user:
with language(user.locale): with language(user.locale):
try: mail(
mail( user.email,
user.email, _('Data shredding completed'),
_('Data shredding completed'), 'pretixbase/email/shred_completed.txt',
'pretixbase/email/shred_completed.txt', {
{ 'user': user,
'user': user, 'organizer': event.organizer.name,
'organizer': event.organizer.name, 'event': str(event.name),
'event': str(event.name), 'start_time': date_format(parse(indexdata['time']).astimezone(event.timezone), 'SHORT_DATETIME_FORMAT'),
'start_time': date_format(parse(indexdata['time']).astimezone(event.timezone), 'SHORT_DATETIME_FORMAT'), 'shredders': ', '.join([str(s.verbose_name) for s in shredders])
'shredders': ', '.join([str(s.verbose_name) for s in shredders]) },
}, event=None,
event=None, user=user,
user=user, locale=user.locale,
locale=user.locale, )
)
except SendMailException:
pass # Already logged
+23
View File
@@ -81,6 +81,7 @@ from pretix.helpers.countries import CachedCountries, pycountry_add
ROUNDING_MODES = ( ROUNDING_MODES = (
('line', _('Compute taxes for every line individually')), ('line', _('Compute taxes for every line individually')),
('sum_by_net', _('Compute taxes based on net total')), ('sum_by_net', _('Compute taxes based on net total')),
('sum_by_net_only_business', _('For business customers, compute taxes based on net total. For individuals, use line-based rounding')),
('sum_by_net_keep_gross', _('Compute taxes based on net total with stable gross prices')), ('sum_by_net_keep_gross', _('Compute taxes based on net total with stable gross prices')),
# We could also have sum_by_gross, but we're not aware of any use-cases for it # We could also have sum_by_gross, but we're not aware of any use-cases for it
) )
@@ -2947,6 +2948,28 @@ If you did not request a new password, please ignore this email.
Best regards, Best regards,
Your {organizer} team""")) # noqa: W291
},
'mail_subject_customer_security_notice': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("Changes to your account at {organizer}")),
},
'mail_text_customer_security_notice': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello {name},
the following change has been made to your account at {organizer}:
{message}
You can review and change your account settings here:
{url}
If this change was not performed by you, please contact us immediately.
Best regards,
Your {organizer} team""")) # noqa: W291 Your {organizer} team""")) # noqa: W291
}, },
'smtp_use_custom': { 'smtp_use_custom': {
+5 -1
View File
@@ -51,7 +51,7 @@ from pretix.api.serializers.waitinglist import WaitingListSerializer
from pretix.base.i18n import LazyLocaleException from pretix.base.i18n import LazyLocaleException
from pretix.base.models import ( from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, OrderPayment, CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, OrderPayment,
OrderPosition, OrderRefund, QuestionAnswer, OrderPosition, OrderRefund, OutgoingMail, QuestionAnswer,
) )
from pretix.base.services.invoices import invoice_pdf_task from pretix.base.services.invoices import invoice_pdf_task
from pretix.base.signals import register_data_shredders from pretix.base.signals import register_data_shredders
@@ -329,6 +329,10 @@ class EmailAddressShredder(BaseDataShredder):
sleep_time=2, sleep_time=2,
) )
slow_delete(
OutgoingMail.objects.filter(event=self.event)
)
for o in _progress_helper(qs_orders, progress_callback, qs_op_cnt, total): for o in _progress_helper(qs_orders, progress_callback, qs_op_cnt, total):
changed = bool(o.email) or bool(o.customer) changed = bool(o.email) or bool(o.customer)
o.email = None o.email = None
+10 -2
View File
@@ -944,32 +944,40 @@ As with all event-plugin signals, the ``sender`` keyword argument will contain t
email_filter = EventPluginSignal() email_filter = EventPluginSignal()
""" """
Arguments: ``message``, ``order``, ``user`` Arguments: ``message``, ``order``, ``user``, ``outgoing_mail``
This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to
return a (possibly modified) copy of the message object passed to you. return a (possibly modified) copy of the message object passed to you.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event. As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
The ``message`` argument will contain an ``EmailMultiAlternatives`` object. The ``message`` argument will contain an ``EmailMultiAlternatives`` object.
The ``outgoing_mail`` argument will contain the ``OutgoingMail`` model instance. Note that the ``message`` object
might have newer information if a previous plugin already modified the email.
If the email is associated with a specific order, the ``order`` argument will be passed as well, otherwise If the email is associated with a specific order, the ``order`` argument will be passed as well, otherwise
it will be ``None``. it will be ``None``.
If the email is associated with a specific user, e.g. a notification email, the ``user`` argument will be passed as If the email is associated with a specific user, e.g. a notification email, the ``user`` argument will be passed as
well, otherwise it will be ``None``. well, otherwise it will be ``None``.
You can raise ``WithholdMailException`` to prevent the email from being sent, e.g. when implementing rate limiting.
""" """
global_email_filter = GlobalSignal() global_email_filter = GlobalSignal()
""" """
Arguments: ``message``, ``order``, ``user``, ``customer``, ``organizer`` Arguments: ``message``, ``order``, ``user``, ``customer``, ``organizer``, ``outgoing_mail``
This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to
return a (possibly modified) copy of the message object passed to you. return a (possibly modified) copy of the message object passed to you.
This signal is called on all events and even if there is no known event. ``sender`` is an event or None. This signal is called on all events and even if there is no known event. ``sender`` is an event or None.
The ``message`` argument will contain an ``EmailMultiAlternatives`` object. The ``message`` argument will contain an ``EmailMultiAlternatives`` object.
The ``outgoing_mail`` argument will contain the ``OutgoingMail`` model instance. Note that the ``message`` object
might have newer information if a previous plugin already modified the email.
If the email is associated with a specific order, the ``order`` argument will be passed as well, otherwise If the email is associated with a specific order, the ``order`` argument will be passed as well, otherwise
it will be ``None``. it will be ``None``.
If the email is associated with a specific user, e.g. a notification email, the ``user`` argument will be passed as If the email is associated with a specific user, e.g. a notification email, the ``user`` argument will be passed as
well, otherwise it will be ``None``. well, otherwise it will be ``None``.
You can raise ``WithholdMailException`` to prevent the email from being sent, e.g. when implementing rate limiting.
""" """
-3
View File
@@ -8,9 +8,6 @@
<h1>{% trans "Not found" %}</h1> <h1>{% trans "Not found" %}</h1>
<p>{% trans "I'm afraid we could not find the the resource you requested." %}</p> <p>{% trans "I'm afraid we could not find the the resource you requested." %}</p>
<p>{{ exception }}</p> <p>{{ exception }}</p>
<p class="links">
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
</p>
{% if request.user.is_staff and not staff_session %} {% if request.user.is_staff and not staff_session %}
<form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post"> <form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post">
<p> <p>
+1
View File
@@ -95,6 +95,7 @@ class OrganizerSlugBanlistValidator(BanlistValidator):
'csp_report', 'csp_report',
'widget', 'widget',
'lead', 'lead',
'scheduling',
] ]
+14 -1
View File
@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
import pycountry import pycountry
from django.conf import settings
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext, pgettext, pgettext_lazy from django.utils.translation import gettext, pgettext, pgettext_lazy
@@ -29,6 +30,7 @@ from django_scopes import scope
from pretix.base.addressvalidation import ( from pretix.base.addressvalidation import (
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED, COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED,
) )
from pretix.base.i18n import language
from pretix.base.invoicing.transmission import get_transmission_types from pretix.base.invoicing.transmission import get_transmission_types
from pretix.base.models import Organizer from pretix.base.models import Organizer
from pretix.base.models.tax import VAT_ID_COUNTRIES from pretix.base.models.tax import VAT_ID_COUNTRIES
@@ -89,7 +91,7 @@ def _info(cc):
} }
def address_form(request): def _address_form(request):
cc = request.GET.get("country", "DE") cc = request.GET.get("country", "DE")
info = _info(cc) info = _info(cc)
@@ -157,4 +159,15 @@ def address_form(request):
# The help text explains that it is optional, so we want to hide that if it is required # The help text explains that it is optional, so we want to hide that if it is required
info["vat_id"]["helptext_visible"] = False info["vat_id"]["helptext_visible"] = False
return info
def address_form(request):
locale = request.GET.get('locale')
if locale in dict(settings.LANGUAGES):
with language(locale):
info = _address_form(request)
else:
info = _address_form(request)
return JsonResponse(info) return JsonResponse(info)
+5
View File
@@ -867,6 +867,11 @@ class TaxSettingsForm(EventSettingsValidationMixin, SettingsForm):
"The gross price of some products may be changed to ensure correct rounding, while the net " "The gross price of some products 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." "prices will be kept as configured. This may cause the actual payment amount to differ."
), ),
"sum_by_net_only_business": _(
"Same as above, but only applied to business customers. Line-based rounding will be used for consumers. "
"Recommended when e-invoicing is only used for business customers and consumers do not receive "
"invoices. This can cause the payment amount to change when the invoice address is changed."
),
"sum_by_net_keep_gross": _( "sum_by_net_keep_gross": _(
"Recommended for e-invoicing when you primarily sell to consumers. " "Recommended for e-invoicing when you primarily sell to consumers. "
"The gross or net price of some products may be changed automatically to ensure correct " "The gross or net price of some products may be changed automatically to ensure correct "
+61 -2
View File
@@ -57,8 +57,9 @@ from pretix.base.forms.widgets import (
from pretix.base.models import ( from pretix.base.models import (
Checkin, CheckinList, Device, Event, EventMetaProperty, EventMetaValue, Checkin, CheckinList, Device, Event, EventMetaProperty, EventMetaValue,
Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition, Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
OrderRefund, Organizer, Question, QuestionAnswer, Quota, SalesChannel, OrderRefund, Organizer, OutgoingMail, Question, QuestionAnswer, Quota,
SubEvent, SubEventMetaValue, Team, TeamAPIToken, TeamInvite, Voucher, SalesChannel, SubEvent, SubEventMetaValue, Team, TeamAPIToken, TeamInvite,
Voucher,
) )
from pretix.base.signals import register_payment_providers from pretix.base.signals import register_payment_providers
from pretix.base.timeframes import ( from pretix.base.timeframes import (
@@ -2815,3 +2816,61 @@ class DeviceFilterForm(FilterForm):
qs = qs.order_by('-device_id') qs = qs.order_by('-device_id')
return qs return qs
class OutgoingMailFilterForm(FilterForm):
orders = {
'date': 'created',
'-date': '-created',
}
query = forms.CharField(
label=_('Search email address or subject'),
widget=forms.TextInput(attrs={
'placeholder': _('Search email address or subject'),
}),
required=False
)
event = forms.ModelChoiceField(
queryset=Event.objects.none(),
label=_('Event'),
empty_label=_('All events'),
required=False,
)
status = forms.ChoiceField(
label=_('Status'),
choices=[
('', _('All')),
*OutgoingMail.STATUS_CHOICES,
],
required=False
)
def __init__(self, *args, **kwargs):
request = kwargs.pop('request')
super().__init__(*args, **kwargs)
self.fields['event'].queryset = request.organizer.events.all()
def filter_qs(self, qs):
fdata = self.cleaned_data
if fdata.get('query'):
query = fdata.get('query')
qs = qs.filter(
Q(to__containsstring=query.lower())
| Q(cc__containsstring=query.lower())
| Q(bcc__containsstring=query.lower())
| Q(subject__icontains=query)
)
if fdata.get('event'):
qs = qs.filter(event=fdata['event'])
if fdata.get('status'):
qs = qs.filter(status=fdata['status'])
if fdata.get('ordering'):
qs = qs.order_by(self.get_order_by())
else:
qs = qs.order_by("-created", "-pk")
return qs
+16
View File
@@ -585,6 +585,7 @@ class MailSettingsForm(SettingsForm):
help_text=''.join([ help_text=''.join([
str(_("All emails will be sent to this address as a Bcc copy.")), str(_("All emails will be sent to this address as a Bcc copy.")),
str(_("You can specify multiple recipients separated by commas.")), str(_("You can specify multiple recipients separated by commas.")),
str(_("Sensitive emails like password resets will not be sent in Bcc.")),
]), ]),
validators=[multimail_validate], validators=[multimail_validate],
required=False, required=False,
@@ -634,6 +635,16 @@ class MailSettingsForm(SettingsForm):
required=False, required=False,
widget=I18nMarkdownTextarea, widget=I18nMarkdownTextarea,
) )
mail_subject_customer_security_notice = I18nFormField(
label=_("Subject"),
required=False,
widget=I18nTextInput,
)
mail_text_customer_security_notice = I18nFormField(
label=_("Text"),
required=False,
widget=I18nMarkdownTextarea,
)
base_context = { base_context = {
'mail_text_customer_registration': ['customer', 'url'], 'mail_text_customer_registration': ['customer', 'url'],
@@ -642,6 +653,8 @@ class MailSettingsForm(SettingsForm):
'mail_subject_customer_email_change': ['customer', 'url'], 'mail_subject_customer_email_change': ['customer', 'url'],
'mail_text_customer_reset': ['customer', 'url'], 'mail_text_customer_reset': ['customer', 'url'],
'mail_subject_customer_reset': ['customer', 'url'], 'mail_subject_customer_reset': ['customer', 'url'],
'mail_text_customer_security_notice': ['customer', 'url', 'message'],
'mail_subject_customer_security_notice': ['customer', 'url', 'message'],
} }
def _get_sample_context(self, base_parameters): def _get_sample_context(self, base_parameters):
@@ -655,6 +668,9 @@ class MailSettingsForm(SettingsForm):
'presale:organizer.customer.activate' 'presale:organizer.customer.activate'
) + '?token=' + get_random_string(30) ) + '?token=' + get_random_string(30)
if 'message' in base_parameters:
placeholders['message'] = _('Your password has been changed.')
if 'customer' in base_parameters: if 'customer' in base_parameters:
placeholders['name'] = pgettext_lazy('person_name_sample', 'John Doe') placeholders['name'] = pgettext_lazy('person_name_sample', 'John Doe')
name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme] name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme]
+10
View File
@@ -170,6 +170,12 @@ class OrderFeeAdded(OrderChangeLogEntryType):
plain = _('A fee has been added') plain = _('A fee has been added')
@log_entry_types.new()
class OrderRecomputed(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.recomputed'
plain = _('Taxes and rounding have been recomputed')
@log_entry_types.new() @log_entry_types.new()
class OrderFeeChanged(OrderChangeLogEntryType): class OrderFeeChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.feevalue' action_type = 'pretix.event.order.changed.feevalue'
@@ -699,6 +705,8 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.organizer.export.schedule.deleted': _('A scheduled export has been deleted.'), 'pretix.organizer.export.schedule.deleted': _('A scheduled export has been deleted.'),
'pretix.organizer.export.schedule.executed': _('A scheduled export has been executed.'), 'pretix.organizer.export.schedule.executed': _('A scheduled export has been executed.'),
'pretix.organizer.export.schedule.failed': _('A scheduled export has failed: {reason}.'), 'pretix.organizer.export.schedule.failed': _('A scheduled export has failed: {reason}.'),
'pretix.organizer.outgoingmails.retried': _('Failed emails have been scheduled to be retried.'),
'pretix.organizer.outgoingmails.aborted': _('Queued emails have been aborted.'),
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'), 'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'), 'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'), 'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),
@@ -793,6 +801,8 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.giftcards.created': _('The gift card has been created.'), 'pretix.giftcards.created': _('The gift card has been created.'),
'pretix.giftcards.modified': _('The gift card has been changed.'), 'pretix.giftcards.modified': _('The gift card has been changed.'),
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'), 'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
'pretix.giftcards.transaction.payment': _('A payment has been performed.'),
'pretix.giftcards.transaction.refund': _('A refund has been performed. '),
'pretix.team.token.created': _('The token "{name}" has been created.'), 'pretix.team.token.created': _('The token "{name}" has been created.'),
'pretix.team.token.deleted': _('The token "{name}" has been revoked.'), 'pretix.team.token.deleted': _('The token "{name}" has been revoked.'),
'pretix.event.checkin.reset': _('The check-in and print log state has been reset.') 'pretix.event.checkin.reset': _('The check-in and print log state has been reset.')
+9
View File
@@ -679,6 +679,15 @@ def get_organizer_navigation(request):
'active': (url.url_name == 'organizer.datasync.failedjobs'), 'active': (url.url_name == 'organizer.datasync.failedjobs'),
}]) }])
nav.append({
'label': _('Outgoing emails'),
'url': reverse('control:organizer.outgoingmails', kwargs={
'organizer': request.organizer.slug,
}),
'active': 'organizer.outgoingmail' in url.url_name,
'icon': 'send',
})
merge_in(nav, sorted( merge_in(nav, sorted(
sum((list(a[1]) for a in nav_organizer.send(request.organizer, request=request, organizer=request.organizer)), sum((list(a[1]) for a in nav_organizer.send(request.organizer, request=request, organizer=request.organizer)),
[]), []),
@@ -65,6 +65,9 @@
{% blocktrans asvar title_reset %}Customer account password reset{% endblocktrans %} {% blocktrans asvar title_reset %}Customer account password reset{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="reset" title=title_reset items="mail_subject_customer_reset,mail_text_customer_reset" %} {% include "pretixcontrol/event/mail_settings_fragment.html" with pid="reset" title=title_reset items="mail_subject_customer_reset,mail_text_customer_reset" %}
{% blocktrans asvar title_security_notice %}Customer account security notification{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="security_notice" title=title_security_notice items="mail_subject_customer_security_notice,mail_text_customer_security_notice" %}
</div> </div>
</fieldset> </fieldset>
</div> </div>
@@ -0,0 +1,222 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load urlreplace %}
{% load icon %}
{% load compress %}
{% load static %}
{% block inner %}
<h1>
{% trans "Outgoing email" %}
</h1>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Email details" %}</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-lg-7 col-md-12">
<dl class="dl-horizontal">
<dt>{% trans "From" context "email" %}</dt>
<dd>{{ sender }}</dd>
<dt>{% trans "To" context "email" %}</dt>
<dd>{{ mail.to|join:", " }}</dd>
{% if mail.cc %}
<dt>{% trans "Cc" context "email" %}</dt>
<dd>{{ mail.cc|join:", " }}</dd>
{% endif %}
{% if mail.bcc %}
<dt>{% trans "Bcc" context "email" %}</dt>
<dd>{{ mail.bcc|join:", " }}</dd>
{% endif %}
<dt>{% trans "Subject" %}</dt>
<dd>{{ mail.subject }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>
{% if mail.status == "queued" %}
<span class="label label-info">{% icon "clock-o" %} {% trans "queued" %}</span>
{% elif mail.status == "inflight" %}
<span class="label label-info">{% icon "send" %} {% trans "being sent" %}</span>
{% elif mail.status == "awaiting_retry" %}
<span class="label label-warning">{% icon "repeat" %} {% trans "will be retried" %}</span>
{% elif mail.status == "failed" %}
<span class="label label-danger">{% icon "warning" %} {% trans "failed" %}</span>
{% elif mail.status == "bounced" %}
<span class="label label-danger">{% icon "exclamation-circle" %} {% trans "bounced" %}</span>
{% elif mail.status == "withheld" %}
<span class="label label-warning">{% icon "ban" %} {% trans "withheld" %}</span>
{% elif mail.status == "aborted" %}
<span class="label label-danger">{% icon "ban" %} {% trans "aborted" %}</span>
{% elif mail.status == "sent" %}
<span class="label label-success">{% icon "check" %} {% trans "sent" %}</span>
{% endif %}
</dd>
<dt>{% trans "Creation" %}</dt>
<dd>{{ mail.created|date:"SHORT_DATETIME_FORMAT" }}</dd>
{% if mail.sent %}
<dt>{% trans "Sent" %}</dt>
<dd>{{ mail.sent|date:"SHORT_DATETIME_FORMAT" }}</dd>
{% endif %}
{% if mail.retry_after and mail.status == "awaiting_retry" %}
<dt>{% trans "Next attempt (estimate)" %}</dt>
<dd>{{ mail.retry_after|date:"SHORT_DATETIME_FORMAT" }}</dd>
{% endif %}
{% if mail.event %}
<dt>{% trans "Event" %}</dt>
<dd>
<a href="{% url "control:event.index" organizer=request.organizer.slug event=mail.event.slug %}">
{{ mail.event }}
</a>
</dd>
{% endif %}
{% if mail.order %}
<dt>{% trans "Order" %}</dt>
<dd>
<a href="{% url "control:event.order" organizer=request.organizer.slug event=mail.event.slug code=mail.order.code %}">
{{ mail.order.code }}</a>{% if mail.orderposition %}-
{{ mail.orderposition.positionid }}{% endif %}
</dd>
{% endif %}
{% if mail.customer %}
<dt>{% trans "Customer" %}</dt>
<dd>
{% icon "user fa-fw" %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=mail.customer.identifier %}">
{{ mail.customer }}
</a>
</dd>
{% endif %}
</dl>
</div>
{% if mail.actual_attachments %}
<div class="col-lg-5 col-md-12">
<strong>{% trans "Attachments" %}</strong><br>
<ul class="list-unstyled">
{% for a in mail.actual_attachments %}
<li>
{% if a.type == "text/calendar" %}
{% icon "calendar-plus-o fa-fw" %}
{% elif a.type == "application/pdf" %}
{% icon "file-pdf-o fa-fw" %}
{% elif "image/" in a.type %}
{% icon "file-image-o fa-fw" %}
{% elif "msword" in a.type or "document" in a.type %}
{% icon "file-word-o fa-fw" %}
{% elif "excel" in a.type or "spreadsheet" in a.type %}
{% icon "file-excel-o fa-fw" %}
{% elif "powerpoint" in a.type or "presentation" in a.type %}
{% icon "file-powerpoint-o fa-fw" %}
{% elif "pkpass" in a.type %}
{% icon "qrcode fa-fw" %}
{% else %}
{% icon "file-o fa-fw" %}
{% endif %}
{{ a.name }}
<span class="text-muted">
({{ a.size|filesizeformat }})
</span>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
</div>
</div>
<div>
<ul class="nav nav-tabs" role="tablist">
{% if mail.is_failed %}
<li role="presentation" class="active">
<a href="#tab-error" role="tab" data-toggle="tab">
<span class="fa fa-warning"></span>
{% trans "Error" %}
</a>
</li>
{% endif %}
{% if mail.body_html %}
<li role="presentation"
{% if not mail.is_failed %}class="active"{% endif %}>
<a href="#tab-html" role="tab" data-toggle="tab">
<span class="fa fa-eye"></span>
{% trans "HTML content" %}
</a>
</li>
{% endif %}
<li role="presentation"
{% if not mail.is_failed and not mail.body_html %}class="active"{% endif %}>
<a href="#tab-text" role="tab" data-toggle="tab">
<span class="fa fa-file-text-o"></span>
{% trans "Text content" %}
</a>
</li>
<li role="presentation">
<a href="#tab-headers" role="tab" data-toggle="tab">
<span class="fa fa-code"></span>
{% trans "Headers" %}
</a>
</li>
</ul>
<div class="tab-content">
{% if mail.is_failed %}
<div role="tabpanel" class="tab-pane active" id="tab-error">
<strong>
{{ mail.error }}
</strong>
<pre>{{ mail.error_detail }}</pre>
</div>
{% endif %}
{% if mail.body_html %}
<div role="tabpanel"
class="tab-pane {% if not mail.is_failed %}active{% endif %}"
id="tab-html">
{% if mail.sensitive %}
<div class="empty-collection">
<p>
{% icon "eye-slash fa-4x" %}
</p>
<p>
{% blocktrans trimmed %}
Sensitive content not shown for security reasons
{% endblocktrans %}
</p>
</div>
{% else %}
{{ data_url|json_script:"mail_body_html" }}
{% endif %}
</div>
{% endif %}
<div role="tabpanel"
class="tab-pane {% if not mail.is_failed and not mail.body_html %}active{% endif %}"
id="tab-text">
{% if mail.sensitive %}
<div class="empty-collection">
<p>
{% icon "eye-slash fa-4x" %}
</p>
<p>
{% blocktrans trimmed %}
Sensitive content not shown for security reasons
{% endblocktrans %}
</p>
</div>
{% else %}
<pre><code>{{ mail.body_plain }}</code></pre>
{% endif %}
</div>
<div role="tabpanel"
class="tab-pane"
id="tab-headers">
<pre><code>{% for k, v in mail.headers.items %}{{ k }}: {{ v }}<br>{% endfor %}</code></pre>
<p class="text-muted">
{% trans "Additional headers will be added by the mail server and are not visible here." %}
</p>
</div>
</div>
</div>
{% compress js %}
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/outgoingmail.js" %}"></script>
{% endcompress %}
{% endblock %}
@@ -0,0 +1,185 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load urlreplace %}
{% load icon %}
{% block inner %}
<h1>
{% trans "Outgoing emails" %}
</h1>
<p>
{% blocktrans trimmed with days=days %}
This is an overview of all emails sent by your organizer account in the last {{ days }} days.
{% endblocktrans %}
</p>
{% if mails|length == 0 and not filter_form.filtered %}
<div class="empty-collection">
<p>
{% blocktrans trimmed %}
You haven't sent any emails recently.
{% endblocktrans %}
</p>
</div>
{% else %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Filter" %}</h3>
</div>
<form class="panel-body filter-form" action="" method="get">
<div class="row">
<div class="col-md-4 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.query %}
</div>
<div class="col-md-3 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.status %}
</div>
<div class="col-md-5 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.event %}
</div>
</div>
<div class="text-right">
<button class="btn btn-primary btn-lg" type="submit">
<span class="fa fa-filter"></span>
{% trans "Filter" %}
</button>
</div>
</form>
</div>
<form action="{% url "control:organizer.outgoingmails.bulk_action" organizer=request.organizer.slug %}" method="post">
{% csrf_token %}
{% for field in filter_form %}
{{ field.as_hidden }}
{% endfor %}
<div class="table-responsive">
<table class="table table-condensed table-hover table-quotas">
<thead>
<tr>
<th>
<label aria-label="{% trans "select all rows for batch-operation" %}"
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
</th>
<th>{% trans "Subject" %}</th>
<th>{% trans "Recipients" %}</th>
<th>{% trans "Context" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Date" %}
<a href="?{% url_replace request 'ordering' '-date' %}"><i
class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'date' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th></th>
</tr>
{% if page_obj.paginator.num_pages > 1 %}
<tr class="table-select-all warning hidden">
<td>
<input type="checkbox" name="__ALL" id="__all"
data-results-total="{{ page_obj.paginator.count }}">
</td>
<td colspan="7">
<label for="__all">
{% trans "Select all results on other pages as well" %}
</label>
</td>
</tr>
{% endif %}
</thead>
<tbody>
{% for m in mails %}
<tr>
<td>
<label aria-label="{% trans "select row for batch-operation" %}"
class="batch-select-label"><input type="checkbox" name="outgoingmail"
class="batch-select-checkbox"
value="{{ m.pk }}"/></label>
</td>
<td>
<a href="{% url "control:organizer.outgoingmail" organizer=request.organizer.slug mail=m.id %}">
{{ m.subject }}
</a>
{% if m.sensitive %}
<span class="text-muted">{% icon "eye-slash" %}</span>
{% endif %}
</td>
<td>
{{ m.to|join:", " }}
{% if m.cc %}
<br><small class="text-muted">{% trans "Cc" context "email" %}: {{ m.cc|join:", " }}</small>
{% endif %}
{% if m.bcc %}
<br><small class="text-muted">{% trans "Bcc" context "email" %}: {{ m.bcc|join:", " }}</small>
{% endif %}
</td>
<td>
{% if m.event %}
<div>
{% icon "calendar fa-fw" %}
<a href="{% url "control:event.index" organizer=request.organizer.slug event=m.event.slug %}">
{{ m.event }}
</a>
</div>
{% endif %}
{% if m.order %}
<div>
{% icon "shopping-cart fa-fw" %}
<a href="{% url "control:event.order" organizer=request.organizer.slug event=m.event.slug code=m.order.code %}">
{{ m.order.code }}</a>{% if m.orderposition %}-{{ m.orderposition.positionid }}{% endif %}
</div>
{% endif %}
{% if m.customer %}
<div>
{% icon "user fa-fw" %}
<a href="{% url "control:organizer.customer" organizer=request.organizer.slug customer=m.customer.identifier %}">
{{ m.customer }}
</a>
</div>
{% endif %}
</td>
<td>
{% if m.status == "queued" %}
<span class="label label-info">{% icon "clock-o" %} {% trans "queued" %}</span>
{% elif m.status == "inflight" %}
<span class="label label-info">{% icon "send" %} {% trans "being sent" %}</span>
{% elif m.status == "awaiting_retry" %}
<span class="label label-warning">{% icon "repeat" %} {% trans "will be retried" %}</span>
{% elif m.status == "failed" %}
<span class="label label-danger">{% icon "warning" %} {% trans "failed" %}</span>
{% elif m.status == "bounced" %}
<span class="label label-danger">{% icon "exclamation-circle" %} {% trans "bounced" %}</span>
{% elif m.status == "withheld" %}
<span class="label label-warning">{% icon "ban" %} {% trans "withheld" %}</span>
{% elif m.status == "aborted" %}
<span class="label label-danger">{% icon "ban" %} {% trans "aborted" %}</span>
{% elif m.status == "sent" %}
<span class="label label-success">{% icon "check" %} {% trans "sent" %}</span>
{% endif %}
</td>
<td>
{{ m.created|date:"SHORT_DATETIME_FORMAT" }}
{% if m.sent %}
<br>
<small class="text-muted">{% trans "Sent:" %} {{ m.sent|date:"SHORT_DATETIME_FORMAT" }}</small>
{% endif %}
</td>
<td class="text-right flip">
<a href="{% url "control:organizer.outgoingmail" organizer=request.organizer.slug mail=m.id %}"
class="btn btn-default btn-sm">{% icon "eye" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="batch-select-actions">
<button type="submit" class="btn btn-primary btn-save" name="action" value="retry">
{% icon "repeat" %}
{% trans "Retry (if failed or withheld)" %}
</button>
<button type="submit" class="btn btn-danger btn-save" name="action" value="abort">
{% icon "ban" %}
{% trans "Abort (if queued, awaiting retry or withheld)" %}
</button>
</div>
</form>
{% include "pretixcontrol/pagination.html" %}
{% endif %}
{% endblock %}
@@ -144,14 +144,23 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p> <p>
{% trans "If you lose access to your devices, you can use one of the following keys to log in. We recommend to store them in a safe place, e.g. printed out or in a password manager. Every token can be used at most once." %} {% blocktrans trimmed %}
If you lose access to your devices, you can use one of your emergency tokens to log in.
We recommend to store them in a safe place, e.g. printed out or in a password manager.
Every token can be used at most once.
{% endblocktrans %}
</p> </p>
<p>{% trans "Unused tokens:" %}</p> {% if static_tokens_device %}
<ul> <p>
{% for t in static_tokens %} {% blocktrans trimmed with generation_date_time=static_tokens_device.created_at %}
<li><code>{{ t.token }}</code></li> You generated your emergency tokens on {{ generation_date_time }}.
{% endfor %} {% endblocktrans %}
</ul> </p>
{% else %}
<p>
{% trans "You don't have any emergency tokens yet." %}
</p>
{% endif %}
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default"> <a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
<span class="fa fa-refresh"></span> <span class="fa fa-refresh"></span>
{% trans "Generate new emergency tokens" %} {% trans "Generate new emergency tokens" %}
+6 -2
View File
@@ -38,8 +38,9 @@ from django.views.generic.base import RedirectView
from pretix.control.views import ( from pretix.control.views import (
auth, checkin, dashboards, datasync, discounts, event, geo, auth, checkin, dashboards, datasync, discounts, event, geo,
global_settings, item, main, modelimport, oauth, orders, organizer, pdf, global_settings, item, mail, main, modelimport, oauth, orders, organizer,
search, shredder, subevents, typeahead, user, users, vouchers, waitinglist, pdf, search, shredder, subevents, typeahead, user, users, vouchers,
waitinglist,
) )
urlpatterns = [ urlpatterns = [
@@ -240,6 +241,9 @@ urlpatterns = [
name='organizer.gate.edit'), name='organizer.gate.edit'),
re_path(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/delete$', organizer.GateDeleteView.as_view(), re_path(r'^organizer/(?P<organizer>[^/]+)/gate/(?P<gate>[^/]+)/delete$', organizer.GateDeleteView.as_view(),
name='organizer.gate.delete'), name='organizer.gate.delete'),
re_path(r'^organizer/(?P<organizer>[^/]+)/outgoingmails$', mail.OutgoingMailListView.as_view(), name='organizer.outgoingmails'),
re_path(r'^organizer/(?P<organizer>[^/]+)/outgoingmail/bulk_action$', mail.OutgoingMailBulkAction.as_view(), name='organizer.outgoingmails.bulk_action'),
re_path(r'^organizer/(?P<organizer>[^/]+)/outgoingmail/(?P<mail>[0-9]+)/$', mail.OutgoingMailDetailView.as_view(), name='organizer.outgoingmail'),
re_path(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'), re_path(r'^organizer/(?P<organizer>[^/]+)/teams$', organizer.TeamListView.as_view(), name='organizer.teams'),
re_path(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'), re_path(r'^organizer/(?P<organizer>[^/]+)/team/add$', organizer.TeamCreateView.as_view(), name='organizer.team.add'),
re_path(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(), re_path(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/$', organizer.TeamMemberView.as_view(),
-4
View File
@@ -66,7 +66,6 @@ from pretix.base.forms.auth import (
) )
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
from pretix.base.services.mail import SendMailException
from pretix.helpers.http import get_client_ip, redirect_to_url from pretix.helpers.http import get_client_ip, redirect_to_url
from pretix.helpers.security import handle_login_source from pretix.helpers.security import handle_login_source
@@ -347,9 +346,6 @@ class Forgot(TemplateView):
except User.DoesNotExist: except User.DoesNotExist:
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.') logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
except SendMailException:
logger.exception('Sending password reset email to \"' + email + '\" failed.')
except RepeatedResetDenied: except RepeatedResetDenied:
pass pass
+194
View File
@@ -0,0 +1,194 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# 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 base64
import logging
from email.header import decode_header, make_header
from email.utils import parseaddr
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import BadRequest
from django.db import transaction
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import ngettext
from django.views import View
from django.views.generic import DetailView, ListView
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
from pretix.base.models import OutgoingMail
from pretix.base.services.mail import mail_send_task
from pretix.control.forms.filter import OutgoingMailFilterForm
from pretix.control.permissions import OrganizerPermissionRequiredMixin
from pretix.control.views.organizer import OrganizerDetailViewMixin
logger = logging.getLogger(__name__)
class OutgoingMailQueryMixin:
@cached_property
def request_data(self):
if self.request.method == "POST":
d = self.request.POST
else:
d = self.request.GET
d = d.copy()
return d
@cached_property
def filter_form(self):
return OutgoingMailFilterForm(
data=self.request_data,
request=self.request,
)
def get_queryset(self):
qs = self.request.organizer.outgoing_mails.select_related(
'event', 'order', 'orderposition', 'customer'
)
if 'outgoingmail' in self.request_data and '__ALL' not in self.request_data:
qs = qs.filter(
id__in=self.request_data.getlist('outgoingmail')
)
elif self.request.method == 'GET' or '__ALL' in self.request_data:
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
else:
raise BadRequest("No mails selected")
return qs
class OutgoingMailListView(OutgoingMailQueryMixin, OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):
model = OutgoingMail
template_name = 'pretixcontrol/organizers/outgoing_mails.html'
# Assume "the highest" permission level for now because emails could belog to any event, order, or customer.
# We plan to add a special permissoin in the future
permission = 'can_change_organizer_settings'
context_object_name = 'mails'
paginate_by = 100
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
ctx['days'] = int(settings.OUTGOING_MAIL_RETENTION / (24 * 3600))
return ctx
class OutgoingMailDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, DetailView):
model = OutgoingMail
template_name = 'pretixcontrol/organizers/outgoing_mail.html'
permission = 'can_change_organizer_settings'
context_object_name = 'mail'
def get_object(self, queryset=None):
return get_object_or_404(OutgoingMail, organizer=self.request.organizer, pk=self.kwargs.get('mail'))
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
if 'Content-Security-Policy' in response:
h = _parse_csp(response['Content-Security-Policy'])
else:
h = {}
csps = {
'frame-src': ['data:'],
# Unfortuantely, we can't avoid unsafe-inline for style here.
# See outgoingmail.js for the protection measures we take.
'style-src': ["'unsafe-inline'"],
}
_merge_csp(h, csps)
response['Content-Security-Policy'] = _render_csp(h)
return response
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if self.object.body_html:
ctx['data_url'] = "data:text/html;charset=utf-8;base64," + base64.b64encode(self.object.body_html.encode()).decode()
from_name, from_email = parseaddr(self.object.sender)
if from_name:
from_name = make_header(decode_header(from_name))
ctx['sender'] = "{} <{}>".format(from_name, from_email) if from_name else from_email
return ctx
class OutgoingMailBulkAction(OutgoingMailQueryMixin, OrganizerPermissionRequiredMixin, OrganizerDetailViewMixin, View):
permission = 'can_change_organizer_settings'
@transaction.atomic
def post(self, request, *args, **kwargs):
if request.POST.get('action') == 'retry':
ids = set(
self.get_queryset().filter(status__in=OutgoingMail.STATUS_LIST_RETRYABLE).values_list("pk", flat=True)
)
with transaction.atomic():
OutgoingMail.objects.filter(pk__in=ids).update(
status=OutgoingMail.STATUS_QUEUED,
sent=None,
)
self.request.organizer.log_action(
'pretix.organizer.outgoingmails.retried', user=self.request.user, data={
'mails': list(ids)
}, save=False
)
for i in ids:
mail_send_task.apply_async(kwargs={"outgoing_mail": i})
messages.success(request, ngettext(
"A retry of one email was scheduled.",
"A retry of {num} emails was scheduled.",
len(ids),
).format(num=len(ids)))
elif request.POST.get('action') == 'abort':
ids = set(
self.get_queryset().filter(
status__in=(OutgoingMail.STATUS_QUEUED, OutgoingMail.STATUS_AWAITING_RETRY)
).values_list("pk", flat=True)
)
with transaction.atomic():
OutgoingMail.objects.filter(pk__in=ids).update(
status=OutgoingMail.STATUS_ABORTED,
sent=None,
)
self.request.organizer.log_action(
'pretix.organizer.outgoingmails.aborted', user=self.request.user, data={
'mails': list(ids)
}, save=False
)
for i in ids:
mail_send_task.apply_async(kwargs={"outgoing_mail": i})
messages.success(request, ngettext(
"One email was aborted and will not be sent.",
"{num} emails were aborted and will not be sent.",
len(ids),
).format(num=len(ids)))
return redirect(self.get_success_url())
def get_success_url(self) -> str:
return reverse('control:organizer.outgoingmails', kwargs={
'organizer': self.request.organizer.slug,
})
+36 -55
View File
@@ -98,9 +98,7 @@ from pretix.base.services.invoices import (
invoice_qualified, regenerate_invoice, transmit_invoice, invoice_qualified, regenerate_invoice, transmit_invoice,
) )
from pretix.base.services.locking import LockTimeoutException from pretix.base.services.locking import LockTimeoutException
from pretix.base.services.mail import ( from pretix.base.services.mail import prefix_subject, render_mail
SendMailException, prefix_subject, render_mail,
)
from pretix.base.services.orders import ( from pretix.base.services.orders import (
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order, OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
extend_order, mark_order_expired, mark_order_refunded, extend_order, mark_order_expired, mark_order_refunded,
@@ -1066,10 +1064,6 @@ class OrderPaymentConfirm(OrderView):
messages.error(self.request, str(e)) messages.error(self.request, str(e))
except PaymentException as e: except PaymentException as e:
messages.error(self.request, str(e)) messages.error(self.request, str(e))
except SendMailException:
messages.warning(self.request,
_('The payment has been marked as complete, but we were unable to send a '
'confirmation mail.'))
else: else:
messages.success(self.request, _('The payment has been marked as complete.')) messages.success(self.request, _('The payment has been marked as complete.'))
else: else:
@@ -1232,7 +1226,11 @@ class OrderRefundView(OrderView):
customer=order.customer, customer=order.customer,
testmode=order.testmode testmode=order.testmode
) )
giftcard.log_action('pretix.giftcards.created', user=self.request.user, data={}) giftcard.log_action(
action='pretix.giftcards.created',
user=self.request.user,
data={}
)
refunds.append(OrderRefund( refunds.append(OrderRefund(
order=order, order=order,
payment=None, payment=None,
@@ -1540,9 +1538,6 @@ class OrderTransition(OrderView):
'message': str(e) 'message': str(e)
}) })
messages.error(self.request, str(e)) messages.error(self.request, str(e))
except SendMailException:
messages.warning(self.request, _('The order has been marked as paid, but we were unable to send a '
'confirmation mail.'))
else: else:
messages.success(self.request, _('The payment has been created successfully.')) messages.success(self.request, _('The payment has been created successfully.'))
elif self.order.cancel_allowed() and to == 'c': elif self.order.cancel_allowed() and to == 'c':
@@ -1781,15 +1776,11 @@ class OrderResendLink(OrderView):
permission = 'can_change_orders' permission = 'can_change_orders'
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
try: if 'position' in kwargs:
if 'position' in kwargs: p = get_object_or_404(self.order.positions, pk=kwargs['position'])
p = get_object_or_404(self.order.positions, pk=kwargs['position']) p.resend_link(user=self.request.user)
p.resend_link(user=self.request.user) else:
else: self.order.resend_link(user=self.request.user)
self.order.resend_link(user=self.request.user)
except SendMailException:
messages.error(self.request, _('There was an error sending the mail. Please try again later.'))
return redirect(self.get_order_url())
messages.success(self.request, _('The email has been queued to be sent.')) messages.success(self.request, _('The email has been queued to be sent.'))
return redirect(self.get_order_url()) return redirect(self.get_order_url())
@@ -2433,24 +2424,18 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
} }
return self.get(self.request, *self.args, **self.kwargs) return self.get(self.request, *self.args, **self.kwargs)
else: else:
try: order.send_mail(
order.send_mail( form.cleaned_data['subject'], email_template,
form.cleaned_data['subject'], email_template, email_context, 'pretix.event.order.email.custom_sent',
email_context, 'pretix.event.order.email.custom_sent', self.request.user, auto_email=False,
self.request.user, auto_email=False, attach_tickets=form.cleaned_data.get('attach_tickets', False),
attach_tickets=form.cleaned_data.get('attach_tickets', False), invoices=form.cleaned_data.get('attach_invoices', []),
invoices=form.cleaned_data.get('attach_invoices', []), attach_other_files=[a for a in [
attach_other_files=[a for a in [ self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):] ] if a] if form.cleaned_data.get('attach_new_order', False) else [],
] if a] if form.cleaned_data.get('attach_new_order', False) else [], )
) messages.success(self.request,
messages.success(self.request, _('Your message has been queued and will be sent to {}.'.format(order.email)))
_('Your message has been queued and will be sent to {}.'.format(order.email)))
except SendMailException:
messages.error(
self.request,
_('Failed to send mail to the following user: {}'.format(order.email))
)
return super(OrderSendMail, self).form_valid(form) return super(OrderSendMail, self).form_valid(form)
def get_success_url(self): def get_success_url(self):
@@ -2503,23 +2488,19 @@ class OrderPositionSendMail(OrderSendMail):
} }
return self.get(self.request, *self.args, **self.kwargs) return self.get(self.request, *self.args, **self.kwargs)
else: else:
try: position.send_mail(
position.send_mail( form.cleaned_data['subject'],
form.cleaned_data['subject'], email_template,
email_template, email_context,
email_context, 'pretix.event.order.position.email.custom_sent',
'pretix.event.order.position.email.custom_sent', self.request.user,
self.request.user, attach_tickets=form.cleaned_data.get('attach_tickets', False),
attach_tickets=form.cleaned_data.get('attach_tickets', False), attach_other_files=[a for a in [
attach_other_files=[a for a in [ self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):] ] if a] if form.cleaned_data.get('attach_new_order', False) else [],
] if a] if form.cleaned_data.get('attach_new_order', False) else [], )
) messages.success(self.request,
messages.success(self.request, _('Your message has been queued and will be sent to {}.'.format(position.attendee_email)))
_('Your message has been queued and will be sent to {}.'.format(position.attendee_email)))
except SendMailException:
messages.error(self.request,
_('Failed to send mail to the following user: {}'.format(position.attendee_email)))
return super(OrderSendMail, self).form_valid(form) return super(OrderSendMail, self).form_valid(form)
+63 -38
View File
@@ -103,7 +103,7 @@ from pretix.base.plugins import (
PLUGIN_LEVEL_ORGANIZER, PLUGIN_LEVEL_ORGANIZER,
) )
from pretix.base.services.export import multiexport, scheduled_organizer_export from pretix.base.services.export import multiexport, scheduled_organizer_export
from pretix.base.services.mail import SendMailException, mail, prefix_subject from pretix.base.services.mail import mail, prefix_subject
from pretix.base.signals import register_multievent_data_exporters from pretix.base.signals import register_multievent_data_exporters
from pretix.base.templatetags.rich_text import markdown_compile_email from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.base.views.tasks import AsyncAction from pretix.base.views.tasks import AsyncAction
@@ -1037,24 +1037,21 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
return ctx return ctx
def _send_invite(self, instance): def _send_invite(self, instance):
try: mail(
mail( instance.email,
instance.email, _('pretix account invitation'),
_('pretix account invitation'), 'pretixcontrol/email/invitation.txt',
'pretixcontrol/email/invitation.txt', {
{ 'user': self,
'user': self, 'organizer': self.request.organizer.name,
'organizer': self.request.organizer.name, 'team': instance.team.name,
'team': instance.team.name, 'url': build_global_uri('control:auth.invite', kwargs={
'url': build_global_uri('control:auth.invite', kwargs={ 'token': instance.token
'token': instance.token })
}) },
}, event=None,
event=None, locale=self.request.LANGUAGE_CODE
locale=self.request.LANGUAGE_CODE )
)
except SendMailException:
pass # Already logged
@transaction.atomic @transaction.atomic
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@@ -1670,9 +1667,12 @@ class GiftCardAcceptanceInviteView(OrganizerDetailViewMixin, OrganizerPermission
active=False, active=False,
) )
self.request.organizer.log_action( self.request.organizer.log_action(
'pretix.giftcards.acceptance.acceptor.invited', action='pretix.giftcards.acceptance.acceptor.invited',
data={'acceptor': form.cleaned_data['acceptor'].slug, data={
'reusable_media': form.cleaned_data['reusable_media']}, 'acceptor': form.cleaned_data['acceptor'].slug,
'issuer': self.request.organizer.slug,
'reusable_media': form.cleaned_data['reusable_media']
},
user=self.request.user user=self.request.user
) )
messages.success(self.request, _('The selected organizer has been invited.')) messages.success(self.request, _('The selected organizer has been invited.'))
@@ -1708,8 +1708,11 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
).delete() ).delete()
if done: if done:
self.request.organizer.log_action( self.request.organizer.log_action(
'pretix.giftcards.acceptance.acceptor.removed', action='pretix.giftcards.acceptance.acceptor.removed',
data={'acceptor': request.POST.get("delete_acceptor")}, data={
'acceptor': request.POST.get("delete_acceptor"),
'issuer': self.request.organizer.slug
},
user=request.user user=request.user
) )
messages.success(self.request, _('The selected connection has been removed.')) messages.success(self.request, _('The selected connection has been removed.'))
@@ -1719,8 +1722,11 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
).delete() ).delete()
if done: if done:
self.request.organizer.log_action( self.request.organizer.log_action(
'pretix.giftcards.acceptance.issuer.removed', action='pretix.giftcards.acceptance.issuer.removed',
data={'issuer': request.POST.get("delete_acceptor")}, data={
'issuer': request.POST.get("delete_acceptor"),
'acceptor': self.request.organizer.slug
},
user=request.user user=request.user
) )
messages.success(self.request, _('The selected connection has been removed.')) messages.success(self.request, _('The selected connection has been removed.'))
@@ -1730,8 +1736,11 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
).update(active=True) ).update(active=True)
if done: if done:
self.request.organizer.log_action( self.request.organizer.log_action(
'pretix.giftcards.acceptance.issuer.accepted', action='pretix.giftcards.acceptance.issuer.accepted',
data={'issuer': request.POST.get("accept_issuer")}, data={
'issuer': request.POST.get("accept_issuer"),
'acceptor': self.request.organizer.slug
},
user=request.user user=request.user
) )
messages.success(self.request, _('The selected connection has been accepted.')) messages.success(self.request, _('The selected connection has been accepted.'))
@@ -1837,10 +1846,12 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
acceptor=request.organizer, acceptor=request.organizer,
) )
self.object.log_action( self.object.log_action(
'pretix.giftcards.transaction.manual', action='pretix.giftcards.transaction.manual',
data={ data={
'value': value, 'value': value,
'text': request.POST.get('text') 'text': request.POST.get('text'),
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}, },
user=self.request.user, user=self.request.user,
) )
@@ -1889,15 +1900,24 @@ class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
messages.success(self.request, _('The gift card has been created and can now be used.')) messages.success(self.request, _('The gift card has been created and can now be used.'))
form.instance.issuer = self.request.organizer form.instance.issuer = self.request.organizer
super().form_valid(form) super().form_valid(form)
form.instance.transactions.create( form.instance.log_action(
acceptor=self.request.organizer, action='pretix.giftcards.created',
value=form.cleaned_data['value'] user=self.request.user,
) )
form.instance.log_action('pretix.giftcards.created', user=self.request.user, data={})
if form.cleaned_data['value']: if form.cleaned_data['value']:
form.instance.log_action('pretix.giftcards.transaction.manual', user=self.request.user, data={ form.instance.transactions.create(
'value': form.cleaned_data['value'] acceptor=self.request.organizer,
}) value=form.cleaned_data['value']
)
form.instance.log_action(
action='pretix.giftcards.transaction.manual',
user=self.request.user,
data={
'value': form.cleaned_data['value'],
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
)
return redirect(reverse( return redirect(reverse(
'control:organizer.giftcard', 'control:organizer.giftcard',
kwargs={ kwargs={
@@ -1925,7 +1945,11 @@ class GiftCardUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('The gift card has been changed.')) messages.success(self.request, _('The gift card has been changed.'))
super().form_valid(form) super().form_valid(form)
form.instance.log_action('pretix.giftcards.modified', user=self.request.user, data=dict(form.cleaned_data)) form.instance.log_action(
action='pretix.giftcards.modified',
user=self.request.user,
data=dict(form.cleaned_data)
)
return redirect(reverse( return redirect(reverse(
'control:organizer.giftcard', 'control:organizer.giftcard',
kwargs={ kwargs={
@@ -3027,6 +3051,7 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
locale=self.customer.locale, locale=self.customer.locale,
customer=self.customer, customer=self.customer,
organizer=self.request.organizer, organizer=self.request.organizer,
sensitive=True,
) )
messages.success( messages.success(
self.request, self.request,
+10 -9
View File
@@ -49,12 +49,14 @@ from django.db import transaction
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.http import url_has_allowed_host_and_scheme from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, ListView, TemplateView, UpdateView from django.views.generic import FormView, ListView, TemplateView, UpdateView
from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_static.models import StaticDevice
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
@@ -85,8 +87,9 @@ logger = logging.getLogger(__name__)
class RecentAuthenticationRequiredMixin: class RecentAuthenticationRequiredMixin:
max_time = 3600 max_time = 900
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
tdelta = time.time() - request.session.get('pretix_auth_login_time', 0) tdelta = time.time() - request.session.get('pretix_auth_login_time', 0)
if tdelta > self.max_time: if tdelta > self.max_time:
@@ -289,16 +292,13 @@ class User2FAMainView(RecentAuthenticationRequiredMixin, TemplateView):
ctx = super().get_context_data() ctx = super().get_context_data()
try: try:
ctx['static_tokens'] = StaticDevice.objects.get(user=self.request.user, name='emergency').token_set.all() ctx['static_tokens_device'] = StaticDevice.objects.get(user=self.request.user, name='emergency')
except StaticDevice.MultipleObjectsReturned: except StaticDevice.MultipleObjectsReturned:
ctx['static_tokens'] = StaticDevice.objects.filter( ctx['static_tokens_device'] = StaticDevice.objects.filter(
user=self.request.user, name='emergency' user=self.request.user, name='emergency'
).first().token_set.all() ).first()
except StaticDevice.DoesNotExist: except StaticDevice.DoesNotExist:
d = StaticDevice.objects.create(user=self.request.user, name='emergency') ctx['static_tokens_device'] = None
for i in range(10):
d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890'))
ctx['static_tokens'] = d.token_set.all()
ctx['devices'] = [] ctx['devices'] = []
for dt in REAL_DEVICE_TYPES: for dt in REAL_DEVICE_TYPES:
@@ -631,7 +631,8 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template
self.request.user.update_session_token() self.request.user.update_session_token()
update_session_auth_hash(self.request, self.request.user) update_session_auth_hash(self.request, self.request.user)
messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe ' messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe '
'place in case you lose access to your devices.')) 'place in case you lose access to your devices. You will not be able to view them '
'again here.\n\nYour emergency codes:\n- ' + '\n- '.join(t.token for t in d.token_set.all())))
return redirect(reverse('control:user.settings.2fa')) return redirect(reverse('control:user.settings.2fa'))
+1 -6
View File
@@ -41,7 +41,6 @@ from hijack import signals
from pretix.base.auth import get_auth_backends from pretix.base.auth import get_auth_backends
from pretix.base.models import User from pretix.base.models import User
from pretix.base.services.mail import SendMailException
from pretix.control.forms.filter import UserFilterForm from pretix.control.forms.filter import UserFilterForm
from pretix.control.forms.users import UserEditForm from pretix.control.forms.users import UserEditForm
from pretix.control.permissions import AdministratorPermissionRequiredMixin from pretix.control.permissions import AdministratorPermissionRequiredMixin
@@ -139,11 +138,7 @@ class UserResetView(AdministratorPermissionRequiredMixin, RecentAuthenticationRe
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = get_object_or_404(User, pk=self.kwargs.get("id")) self.object = get_object_or_404(User, pk=self.kwargs.get("id"))
try: self.object.send_password_reset()
self.object.send_password_reset()
except SendMailException:
messages.error(request, _('There was an error sending the mail. Please try again later.'))
return redirect(self.get_success_url())
self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent', self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent',
user=request.user) user=request.user)
+27 -3
View File
@@ -34,7 +34,10 @@ def set_cookie_without_samesite(request, response, key, *args, **kwargs):
if not is_secure: if not is_secure:
# https://www.chromestatus.com/feature/5633521622188032 # https://www.chromestatus.com/feature/5633521622188032
return return
if should_send_same_site_none(request.headers.get('User-Agent', '')):
useragent = request.headers.get('User-Agent', '')
if should_send_same_site_none(useragent):
# Chromium is rolling out SameSite=Lax as a default # Chromium is rolling out SameSite=Lax as a default
# https://www.chromestatus.com/feature/5088147346030592 # https://www.chromestatus.com/feature/5088147346030592
# This however breaks all pretix-in-an-iframe things, such as the pretix Widget. # This however breaks all pretix-in-an-iframe things, such as the pretix Widget.
@@ -44,8 +47,29 @@ def set_cookie_without_samesite(request, response, key, *args, **kwargs):
# This will only work on secure cookies as well # This will only work on secure cookies as well
# https://www.chromestatus.com/feature/5633521622188032 # https://www.chromestatus.com/feature/5633521622188032
response.cookies[key]['secure'] = is_secure response.cookies[key]['secure'] = is_secure
# CHIPS
response.cookies[key]['Partitioned'] = True if can_send_partitioned_cookie(useragent):
# CHIPS
response.cookies[key]['Partitioned'] = True
def can_send_partitioned_cookie(useragent):
# Safari currently exhibits a bug where Partitioned cookies (CHIPS) are not
# sent back to the originating site after multi-hop cross-site redirects,
# breaking SSO login flows in pretix.
#
# Partitioned cookies were initially introduced in Safari 18.4, removed
# again in 18.5 due to a bug, and reintroduced in Safari 26.2, where the
# current issue is present.
#
# Once the Safari issue is fixed, this check should be refined to be
# conditional on the affected versions only.
#
# WebKit issues:
#
# - https://bugs.webkit.org/show_bug.cgi?id=292975
# - https://bugs.webkit.org/show_bug.cgi?id=306194
return not is_safari(useragent)
# Based on https://www.chromium.org/updates/same-site/incompatible-clients # Based on https://www.chromium.org/updates/same-site/incompatible-clients
+4 -1
View File
@@ -148,4 +148,7 @@ def pycountry_add(db, **kw):
continue continue
value = value.lower() value = value.lower()
index = db.indices.setdefault(key, {}) index = db.indices.setdefault(key, {})
index.setdefault(value, set()).add(obj) if key in ["country_code"]:
index.setdefault(value, set()).add(obj)
else:
index[value] = obj
+14 -1
View File
@@ -25,7 +25,7 @@ from django.conf import settings
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.db import connection, transaction from django.db import connection, transaction
from django.db.models import ( from django.db.models import (
Aggregate, Expression, F, Field, Lookup, OrderBy, Value, Aggregate, Expression, F, Field, JSONField, Lookup, OrderBy, Value,
) )
from django.utils.functional import lazy from django.utils.functional import lazy
@@ -154,6 +154,19 @@ class NotEqual(Lookup):
return '%s <> %s' % (lhs, rhs), params return '%s <> %s' % (lhs, rhs), params
@JSONField.register_lookup
class ContainsString(Lookup):
lookup_name = 'containsstring'
def as_sql(self, compiler, connection):
if connection.vendor != "postgresql":
raise NotImplementedError("Lookup in JSON Array not supported on this database")
lhs, lhs_params = self.process_lhs(compiler, connection)
rhs, rhs_params = self.process_rhs(compiler, connection)
params = lhs_params + rhs_params
return '%s ? %s' % (lhs, rhs), params
class PostgresWindowFrame(Expression): class PostgresWindowFrame(Expression):
template = "%(frame_type)s BETWEEN %(start)s AND %(end)s" template = "%(frame_type)s BETWEEN %(start)s AND %(end)s"
+25 -2
View File
@@ -22,6 +22,7 @@
import logging import logging
from string import Formatter from string import Formatter
from django.core.exceptions import SuspiciousOperation
from django.utils.html import conditional_escape from django.utils.html import conditional_escape
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -37,6 +38,17 @@ class PlainHtmlAlternativeString:
return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')" return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')"
class FormattedString(str):
"""
A str subclass that has been specifically marked as "already formatted" for email rendering
purposes to avoid duplicate formatting.
"""
__slots__ = ()
def __str__(self):
return self
class SafeFormatter(Formatter): class SafeFormatter(Formatter):
""" """
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
@@ -77,8 +89,19 @@ class SafeFormatter(Formatter):
# Ignore format_spec # Ignore format_spec
return super().format_field(self._prepare_value(value), '') return super().format_field(self._prepare_value(value), '')
def convert_field(self, value, conversion):
# Ignore any conversions
if conversion is None:
return value
else:
return str(value)
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None):
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None) -> FormattedString:
if isinstance(template, FormattedString):
raise SuspiciousOperation("Calling format_map() on an already formatted string is likely unsafe.")
if not isinstance(template, str): if not isinstance(template, str):
template = str(template) template = str(template)
return SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template) return FormattedString(
SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)
)
+16 -19
View File
@@ -32,7 +32,7 @@ from django_countries.fields import Country
from geoip2.errors import AddressNotFoundError from geoip2.errors import AddressNotFoundError
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import mail
from pretix.helpers.http import get_client_ip from pretix.helpers.http import get_client_ip
from pretix.helpers.urls import build_absolute_uri from pretix.helpers.urls import build_absolute_uri
@@ -159,21 +159,18 @@ def handle_login_source(user, request):
}) })
if user.known_login_sources.count() > 1: if user.known_login_sources.count() > 1:
# Do not send on first login or first login after introduction of this feature: # Do not send on first login or first login after introduction of this feature:
try: with language(user.locale):
with language(user.locale): mail(
mail( user.email,
user.email, _('Login from new source detected'),
_('Login from new source detected'), 'pretixcontrol/email/login_notice.txt',
'pretixcontrol/email/login_notice.txt', {
{ 'source': src,
'source': src, 'country': Country(str(country)).name if country else _('Unknown country'),
'country': Country(str(country)).name if country else _('Unknown country'), 'instance': settings.PRETIX_INSTANCE_NAME,
'instance': settings.PRETIX_INSTANCE_NAME, 'url': build_absolute_uri('control:user.settings')
'url': build_absolute_uri('control:user.settings') },
}, event=None,
event=None, user=user,
user=user, locale=user.locale
locale=user.locale )
)
except SendMailException:
pass # Not much we can do
+3 -3
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n" "POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2026-01-28 18:00+0000\n" "PO-Revision-Date: 2026-01-29 19:42+0000\n"
"Last-Translator: Jiří Pastrňák <jiri@pastrnak.email>\n" "Last-Translator: Jiří Pastrňák <jiri@pastrnak.email>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>" "Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
"\n" "\n"
@@ -2848,7 +2848,7 @@ msgstr "Stavy platby"
#: pretix/base/exporters/orderlist.py:1089 #: pretix/base/exporters/orderlist.py:1089
msgid "Refund states" msgid "Refund states"
msgstr "Stav vrácení peněz" msgstr "Stavy vrácení peněz"
#: pretix/base/exporters/orderlist.py:1132 #: pretix/base/exporters/orderlist.py:1132
#: pretix/base/exporters/orderlist.py:1274 #: pretix/base/exporters/orderlist.py:1274
@@ -14841,7 +14841,7 @@ msgstr "Jakýkoli produkt v kvótě \"{quota}\""
#: pretix/control/forms/filter.py:2439 #: pretix/control/forms/filter.py:2439
msgid "Refund status" msgid "Refund status"
msgstr "Stav náhrady" msgstr "Stav vrácení peněz"
#: pretix/control/forms/filter.py:2441 #: pretix/control/forms/filter.py:2441
msgid "All open refunds" msgid "All open refunds"
+27 -18
View File
@@ -4,16 +4,16 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n" "POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2025-08-27 22:00+0000\n" "PO-Revision-Date: 2026-02-03 17:12+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n" "Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/" "Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/"
">\n" "da/>\n"
"Language: da\n" "Language: da\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.13\n" "X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -35175,30 +35175,31 @@ msgid "Yes, request cancellation"
msgstr "Generér afbestilling" msgstr "Generér afbestilling"
#: pretix/presale/templates/pretixpresale/event/order_change_confirm.html:19 #: pretix/presale/templates/pretixpresale/event/order_change_confirm.html:19
#, fuzzy
msgid "Please confirm the following changes to your order." msgid "Please confirm the following changes to your order."
msgstr "Kontroller venligst detaljerne nedenfor og bekræft din bestilling." msgstr "Bekræft venligst følgende ændringer i din ordre."
#: pretix/presale/templates/pretixpresale/event/order_giftcard.html:10 #: pretix/presale/templates/pretixpresale/event/order_giftcard.html:10
#: pretix/presale/templates/pretixpresale/event/position_giftcard.html:10 #: pretix/presale/templates/pretixpresale/event/position_giftcard.html:10
#, fuzzy, python-format #, python-format
msgid "Gift card: %(code)s" msgid "Gift card: %(code)s"
msgstr "Gavekort" msgstr "Gavekort: %(code)s"
#: pretix/presale/templates/pretixpresale/event/order_modify.html:5 #: pretix/presale/templates/pretixpresale/event/order_modify.html:5
msgid "Modify order" msgid "Modify order"
msgstr "Rediger bestilling" msgstr "Redigér ordre"
#: pretix/presale/templates/pretixpresale/event/order_modify.html:8 #: pretix/presale/templates/pretixpresale/event/order_modify.html:8
#, python-format #, python-format
msgid "Modify order: %(code)s" msgid "Modify order: %(code)s"
msgstr "" msgstr "Rediger ordre: %(code)s"
#: pretix/presale/templates/pretixpresale/event/order_modify.html:18 #: pretix/presale/templates/pretixpresale/event/order_modify.html:18
msgid "" msgid ""
"Modifying your invoice address will not automatically generate a new " "Modifying your invoice address will not automatically generate a new "
"invoice. Please contact us if you need a new invoice." "invoice. Please contact us if you need a new invoice."
msgstr "" msgstr ""
"Ændring af din fakturaadresse vil ikke automatisk generere en ny faktura. "
"Kontakt os venligst, hvis du har brug for en ny faktura."
#: pretix/presale/templates/pretixpresale/event/order_modify.html:88 #: pretix/presale/templates/pretixpresale/event/order_modify.html:88
#: pretix/presale/templates/pretixpresale/event/position_modify.html:49 #: pretix/presale/templates/pretixpresale/event/position_modify.html:49
@@ -35210,19 +35211,22 @@ msgid "Change payment method"
msgstr "Skift betalingsmetode" msgstr "Skift betalingsmetode"
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html:13 #: pretix/presale/templates/pretixpresale/event/order_pay_change.html:13
#, fuzzy, python-format #, python-format
msgid "Choose payment method: %(code)s" msgid "Choose payment method: %(code)s"
msgstr "Skift betalingsmetode: %(code)s" msgstr "Vælg betalingsmetode: %(code)s"
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html:19 #: pretix/presale/templates/pretixpresale/event/order_pay_change.html:19
msgid "" msgid ""
"Please note: If you change your payment method, your order total will change " "Please note: If you change your payment method, your order total will change "
"by the amount displayed to the right of each method." "by the amount displayed to the right of each method."
msgstr "" msgstr ""
"Bemærk: Hvis du ændrer din betalingsmetode, ændres din ordres samlede beløb "
"med det beløb, der vises til højre for hver metode."
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html:61 #: pretix/presale/templates/pretixpresale/event/order_pay_change.html:61
msgid "There are no alternative payment providers available for this order." msgid "There are no alternative payment providers available for this order."
msgstr "" msgstr ""
"Der er ingen alternative betalingsudbydere tilgængelige for denne ordre."
#: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html:16 #: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html:16
msgid "Please confirm the following payment details." msgid "Please confirm the following payment details."
@@ -35231,7 +35235,7 @@ msgstr "Bekræft venligst følgende betalingsoplysninger."
#: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html:22 #: pretix/presale/templates/pretixpresale/event/order_pay_confirm.html:22
#, python-format #, python-format
msgid "Total: %(total)s" msgid "Total: %(total)s"
msgstr "" msgstr "Total: %(total)s"
#: pretix/presale/templates/pretixpresale/event/payment_qr_codes.html:17 #: pretix/presale/templates/pretixpresale/event/payment_qr_codes.html:17
msgid "" msgid ""
@@ -35270,10 +35274,9 @@ msgid ""
msgstr "" msgstr ""
#: pretix/presale/templates/pretixpresale/event/position.html:63 #: pretix/presale/templates/pretixpresale/event/position.html:63
#, fuzzy
msgctxt "action" msgctxt "action"
msgid "Change your ticket" msgid "Change your ticket"
msgstr "Ændr pris til" msgstr "Ændr din billet"
#: pretix/presale/templates/pretixpresale/event/position.html:68 #: pretix/presale/templates/pretixpresale/event/position.html:68
msgid "" msgid ""
@@ -35356,30 +35359,36 @@ msgstr "Aktiver betalingsmetode"
#: pretix/presale/templates/pretixpresale/event/voucher.html:36 #: pretix/presale/templates/pretixpresale/event/voucher.html:36
#: pretix/presale/templates/pretixpresale/event/voucher_form.html:9 #: pretix/presale/templates/pretixpresale/event/voucher_form.html:9
msgid "Voucher redemption" msgid "Voucher redemption"
msgstr "" msgstr "Indløsning af rabatkode"
#: pretix/presale/templates/pretixpresale/event/voucher.html:20 #: pretix/presale/templates/pretixpresale/event/voucher.html:20
#, fuzzy
msgid "This voucher is valid only for the following specific date and time." msgid "This voucher is valid only for the following specific date and time."
msgstr "Voucheren er ikke gyldig for denne dato." msgstr "Rabatkoden er ikke gyldig for denne dato."
#: pretix/presale/templates/pretixpresale/event/voucher.html:43 #: pretix/presale/templates/pretixpresale/event/voucher.html:43
msgid "" msgid ""
"For the selected date, there are currently no products available that can be " "For the selected date, there are currently no products available that can be "
"bought with this voucher. Please try a different date or a different voucher." "bought with this voucher. Please try a different date or a different voucher."
msgstr "" msgstr ""
"Der er i øjeblikket ingen produkter tilgængelige for den valgte dato, som "
"kan købes med denne rabatkode. Prøv venligst en anden dato eller en anden "
"rabatkode."
#: pretix/presale/templates/pretixpresale/event/voucher.html:47 #: pretix/presale/templates/pretixpresale/event/voucher.html:47
msgid "" msgid ""
"There are currently no products available that can be bought with this " "There are currently no products available that can be bought with this "
"voucher." "voucher."
msgstr "" msgstr ""
"Der er i øjeblikket ingen produkter tilgængelige, der kan købes med denne "
"rabatkode."
#: pretix/presale/templates/pretixpresale/event/voucher.html:52 #: pretix/presale/templates/pretixpresale/event/voucher.html:52
msgid "" msgid ""
"You entered a voucher code that allows you to buy one of the following " "You entered a voucher code that allows you to buy one of the following "
"products at the specified price:" "products at the specified price:"
msgstr "" msgstr ""
"Du har indtastet en rabatkode, der giver dig mulighed for at købe et af "
"følgende produkter til den angivne pris:"
#: pretix/presale/templates/pretixpresale/event/voucher.html:112 #: pretix/presale/templates/pretixpresale/event/voucher.html:112
#, python-format #, python-format
+139 -206
View File
@@ -3,16 +3,16 @@ msgstr ""
"Project-Id-Version: HE PRETIX\n" "Project-Id-Version: HE PRETIX\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n" "POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2025-05-21 10:46+0000\n" "PO-Revision-Date: 2026-02-09 21:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n" "Last-Translator: roi belotsercovsky <rbelotsercovsky@gmail.com>\n"
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix/he/" "Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix/"
">\n" "he/>\n"
"Language: he\n" "Language: he\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Weblate 5.11.4\n" "X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -84,7 +84,7 @@ msgstr "יוונית"
#: pretix/_base_settings.py:104 #: pretix/_base_settings.py:104
msgid "Hebrew" msgid "Hebrew"
msgstr "" msgstr "עברית"
#: pretix/_base_settings.py:105 #: pretix/_base_settings.py:105
msgid "Indonesian" msgid "Indonesian"
@@ -140,7 +140,7 @@ msgstr "ספרדית"
#: pretix/_base_settings.py:118 #: pretix/_base_settings.py:118
msgid "Spanish (Latin America)" msgid "Spanish (Latin America)"
msgstr "" msgstr "ספרדית (אמריקה הלטינית)"
#: pretix/_base_settings.py:119 #: pretix/_base_settings.py:119
msgid "Turkish" msgid "Turkish"
@@ -285,28 +285,20 @@ msgid "The bundled item must not have bundles on its own."
msgstr "הפריט המצורף לא יכול להחזיק חבילות בעצמו." msgstr "הפריט המצורף לא יכול להחזיק חבילות בעצמו."
#: pretix/api/serializers/item.py:235 #: pretix/api/serializers/item.py:235
#, fuzzy
#| msgid "The payment is too late to be accepted."
msgid "The program start must not be empty." msgid "The program start must not be empty."
msgstr "התשלום התקבל מאוחר מדי." msgstr "תחילת התוכנית לא יכולה להיות ריקה."
#: pretix/api/serializers/item.py:239 #: pretix/api/serializers/item.py:239
#, fuzzy
#| msgid "The payment is too late to be accepted."
msgid "The program end must not be empty." msgid "The program end must not be empty."
msgstr "התשלום התקבל מאוחר מדי." msgstr "סוף התוכנית לא יכול להיות ריק."
#: pretix/api/serializers/item.py:242 pretix/base/models/items.py:2322 #: pretix/api/serializers/item.py:242 pretix/base/models/items.py:2322
#, fuzzy
#| msgid "The maximum date must not be before the minimum value."
msgid "The program end must not be before the program start." msgid "The program end must not be before the program start."
msgstr "התאריך המרבי לא יכול להיות לפני הערך המינימלי." msgstr "סוף התוכנית לא יכול להיות לפני תחילת התוכנית."
#: pretix/api/serializers/item.py:247 pretix/base/models/items.py:2316 #: pretix/api/serializers/item.py:247 pretix/base/models/items.py:2316
#, fuzzy
#| msgid "You can not select a subevent if your event is not an event series."
msgid "You cannot use program times on an event series." msgid "You cannot use program times on an event series."
msgstr "לא ניתן לבחור תת-אירוע אם האירוע שלך אינו סדרת אירועים." msgstr "לא ניתן לבחור שעות תוכניות בסדרת אירועים."
#: pretix/api/serializers/item.py:337 #: pretix/api/serializers/item.py:337
#, fuzzy #, fuzzy
@@ -569,22 +561,15 @@ msgid "Event series date deleted"
msgstr "תאריך סדרת אירועים נמחק" msgstr "תאריך סדרת אירועים נמחק"
#: pretix/api/webhooks.py:375 #: pretix/api/webhooks.py:375
#, fuzzy
#| msgid "Product name"
msgid "Product changed" msgid "Product changed"
msgstr "שם המוצר" msgstr "המוצר השתנה"
#: pretix/api/webhooks.py:376 #: pretix/api/webhooks.py:376
#, fuzzy
#| msgid ""
#| "Product changed (including product added or deleted and including changes "
#| "to nested objects like variations or bundles)"
msgid "" msgid ""
"This includes product added or deleted and changes to nested objects like " "This includes product added or deleted and changes to nested objects like "
"variations or bundles." "variations or bundles."
msgstr "" msgstr ""
"המוצר שונה (כולל הוספה או מחיקה של מוצר ושינויים לאובייקטים מקוננים כמו " "זה כולל מוצר שהוסף או נמחק ושונה לאובייקטים מקוננים כמו וריאציות או באנדלים"
"וריאציות או חבילות)"
#: pretix/api/webhooks.py:381 #: pretix/api/webhooks.py:381
msgid "Shop taken live" msgid "Shop taken live"
@@ -619,28 +604,22 @@ msgid "Waiting list entry received voucher"
msgstr "רשומת רשימת המתנה קיבלה שובר" msgstr "רשומת רשימת המתנה קיבלה שובר"
#: pretix/api/webhooks.py:413 #: pretix/api/webhooks.py:413
#, fuzzy
#| msgid "Voucher code"
msgid "Voucher added" msgid "Voucher added"
msgstr "קוד שובר" msgstr "קוד שובר הוסף"
#: pretix/api/webhooks.py:417 #: pretix/api/webhooks.py:417
#, fuzzy
#| msgid "Voucher assigned"
msgid "Voucher changed" msgid "Voucher changed"
msgstr "שובר הוקצה" msgstr "שובר שונה"
#: pretix/api/webhooks.py:418 #: pretix/api/webhooks.py:418
msgid "" msgid ""
"Only includes explicit changes to the voucher, not e.g. an increase of the " "Only includes explicit changes to the voucher, not e.g. an increase of the "
"number of redemptions." "number of redemptions."
msgstr "" msgstr "כולל רק שינויים מפורשים לשובר, לא למשל העלאת מספר המימושים."
#: pretix/api/webhooks.py:422 #: pretix/api/webhooks.py:422
#, fuzzy
#| msgid "Voucher redeemed"
msgid "Voucher deleted" msgid "Voucher deleted"
msgstr "שובר מומש" msgstr "שובר נמחק"
#: pretix/api/webhooks.py:426 #: pretix/api/webhooks.py:426
msgid "Customer account created" msgid "Customer account created"
@@ -811,7 +790,7 @@ msgstr ""
msgid "" msgid ""
"Field \"{field_name}\" does not exist. Please check your {provider_name} " "Field \"{field_name}\" does not exist. Please check your {provider_name} "
"settings." "settings."
msgstr "" msgstr "שדה \"{field_name}\" לא קיים. נא לבדוק את הגדרות {provider_name}."
#: pretix/base/datasync/datasync.py:271 #: pretix/base/datasync/datasync.py:271
#, python-brace-format #, python-brace-format
@@ -819,25 +798,23 @@ msgid ""
"Field \"{field_name}\" requires {required_input}, but only got " "Field \"{field_name}\" requires {required_input}, but only got "
"{available_inputs}. Please check your {provider_name} settings." "{available_inputs}. Please check your {provider_name} settings."
msgstr "" msgstr ""
"שדה \"{field_name}\" דורש {required_input}, אבל קיבל רק {available_inputs}"
". נא לבדוק הגדרות {provider_name}."
#: pretix/base/datasync/datasync.py:282 #: pretix/base/datasync/datasync.py:282
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"Please update value mapping for field \"{field_name}\" - option \"{val}\" " "Please update value mapping for field \"{field_name}\" - option \"{val}\" "
"not assigned" "not assigned"
msgstr "" msgstr "נא לעדכן מיפוי ערכים לשדה \"{field_name}\" - אפשרות \"{val}\" לא מוקצה"
#: pretix/base/datasync/sourcefields.py:128 #: pretix/base/datasync/sourcefields.py:128
#, fuzzy
#| msgid "Order positions"
msgid "Order position details" msgid "Order position details"
msgstr "פריטי הזמנה" msgstr "פרטי מצב הזמנה"
#: pretix/base/datasync/sourcefields.py:129 #: pretix/base/datasync/sourcefields.py:129
#, fuzzy
#| msgid "Attendee email"
msgid "Attendee details" msgid "Attendee details"
msgstr "דוא\"ל משתתף" msgstr "פרטי משתתף"
#: pretix/base/datasync/sourcefields.py:130 pretix/base/exporters/answers.py:66 #: pretix/base/datasync/sourcefields.py:130 pretix/base/exporters/answers.py:66
#: pretix/base/models/items.py:1767 pretix/control/navigation.py:172 #: pretix/base/models/items.py:1767 pretix/control/navigation.py:172
@@ -847,10 +824,8 @@ msgid "Questions"
msgstr "שאלות" msgstr "שאלות"
#: pretix/base/datasync/sourcefields.py:131 #: pretix/base/datasync/sourcefields.py:131
#, fuzzy
#| msgid "Product data"
msgid "Product details" msgid "Product details"
msgstr "נתוני מוצר" msgstr "פרטי מוצר"
#: pretix/base/datasync/sourcefields.py:132 #: pretix/base/datasync/sourcefields.py:132
#: pretix/control/templates/pretixcontrol/event/settings.html:279 #: pretix/control/templates/pretixcontrol/event/settings.html:279
@@ -875,17 +850,13 @@ msgid "Invoice address"
msgstr "כתובת לחשבונית" msgstr "כתובת לחשבונית"
#: pretix/base/datasync/sourcefields.py:134 #: pretix/base/datasync/sourcefields.py:134
#, fuzzy
#| msgid "Meta information"
msgid "Event information" msgid "Event information"
msgstr "מידע מטא" msgstr "מידע על האירוע"
#: pretix/base/datasync/sourcefields.py:135 #: pretix/base/datasync/sourcefields.py:135
#, fuzzy
#| msgid "Send recovery information"
msgctxt "subevent" msgctxt "subevent"
msgid "Event or date information" msgid "Event or date information"
msgstr "שלח פרטי שחזור" msgstr "מידע על האירוע או התאריך"
#: pretix/base/datasync/sourcefields.py:175 #: pretix/base/datasync/sourcefields.py:175
#: pretix/base/exporters/orderlist.py:638 #: pretix/base/exporters/orderlist.py:638
@@ -910,10 +881,8 @@ msgstr "שם משתתף"
#: pretix/base/datasync/sourcefields.py:187 #: pretix/base/datasync/sourcefields.py:187
#: pretix/base/datasync/sourcefields.py:604 #: pretix/base/datasync/sourcefields.py:604
#: pretix/base/datasync/sourcefields.py:628 #: pretix/base/datasync/sourcefields.py:628
#, fuzzy
#| msgid "Attendee name"
msgid "Attendee" msgid "Attendee"
msgstr "שם משתתף" msgstr "משתתף"
#: pretix/base/datasync/sourcefields.py:207 #: pretix/base/datasync/sourcefields.py:207
#: pretix/base/exporters/orderlist.py:645 pretix/base/forms/questions.py:693 #: pretix/base/exporters/orderlist.py:645 pretix/base/forms/questions.py:693
@@ -927,10 +896,8 @@ msgid "Attendee email"
msgstr "דוא\"ל משתתף" msgstr "דוא\"ל משתתף"
#: pretix/base/datasync/sourcefields.py:219 #: pretix/base/datasync/sourcefields.py:219
#, fuzzy
#| msgid "Attendee email"
msgid "Attendee or order email" msgid "Attendee or order email"
msgstr "דוא\"ל משתתף" msgstr "דוא\"ל משתתף או הזמנה"
#: pretix/base/datasync/sourcefields.py:232 #: pretix/base/datasync/sourcefields.py:232
#: pretix/base/exporters/orderlist.py:646 pretix/base/pdf.py:188 #: pretix/base/exporters/orderlist.py:646 pretix/base/pdf.py:188
@@ -943,10 +910,8 @@ msgid "Attendee company"
msgstr "חברת המשתתף" msgstr "חברת המשתתף"
#: pretix/base/datasync/sourcefields.py:241 #: pretix/base/datasync/sourcefields.py:241
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address street" msgid "Attendee address street"
msgstr "כתובת המשתתף" msgstr "רחוב המשתתף"
#: pretix/base/datasync/sourcefields.py:250 #: pretix/base/datasync/sourcefields.py:250
#, fuzzy #, fuzzy
@@ -955,16 +920,12 @@ msgid "Attendee address ZIP code"
msgstr "מיקוד המשתתף" msgstr "מיקוד המשתתף"
#: pretix/base/datasync/sourcefields.py:259 #: pretix/base/datasync/sourcefields.py:259
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address city" msgid "Attendee address city"
msgstr "כתובת המשתתף" msgstr "עיר המשתתף"
#: pretix/base/datasync/sourcefields.py:268 #: pretix/base/datasync/sourcefields.py:268
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address country" msgid "Attendee address country"
msgstr "כתובת המשתתף" msgstr "מדינת המשתתף"
#: pretix/base/datasync/sourcefields.py:279 #: pretix/base/datasync/sourcefields.py:279
#: pretix/base/exporters/orderlist.py:687 pretix/base/pdf.py:346 #: pretix/base/exporters/orderlist.py:687 pretix/base/pdf.py:346
@@ -1000,16 +961,12 @@ msgid "Invoice address country"
msgstr "מדינת כתובת לחשבונית" msgstr "מדינת כתובת לחשבונית"
#: pretix/base/datasync/sourcefields.py:353 #: pretix/base/datasync/sourcefields.py:353
#, fuzzy
#| msgid "Order details"
msgid "Order email" msgid "Order email"
msgstr "פרטי הזמנה" msgstr "דוא\"ל הזמנה"
#: pretix/base/datasync/sourcefields.py:362 #: pretix/base/datasync/sourcefields.py:362
#, fuzzy
#| msgid "Organizer domain"
msgid "Order email domain" msgid "Order email domain"
msgstr "דומיין מארגן" msgstr "דומיין מייל הזמנה"
#: pretix/base/datasync/sourcefields.py:371 #: pretix/base/datasync/sourcefields.py:371
#: pretix/base/exporters/invoices.py:203 pretix/base/exporters/invoices.py:332 #: pretix/base/exporters/invoices.py:203 pretix/base/exporters/invoices.py:332
@@ -1041,10 +998,8 @@ msgid "Order code"
msgstr "קוד הזמנה" msgstr "קוד הזמנה"
#: pretix/base/datasync/sourcefields.py:380 #: pretix/base/datasync/sourcefields.py:380
#, fuzzy
#| msgid "End order date"
msgid "Event and order code" msgid "Event and order code"
msgstr "תאריך סיום הזמנה" msgstr "אירוע וקוד הזמנה"
#: pretix/base/datasync/sourcefields.py:389 #: pretix/base/datasync/sourcefields.py:389
#: pretix/base/exporters/orderlist.py:283 pretix/base/notifications.py:201 #: pretix/base/exporters/orderlist.py:283 pretix/base/notifications.py:201
@@ -1069,16 +1024,12 @@ msgid "Product ID"
msgstr "מזהה מוצר" msgstr "מזהה מוצר"
#: pretix/base/datasync/sourcefields.py:419 #: pretix/base/datasync/sourcefields.py:419
#, fuzzy
#| msgid "Non-admission product"
msgid "Product is admission product" msgid "Product is admission product"
msgstr "מוצר שאינו עבור כניסה" msgstr "מוצר שהינו כרטיס כניסה"
#: pretix/base/datasync/sourcefields.py:428 #: pretix/base/datasync/sourcefields.py:428
#, fuzzy
#| msgid "Event short name"
msgid "Event short form" msgid "Event short form"
msgstr "שם האירוע המקוצר" msgstr "טופס האירוע המקוצר"
#: pretix/base/datasync/sourcefields.py:437 pretix/base/exporters/events.py:57 #: pretix/base/datasync/sourcefields.py:437 pretix/base/exporters/events.py:57
#: pretix/base/exporters/orderlist.py:283 #: pretix/base/exporters/orderlist.py:283
@@ -1121,10 +1072,8 @@ msgid "Order code and position number"
msgstr "קוד הזמנה ומספר פריט" msgstr "קוד הזמנה ומספר פריט"
#: pretix/base/datasync/sourcefields.py:482 #: pretix/base/datasync/sourcefields.py:482
#, fuzzy
#| msgid "Ticket page"
msgid "Ticket price" msgid "Ticket price"
msgstr "דף כרטיס" msgstr "מחיר כרטיס"
#: pretix/base/datasync/sourcefields.py:491 pretix/base/notifications.py:204 #: pretix/base/datasync/sourcefields.py:491 pretix/base/notifications.py:204
#: pretix/control/forms/filter.py:220 pretix/control/forms/filter.py:1242 #: pretix/control/forms/filter.py:220 pretix/control/forms/filter.py:1242
@@ -1133,22 +1082,16 @@ msgid "Order status"
msgstr "סטטוס הזמנה" msgstr "סטטוס הזמנה"
#: pretix/base/datasync/sourcefields.py:500 #: pretix/base/datasync/sourcefields.py:500
#, fuzzy
#| msgid "Device status"
msgid "Ticket status" msgid "Ticket status"
msgstr "סטטוס מכשיר" msgstr "סטטוס כרטיס"
#: pretix/base/datasync/sourcefields.py:509 #: pretix/base/datasync/sourcefields.py:509
#, fuzzy
#| msgid "Purchase date and time"
msgid "Order date and time" msgid "Order date and time"
msgstr "תאריך ושעת הקנייה" msgstr "תאריך ושעת ההזמנה"
#: pretix/base/datasync/sourcefields.py:518 #: pretix/base/datasync/sourcefields.py:518
#, fuzzy
#| msgid "Printing date and time"
msgid "Payment date and time" msgid "Payment date and time"
msgstr "תאריך ושעת ההדפסה" msgstr "תאריך ושעת התשלום"
#: pretix/base/datasync/sourcefields.py:527 #: pretix/base/datasync/sourcefields.py:527
#: pretix/base/exporters/orderlist.py:292 #: pretix/base/exporters/orderlist.py:292
@@ -1159,35 +1102,27 @@ msgid "Order locale"
msgstr "שפת הזמנה" msgstr "שפת הזמנה"
#: pretix/base/datasync/sourcefields.py:536 #: pretix/base/datasync/sourcefields.py:536
#, fuzzy
#| msgid "Order position"
msgid "Order position ID" msgid "Order position ID"
msgstr "עמדת הזמנה" msgstr "מספר עמדת הזמנה"
#: pretix/base/datasync/sourcefields.py:545 #: pretix/base/datasync/sourcefields.py:545
#: pretix/base/exporters/orderlist.py:312 #: pretix/base/exporters/orderlist.py:312
#, fuzzy
#| msgid "Order time"
msgid "Order link" msgid "Order link"
msgstr "שעת הזמנה" msgstr "קישור הזמנה"
#: pretix/base/datasync/sourcefields.py:560 #: pretix/base/datasync/sourcefields.py:560
#, fuzzy
#| msgid "Ticket block"
msgid "Ticket link" msgid "Ticket link"
msgstr "חסימת כרטיס" msgstr "קישור כרטיס"
#: pretix/base/datasync/sourcefields.py:578 #: pretix/base/datasync/sourcefields.py:578
#, fuzzy, python-brace-format #, python-brace-format
#| msgid "Check-in list {val}"
msgid "Check-in datetime on list {}" msgid "Check-in datetime on list {}"
msgstr "רשימת הצ'ק-אין {val}" msgstr "צ'ק-אין בתאריך-זמן ברשימה {}"
#: pretix/base/datasync/sourcefields.py:590 #: pretix/base/datasync/sourcefields.py:590
#, fuzzy, python-brace-format #, python-brace-format
#| msgid "Question: %(name)s"
msgid "Question: {name}" msgid "Question: {name}"
msgstr "שאלה: %(name)s" msgstr "שאלה: {name}"
#: pretix/base/datasync/sourcefields.py:604 #: pretix/base/datasync/sourcefields.py:604
#: pretix/base/datasync/sourcefields.py:614 pretix/base/settings.py:3691 #: pretix/base/datasync/sourcefields.py:614 pretix/base/settings.py:3691
@@ -2328,6 +2263,8 @@ msgid ""
"contain at least one position of this product. The order totals etc. still " "contain at least one position of this product. The order totals etc. still "
"include all products contained in the order." "include all products contained in the order."
msgstr "" msgstr ""
"אם אף אחד מהם לא נבחר, כל המוצרים כלולים. הפקודות נכללות אם הן כוללות לפחות "
"עמדה אחת של המוצר. סך ההזמנות וכו'. עדיין כולל את כל המוצרים הכלולים בהזמנה."
#: pretix/base/exporters/orderlist.py:283 #: pretix/base/exporters/orderlist.py:283
#: pretix/base/exporters/orderlist.py:478 #: pretix/base/exporters/orderlist.py:478
@@ -2591,10 +2528,8 @@ msgid "Voucher"
msgstr "שובר" msgstr "שובר"
#: pretix/base/exporters/orderlist.py:653 #: pretix/base/exporters/orderlist.py:653
#, fuzzy
#| msgid "Voucher code used:"
msgid "Voucher budget usage" msgid "Voucher budget usage"
msgstr "השתמש בקוד שובר:" msgstr "שימוש בתקציב השוברים"
#: pretix/base/exporters/orderlist.py:654 #: pretix/base/exporters/orderlist.py:654
msgid "Pseudonymization ID" msgid "Pseudonymization ID"
@@ -2686,10 +2621,8 @@ msgid "Check-in lists"
msgstr "רשימות צ'ק-אין" msgstr "רשימות צ'ק-אין"
#: pretix/base/exporters/orderlist.py:706 #: pretix/base/exporters/orderlist.py:706
#, fuzzy
#| msgid "Resend order link"
msgid "Position order link" msgid "Position order link"
msgstr "שלח את קישור ההזמנה שוב" msgstr "עמדת קישור הזמנה"
#: pretix/base/exporters/orderlist.py:876 #: pretix/base/exporters/orderlist.py:876
msgid "Order transaction data" msgid "Order transaction data"
@@ -3326,10 +3259,8 @@ msgid "Repeat password"
msgstr "חזור על הסיסמה" msgstr "חזור על הסיסמה"
#: pretix/base/forms/auth.py:220 pretix/base/forms/user.py:99 #: pretix/base/forms/auth.py:220 pretix/base/forms/user.py:99
#, fuzzy
#| msgid "Email address"
msgid "Your email address" msgid "Your email address"
msgstr "כתובת דוא\"ל" msgstr "כתובת הדוא\"ל שלך"
#: pretix/base/forms/auth.py:327 pretix/control/forms/orders.py:1041 #: pretix/base/forms/auth.py:327 pretix/control/forms/orders.py:1041
#: pretix/control/templates/pretixcontrol/shredder/download.html:53 #: pretix/control/templates/pretixcontrol/shredder/download.html:53
@@ -3410,14 +3341,12 @@ msgstr ""
"למדינתך ומדינת המוכר." "למדינתך ומדינת המוכר."
#: pretix/base/forms/questions.py:1185 #: pretix/base/forms/questions.py:1185
#, fuzzy
#| msgid "Cancellation requested"
msgid "No invoice requested" msgid "No invoice requested"
msgstr "בקשת ביטול" msgstr "לא התבקשה חשבונית"
#: pretix/base/forms/questions.py:1187 #: pretix/base/forms/questions.py:1187
msgid "Invoice transmission method" msgid "Invoice transmission method"
msgstr "" msgstr "שיטת שידור חשבונית"
#: pretix/base/forms/questions.py:1333 #: pretix/base/forms/questions.py:1333
msgid "You need to provide a company name." msgid "You need to provide a company name."
@@ -3431,25 +3360,24 @@ msgstr "עליך לספק את שמך."
msgid "" msgid ""
"If you enter an invoice address, you also need to select an invoice " "If you enter an invoice address, you also need to select an invoice "
"transmission method." "transmission method."
msgstr "" msgstr "אם אתה מזין כתובת לחשבונית, עליך גם לבחור שיטת שידור חשבונית."
#: pretix/base/forms/questions.py:1403 #: pretix/base/forms/questions.py:1403
#, fuzzy
#| msgid "The selected media type is not enabled in your organizer settings."
msgid "" msgid ""
"The selected transmission type is not available in your country or for your " "The selected transmission type is not available in your country or for your "
"type of address." "type of address."
msgstr "סוג המדיה שנבחר אינו מופעל בהגדרות הארגון שלכם." msgstr "סוג השידור שנבחר אינו זמין במדינתך או בסוג הכתובת שבחרת."
#: pretix/base/forms/questions.py:1412 #: pretix/base/forms/questions.py:1412
msgid "" msgid ""
"The selected type of invoice transmission requires a field that is currently " "The selected type of invoice transmission requires a field that is currently "
"not available, please reach out to the organizer." "not available, please reach out to the organizer."
msgstr "" msgstr ""
"סוג שידור החשבונית שנבחר דורש שדה שאינו זמין כרגע, נא ליצור קשר עם המארגן."
#: pretix/base/forms/questions.py:1416 #: pretix/base/forms/questions.py:1416
msgid "This field is required for the selected type of invoice transmission." msgid "This field is required for the selected type of invoice transmission."
msgstr "" msgstr "שדה זה הינו חובה לסוג שידור החשבונית שנבחר."
#: pretix/base/forms/user.py:54 pretix/control/forms/organizer.py:458 #: pretix/base/forms/user.py:54 pretix/control/forms/organizer.py:458
#: pretix/control/forms/users.py:58 #: pretix/control/forms/users.py:58
@@ -3465,10 +3393,8 @@ msgstr ""
"באזור הזמן של האירוע עצמו." "באזור הזמן של האירוע עצמו."
#: pretix/base/forms/user.py:77 #: pretix/base/forms/user.py:77
#, fuzzy
#| msgid "Attendee email address"
msgid "Change email address" msgid "Change email address"
msgstr "כתובת דוא\"ל של המשתתף" msgstr "שינוי כתובת דוא\"ל"
#: pretix/base/forms/user.py:83 #: pretix/base/forms/user.py:83
msgid "Device name" msgid "Device name"
@@ -3516,16 +3442,12 @@ msgid ""
msgstr "כבר קיים חשבון הקשור לכתובת האימייל הזו. אנא בחר כתובת אחרת." msgstr "כבר קיים חשבון הקשור לכתובת האימייל הזו. אנא בחר כתובת אחרת."
#: pretix/base/forms/user.py:179 #: pretix/base/forms/user.py:179
#, fuzzy
#| msgid "Email address"
msgid "Old email address" msgid "Old email address"
msgstr "כתובת דוא\"ל" msgstr "כתובת דוא\"ל ישנה"
#: pretix/base/forms/user.py:180 #: pretix/base/forms/user.py:180
#, fuzzy
#| msgid "Email address"
msgid "New email address" msgid "New email address"
msgstr "כתובת דוא\"ל" msgstr "כתובת דוא\"ל חדשה"
#: pretix/base/forms/validators.py:51 #: pretix/base/forms/validators.py:51
msgid "" msgid ""
@@ -3569,60 +3491,47 @@ msgid "Individual customer"
msgstr "לקוח פרטי" msgstr "לקוח פרטי"
#: pretix/base/invoicing/email.py:50 #: pretix/base/invoicing/email.py:50
#, fuzzy
#| msgid ""
#| "Please additionally send my invoice directly to our accounting department"
msgid "Email invoice directly to accounting department" msgid "Email invoice directly to accounting department"
msgstr "אנא שלח בנוסף את החשבונית ישירות למחלקת הנהלת החשבונות שלנו" msgstr "שלח חשבונית בדוא\"ל ישירות למחלקת הנהלת חשבונות."
#: pretix/base/invoicing/email.py:51 #: pretix/base/invoicing/email.py:51
#, fuzzy
#| msgid "The invoice was sent to the designated email address."
msgid "" msgid ""
"If not selected, the invoice will be sent to you using the email address " "If not selected, the invoice will be sent to you using the email address "
"listed above." "listed above."
msgstr "החשבונית נשלחה לכתובת הדוא\"ל שהוגדרה." msgstr "אם לא ייבחר, בחשבונית תישלח לכתובת הדוא\"ל המצויינת מעלה."
#: pretix/base/invoicing/email.py:55 #: pretix/base/invoicing/email.py:55
#, fuzzy
#| msgid "Email address verified"
msgid "Email address for invoice" msgid "Email address for invoice"
msgstr "כתובת דוא\"ל מאומתת" msgstr "כתובת דוא\"ל לחשבונית"
#: pretix/base/invoicing/email.py:91 #: pretix/base/invoicing/email.py:91
#, fuzzy
#| msgid "Preview email"
msgid "PDF via email" msgid "PDF via email"
msgstr "הצג תצוגה מקדימה של המייל" msgstr "PDF באמצעות דוא\"ל"
#: pretix/base/invoicing/national.py:37 #: pretix/base/invoicing/national.py:37
msgctxt "italian_invoice" msgctxt "italian_invoice"
msgid "Italian Exchange System (SdI)" msgid "Italian Exchange System (SdI)"
msgstr "" msgstr "מערכת ההחלפה האיטלקית (SdI)"
#: pretix/base/invoicing/national.py:38 #: pretix/base/invoicing/national.py:38
msgctxt "italian_invoice" msgctxt "italian_invoice"
msgid "Exchange System (SdI)" msgid "Exchange System (SdI)"
msgstr "" msgstr "מערכת ההחלפה (SdI)"
#: pretix/base/invoicing/national.py:51 #: pretix/base/invoicing/national.py:51
#, fuzzy
#| msgid "Gift card code"
msgctxt "italian_invoice" msgctxt "italian_invoice"
msgid "Fiscal code" msgid "Fiscal code"
msgstr "קוד כרטיס מתנה" msgstr "קוד פיסקלי"
#: pretix/base/invoicing/national.py:55 #: pretix/base/invoicing/national.py:55
msgctxt "italian_invoice" msgctxt "italian_invoice"
msgid "Address for certified electronic mail" msgid "Address for certified electronic mail"
msgstr "" msgstr "כתובת לדואר אלקטרוני מאושר."
#: pretix/base/invoicing/national.py:59 #: pretix/base/invoicing/national.py:59
#, fuzzy
#| msgid "Recipient"
msgctxt "italian_invoice" msgctxt "italian_invoice"
msgid "Recipient code" msgid "Recipient code"
msgstr "נמען" msgstr "קוד נמען"
#: pretix/base/invoicing/national.py:83 #: pretix/base/invoicing/national.py:83
msgctxt "italian_invoice" msgctxt "italian_invoice"
@@ -3632,6 +3541,9 @@ msgid ""
"in accordance with the procedures and terms set forth in No. 89757/2018 of " "in accordance with the procedures and terms set forth in No. 89757/2018 of "
"April 30, 2018, issued by the Director of the Revenue Agency." "April 30, 2018, issued by the Director of the Revenue Agency."
msgstr "" msgstr ""
"קובץ PDF זה הוא העתק ויזואלי של החשבונית ואינו מהווה חשבונית לצרכי מע\"מ. "
"החשבונית מונפקת בפורמט XML, ומשודרת בהתאם לנהלים ולתנאים המפורטים בתקנה "
"89757/2018 מה30 באפריל, 2016 שפורסמה על ידי מנהל סוכנות ההכנסות."
#: pretix/base/invoicing/pdf.py:142 #: pretix/base/invoicing/pdf.py:142
#, python-format #, python-format
@@ -3812,12 +3724,10 @@ msgid "Remaining amount"
msgstr "הסכום שנותר" msgstr "הסכום שנותר"
#: pretix/base/invoicing/pdf.py:1008 #: pretix/base/invoicing/pdf.py:1008
#, fuzzy, python-brace-format #, python-brace-format
#| msgctxt "invoice"
#| msgid "Event date: {date_range}"
msgctxt "invoice" msgctxt "invoice"
msgid "Invoice period: {daterange}" msgid "Invoice period: {daterange}"
msgstr אריך אירוע: {date_range}" msgstr קופת החשבונית: {daterange}"
#: pretix/base/invoicing/pdf.py:1039 #: pretix/base/invoicing/pdf.py:1039
msgctxt "invoice" msgctxt "invoice"
@@ -3880,7 +3790,7 @@ msgstr "תאריך אירוע: {date_range}"
#: pretix/base/invoicing/peppol.py:136 #: pretix/base/invoicing/peppol.py:136
msgid "" msgid ""
"A Peppol participant ID always starts with a prefix, followed by a colon (:)." "A Peppol participant ID always starts with a prefix, followed by a colon (:)."
msgstr "" msgstr "מזהה משתתף של פפול תמיד מתחיל בתחילית ואחריה נקודתיים(:)."
#: pretix/base/invoicing/peppol.py:140 #: pretix/base/invoicing/peppol.py:140
#, python-format #, python-format
@@ -3888,6 +3798,8 @@ msgid ""
"The Peppol participant ID prefix %(number)s is not known to our system. " "The Peppol participant ID prefix %(number)s is not known to our system. "
"Please reach out to us if you are sure this ID is correct." "Please reach out to us if you are sure this ID is correct."
msgstr "" msgstr ""
"תחילית מזהה המשתתף של פפול %(number)s אינה מוכרת למערכת. צור עימנו קשר אם "
"אתה בטוח שזהו המזהה הנכון."
#: pretix/base/invoicing/peppol.py:144 #: pretix/base/invoicing/peppol.py:144
#, python-format #, python-format
@@ -3895,21 +3807,21 @@ msgid ""
"The Peppol participant ID does not match the validation rules for the prefix " "The Peppol participant ID does not match the validation rules for the prefix "
"%(number)s. Please reach out to us if you are sure this ID is correct." "%(number)s. Please reach out to us if you are sure this ID is correct."
msgstr "" msgstr ""
"מזהה המשתתף של פפול אינו תואם לחוקי הולידציה של התחילית %(number)s. צור "
"עימנו קשר אם אתה בטוח שהמזהה נכון."
#: pretix/base/invoicing/peppol.py:166 #: pretix/base/invoicing/peppol.py:166
msgid "The Peppol participant ID is not registered on the Peppol network." msgid "The Peppol participant ID is not registered on the Peppol network."
msgstr "" msgstr "מזהה המשתתף של פפול אינו רשום ברשת פפול."
#: pretix/base/invoicing/peppol.py:192 #: pretix/base/invoicing/peppol.py:192
msgid "Peppol participant ID" msgid "Peppol participant ID"
msgstr "" msgstr "מזהה משתתף פפול"
#: pretix/base/invoicing/peppol.py:208 #: pretix/base/invoicing/peppol.py:208
#, fuzzy
#| msgid "Gift card code"
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
msgid "Visual copy" msgid "Visual copy"
msgstr "קוד כרטיס מתנה" msgstr "עותק ויזואלי"
#: pretix/base/invoicing/peppol.py:213 #: pretix/base/invoicing/peppol.py:213
msgctxt "peppol_invoice" msgctxt "peppol_invoice"
@@ -3918,6 +3830,8 @@ msgid ""
"invoice for VAT purposes. The original invoice is issued in XML format and " "invoice for VAT purposes. The original invoice is issued in XML format and "
"transmitted through the Peppol network." "transmitted through the Peppol network."
msgstr "" msgstr ""
"קובץ PDF זה הינו העתק ויזואלי של החשבונית ואינו מהווה חשבונית לצרכי מע\"מ. "
"החשבונית המקורית מופקת בפורמט XML ומשודרת דרך רשת פפול."
#: pretix/base/logentrytype_registry.py:43 #: pretix/base/logentrytype_registry.py:43
msgid "" msgid ""
@@ -4030,7 +3944,7 @@ msgstr "נמצאו מספר תאריכים תואמים."
#: pretix/base/modelimport_orders.py:73 #: pretix/base/modelimport_orders.py:73
msgid "Grouping" msgid "Grouping"
msgstr "" msgstr "קיבוץ"
#: pretix/base/modelimport_orders.py:75 #: pretix/base/modelimport_orders.py:75
msgid "" msgid ""
@@ -4038,6 +3952,8 @@ msgid ""
"together...\". Lines with the same grouping value will be put in the same " "together...\". Lines with the same grouping value will be put in the same "
"order, but MUST be consecutive lines of the input file." "order, but MUST be consecutive lines of the input file."
msgstr "" msgstr ""
"ישים רק כש\"מצב יבוא\" מוגדר ל\"קיבוץ מספר שורות יחד...\". שורות עם אותו ערך "
"קיבוץ יוצבו באותו סדר, אך חייבים להיות שורות רציפות של קובץ הקלט."
#: pretix/base/modelimport_orders.py:101 #: pretix/base/modelimport_orders.py:101
msgid "Enter a valid phone number." msgid "Enter a valid phone number."
@@ -4049,6 +3965,8 @@ msgid ""
"The date can be specified through its full name, full date and time, or " "The date can be specified through its full name, full date and time, or "
"internal ID, provided only one date in the system matches the input." "internal ID, provided only one date in the system matches the input."
msgstr "" msgstr ""
"התאריך ניתן לציון דרך השם המלא, התאריך והשעה המלאים, או מזהה פנימי בתנאי "
"שהמזהה תואם רק תאריך אחד."
#: pretix/base/modelimport_orders.py:120 pretix/presale/views/waiting.py:157 #: pretix/base/modelimport_orders.py:120 pretix/presale/views/waiting.py:157
msgctxt "subevent" msgctxt "subevent"
@@ -4058,7 +3976,7 @@ msgstr "עליך לבחור תאריך."
#: pretix/base/modelimport_orders.py:131 #: pretix/base/modelimport_orders.py:131
msgid "" msgid ""
"The product can be specified by its internal ID, full name or internal name." "The product can be specified by its internal ID, full name or internal name."
msgstr "" msgstr "המוצר ניתן לציון ע\"י מזהה פנימי, שם מלא או שם פנימי."
#: pretix/base/modelimport_orders.py:149 #: pretix/base/modelimport_orders.py:149
#: pretix/base/modelimport_vouchers.py:194 #: pretix/base/modelimport_vouchers.py:194
@@ -4078,7 +3996,7 @@ msgstr "וריאציית מוצר"
#: pretix/base/modelimport_orders.py:161 #: pretix/base/modelimport_orders.py:161
msgid "The variation can be specified by its internal ID or full name." msgid "The variation can be specified by its internal ID or full name."
msgstr "" msgstr "הוריאציה ניתנת לציון לפי מזהה פנימי או שם מלא."
#: pretix/base/modelimport_orders.py:181 #: pretix/base/modelimport_orders.py:181
#: pretix/base/modelimport_vouchers.py:225 #: pretix/base/modelimport_vouchers.py:225
@@ -4097,10 +4015,8 @@ msgid "You need to select a variation for this product."
msgstr "עליך לבחור וריאציה למוצר זה." msgstr "עליך לבחור וריאציה למוצר זה."
#: pretix/base/modelimport_orders.py:265 pretix/base/modelimport_orders.py:417 #: pretix/base/modelimport_orders.py:265 pretix/base/modelimport_orders.py:417
#, fuzzy
#| msgid "The count needs to be equal to or greater than zero."
msgid "The country needs to be specified using a two-letter country code." msgid "The country needs to be specified using a two-letter country code."
msgstr כמות צריכה להיות שווה או גדולה מאפס." msgstr מדינה נדרשת להזנה לפי קוד מדינה בין 2 אותיות."
#: pretix/base/modelimport_orders.py:281 pretix/base/modelimport_orders.py:432 #: pretix/base/modelimport_orders.py:281 pretix/base/modelimport_orders.py:432
msgid "Please enter a valid country code." msgid "Please enter a valid country code."
@@ -4108,7 +4024,7 @@ msgstr "נא להזין קוד ארץ תקין."
#: pretix/base/modelimport_orders.py:290 pretix/base/modelimport_orders.py:441 #: pretix/base/modelimport_orders.py:290 pretix/base/modelimport_orders.py:441
msgid "The state can be specified by its short form or full name." msgid "The state can be specified by its short form or full name."
msgstr "" msgstr "המדינה ניתנת להזנה ע\"י שימוש בקיצור או בשמה המלא."
#: pretix/base/modelimport_orders.py:300 pretix/base/modelimport_orders.py:450 #: pretix/base/modelimport_orders.py:300 pretix/base/modelimport_orders.py:450
msgid "States are not supported for this country." msgid "States are not supported for this country."
@@ -4164,17 +4080,15 @@ msgstr "אנא הזן קוד שפה תקין."
msgid "" msgid ""
"The sales channel can be specified by it's internal identifier or its full " "The sales channel can be specified by it's internal identifier or its full "
"name." "name."
msgstr "" msgstr "ערוץ המכירה ניתן להזנה ע\"י מזהה פנימי או שם מלא."
#: pretix/base/modelimport_orders.py:599 pretix/base/modelimport_orders.py:601 #: pretix/base/modelimport_orders.py:599 pretix/base/modelimport_orders.py:601
msgid "Please enter a valid sales channel." msgid "Please enter a valid sales channel."
msgstr "אנא הזן ערוץ מכירות תקין." msgstr "אנא הזן ערוץ מכירות תקין."
#: pretix/base/modelimport_orders.py:611 #: pretix/base/modelimport_orders.py:611
#, fuzzy
#| msgid "The refund amount needs to be positive and less than {}."
msgid "The seat needs to be specified by its internal ID." msgid "The seat needs to be specified by its internal ID."
msgstr "סכום ההחזר חייב להיות חיובי ופחות מ-{}." msgstr "המושב חייב להיות מצויין לפי המזהה הפנימי."
#: pretix/base/modelimport_orders.py:626 #: pretix/base/modelimport_orders.py:626
#: pretix/base/modelimport_vouchers.py:291 #: pretix/base/modelimport_vouchers.py:291
@@ -4376,17 +4290,17 @@ msgid ""
"to confirm changing your email address from {old_email}\n" "to confirm changing your email address from {old_email}\n"
"to {new_email}, use the following code:" "to {new_email}, use the following code:"
msgstr "" msgstr ""
"לאימות שינוי כתובת הדוא\"ל מ{old_email} \n"
"ל {new_email}, השתמש בקוד הבא:"
#: pretix/base/models/auth.py:377 #: pretix/base/models/auth.py:377
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"to confirm that your email address {email} belongs to your pretix account, " "to confirm that your email address {email} belongs to your pretix account, "
"use the following code:" "use the following code:"
msgstr "" msgstr "לאימות שכתובת הדוא\"ל {email} שייכת לחשבונך, השתמש בקוד הבא:"
#: pretix/base/models/auth.py:391 #: pretix/base/models/auth.py:391
#, fuzzy
#| msgid "Confirmation code"
msgid "pretix confirmation code" msgid "pretix confirmation code"
msgstr "קוד אישור" msgstr "קוד אישור"
@@ -4646,21 +4560,19 @@ msgstr "הפרד בין ערכים מרובים באמצעות רווחים"
#: pretix/base/models/datasync.py:53 #: pretix/base/models/datasync.py:53
msgid "Temporary error, auto-retry limit exceeded" msgid "Temporary error, auto-retry limit exceeded"
msgstr "" msgstr "שגיאה זמנית, מכסת הניסיונות מחדש האוטומטיים נחרגה"
#: pretix/base/models/datasync.py:54 #: pretix/base/models/datasync.py:54
#, fuzzy
#| msgid "Stripe reported an error: %s"
msgid "Provider reported a permanent error" msgid "Provider reported a permanent error"
msgstr "Stripe דיווח על שגיאה: %s" msgstr "הספק דיווח על שגיאה קבועה"
#: pretix/base/models/datasync.py:55 #: pretix/base/models/datasync.py:55
msgid "Misconfiguration, please check provider settings" msgid "Misconfiguration, please check provider settings"
msgstr "" msgstr "שגיאת הגדרה, בדוק את הגדרות הספק"
#: pretix/base/models/datasync.py:56 pretix/base/models/datasync.py:57 #: pretix/base/models/datasync.py:56 pretix/base/models/datasync.py:57
msgid "System error, needs manual intervention" msgid "System error, needs manual intervention"
msgstr "" msgstr "תקלת מערכת, נדרשת התערבות ידנית"
#: pretix/base/models/devices.py:70 pretix/base/models/items.py:1675 #: pretix/base/models/devices.py:70 pretix/base/models/items.py:1675
msgid "Internal identifier" msgid "Internal identifier"
@@ -4908,13 +4820,15 @@ msgstr "לא חובה. לא יימכרו מוצרים לפני תאריך זה."
#: pretix/base/models/event.py:644 #: pretix/base/models/event.py:644
msgid "This event is remote or partially remote." msgid "This event is remote or partially remote."
msgstr "" msgstr "אירוע זה הינו אירוע וירטואלי או חלקית וירטואלי."
#: pretix/base/models/event.py:645 #: pretix/base/models/event.py:645
msgid "" msgid ""
"This will be used to let users know if the event is in a different timezone " "This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times." "and lets us calculate users local times."
msgstr "" msgstr ""
"זה ישמש לעדכן את המשתמשים לדעת אם השעה המצויינת הינה באזור זמן שונה ויאפשר "
"לנו לחשב את השעה בהתאם לשעון המקומי של המשתמשים."
#: pretix/base/models/event.py:665 pretix/base/models/organizer.py:97 #: pretix/base/models/event.py:665 pretix/base/models/organizer.py:97
#: pretix/control/navigation.py:65 pretix/control/navigation.py:499 #: pretix/control/navigation.py:65 pretix/control/navigation.py:499
@@ -5191,7 +5105,7 @@ msgstr "יתרה ממתינה"
#: pretix/base/models/invoices.py:123 #: pretix/base/models/invoices.py:123
msgid "currently being transmitted" msgid "currently being transmitted"
msgstr "" msgstr "משודר כרגע"
#: pretix/base/models/invoices.py:124 #: pretix/base/models/invoices.py:124
#, fuzzy #, fuzzy
@@ -6086,6 +6000,8 @@ msgid ""
"with changing the type of question without data loss. Consider hiding this " "with changing the type of question without data loss. Consider hiding this "
"question and creating a new one instead." "question and creating a new one instead."
msgstr "" msgstr ""
"המערכת כבר כוללת תשובות לשאלה זו שאינם מאפשרות שינוי סוג השאלה מבלי לאבד "
"מידע. מומלץ לשקול להסתיר את שאלה זו וליצור חדשה במקום."
#: pretix/base/models/items.py:1961 #: pretix/base/models/items.py:1961
#: pretix/control/templates/pretixcontrol/items/question.html:75 #: pretix/control/templates/pretixcontrol/items/question.html:75
@@ -8026,6 +7942,9 @@ msgid ""
"2017-05-31 14:00 16:00\n" "2017-05-31 14:00 16:00\n"
"2017-05-31 14:00 2017-06-01 14:00" "2017-05-31 14:00 2017-06-01 14:00"
msgstr "" msgstr ""
"2017-05-31 10:00 12:00\n"
"2017-05-31 14:00 16:00\n"
"2017-05-31 14:00 2017-06-01 14:00"
#: pretix/base/pdf.py:500 #: pretix/base/pdf.py:500
msgid "Reusable Medium ID" msgid "Reusable Medium ID"
@@ -8234,7 +8153,7 @@ msgstr "בחרת מוצר שאינו זמין למכירה."
msgid "" msgid ""
"Some products can no longer be purchased and have been removed from your " "Some products can no longer be purchased and have been removed from your "
"cart for the following reason: %s" "cart for the following reason: %s"
msgstr "" msgstr "חלק מהמוצרים לא ניתנים לרכישה יותר והוסרו מעגלת הקניות מהסיבה הבאה: %s"
#: pretix/base/services/cart.py:117 #: pretix/base/services/cart.py:117
msgid "" msgid ""
@@ -8984,7 +8903,7 @@ msgstr "ההזמנה אינה יכולה להכיל יותר מ־%(max)s עמד
msgid "" msgid ""
"The grouping \"%(value)s\" occurs on non-consecutive lines (seen again on " "The grouping \"%(value)s\" occurs on non-consecutive lines (seen again on "
"line %(row)s)." "line %(row)s)."
msgstr "" msgstr "הקיבוץ \"%(value)s\" מתקיים בשורות לא רציפות (נראה שוב בשורה %(row)s)."
#: pretix/base/services/modelimport.py:154 #: pretix/base/services/modelimport.py:154
#, python-brace-format #, python-brace-format
@@ -8992,6 +8911,8 @@ msgid ""
"Inconsistent data in row {row}: Column {col} contains value \"{val_line}\", " "Inconsistent data in row {row}: Column {col} contains value \"{val_line}\", "
"but for this order, the value has already been set to \"{val_order}\"." "but for this order, the value has already been set to \"{val_order}\"."
msgstr "" msgstr ""
"מידע לא רציף בשורה {row}: טור {col} מכיל ערך \"{val_line}\", אך להזמנה זו, "
"הערך כבר הוגדר ל \"{val_order}\"."
#: pretix/base/services/modelimport.py:168 #: pretix/base/services/modelimport.py:168
#: pretix/base/services/modelimport.py:289 #: pretix/base/services/modelimport.py:289
@@ -9433,15 +9354,15 @@ msgstr "השובר נשלח ל-{recipient}."
#: pretix/base/settings.py:82 #: pretix/base/settings.py:82
msgid "Compute taxes for every line individually" msgid "Compute taxes for every line individually"
msgstr "" msgstr "חשב מיסים לכל שורה בנפרד"
#: pretix/base/settings.py:83 #: pretix/base/settings.py:83
msgid "Compute taxes based on net total" msgid "Compute taxes based on net total"
msgstr "" msgstr "חשב מיסים תוך ביסוס על סה\"כ נטו"
#: pretix/base/settings.py:84 #: pretix/base/settings.py:84
msgid "Compute taxes based on net total with stable gross prices" msgid "Compute taxes based on net total with stable gross prices"
msgstr "" msgstr "חשב מיסים על סה\"כ נטו עם ברוטו יציב"
#: pretix/base/settings.py:134 #: pretix/base/settings.py:134
msgid "Allow usage of restricted plugins" msgid "Allow usage of restricted plugins"
@@ -9470,7 +9391,7 @@ msgstr "אם אפשרות זו מושבתת, תצטרך לחבר ספקי זיה
#: pretix/base/settings.py:189 #: pretix/base/settings.py:189
msgid "Require login to access order confirmation pages" msgid "Require login to access order confirmation pages"
msgstr "" msgstr "דרוש כניסה למשתמש על מנת לגשת לדפי אישור הזמנה"
#: pretix/base/settings.py:190 #: pretix/base/settings.py:190
msgid "" msgid ""
@@ -9479,6 +9400,9 @@ msgid ""
"placing an order, the restriction only becomes active after the customer " "placing an order, the restriction only becomes active after the customer "
"account is activated." "account is activated."
msgstr "" msgstr ""
"אם מופעל, משתמשים שהיו מחוברים לאתר בזמן רכישה יצטרכו להתחבר לחשבונם באתר על "
"מנת לגשת לפרטי ההזמנה שלהם. אם החשבון נוצר במהלך הרכישה, ההגבלה נכנסת לתוקף "
"רק לאחר הפעלת החשבון."
#: pretix/base/settings.py:202 #: pretix/base/settings.py:202
msgid "Match orders based on email address" msgid "Match orders based on email address"
@@ -9658,6 +9582,8 @@ msgid ""
"for tax reporting, you need to make sure to account for possible rounding " "for tax reporting, you need to make sure to account for possible rounding "
"differences if your external system rounds differently than pretix." "differences if your external system rounds differently than pretix."
msgstr "" msgstr ""
"שים לב- במידה ותייצא את נתוני המכירות שלך למערכת חיצונית לדיווח מס, עלייך "
"לקחת בחשבון כי ייתכנו פערים אם המערכת החיצונית מבצעת עיגול בצורה שונה."
#: pretix/base/settings.py:514 #: pretix/base/settings.py:514
msgid "Ask for invoice address" msgid "Ask for invoice address"
@@ -9786,6 +9712,8 @@ msgid ""
"ID in all countries. VAT ID will be required for all business addresses in " "ID in all countries. VAT ID will be required for all business addresses in "
"the selected countries." "the selected countries."
msgstr "" msgstr ""
"מספר עסק מגדר כרשות כברירת מחדל מאחר ובחלק מהמדינות ישנם עסקים שלא מוקצה להם "
"מספר עסק. מספר עסק יוגדר כחובה בכל המדינות הנבחרות."
#: pretix/base/settings.py:685 #: pretix/base/settings.py:685
msgid "Invoice address explanation" msgid "Invoice address explanation"
@@ -10172,10 +10100,12 @@ msgid ""
"Automatic based on ticket-specific validity, membership validity, event " "Automatic based on ticket-specific validity, membership validity, event "
"series date, or event date" "series date, or event date"
msgstr "" msgstr ""
"אוטומטי בהתאם לזמינות הכרטיס הספציפי, זמינות המנוי, תאריכי סדרת האירועים או "
"תאריך האירוע"
#: pretix/base/settings.py:1179 pretix/base/settings.py:1190 #: pretix/base/settings.py:1179 pretix/base/settings.py:1190
msgid "Automatic, but prefer invoice date over event date" msgid "Automatic, but prefer invoice date over event date"
msgstr "" msgstr "אוטומטי, אבל העדף תאריך חשבונית על תאריך אירוע"
#: pretix/base/settings.py:1182 pretix/base/settings.py:1193 #: pretix/base/settings.py:1182 pretix/base/settings.py:1193
#, fuzzy #, fuzzy
@@ -10196,6 +10126,7 @@ msgid ""
"This controls what dates are shown on the invoice, but is especially " "This controls what dates are shown on the invoice, but is especially "
"important for electronic invoicing." "important for electronic invoicing."
msgstr "" msgstr ""
"זה שולט על איזה תאריכים מוצגים על החשבונית, ובמיוחד חשוב לחשבוניות דיגיטליות."
#: pretix/base/settings.py:1206 #: pretix/base/settings.py:1206
msgid "Automatically cancel and reissue invoice on address changes" msgid "Automatically cancel and reissue invoice on address changes"
@@ -12767,7 +12698,7 @@ msgstr "אם זה לוקח יותר מכמה דקות, רענן את הדף או
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt:2 #: pretix/base/templates/pretixbase/email/cancel_confirm.txt:2
msgid "" msgid ""
"You have requested us to cancel an event which includes a larger bulk-refund:" "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 #: pretix/base/templates/pretixbase/email/cancel_confirm.txt:6
#, fuzzy #, fuzzy
@@ -12780,6 +12711,7 @@ msgid ""
"Please confirm that you want to proceed by coping the following confirmation " "Please confirm that you want to proceed by coping the following confirmation "
"code into the cancellation form:" "code into the cancellation form:"
msgstr "" msgstr ""
"נא לאשר שברצונך להמשיך באמצעות העתקה של קוד האימות הבא לתוך טופס הביטול:"
#: pretix/base/templates/pretixbase/email/email_footer.html:3 #: pretix/base/templates/pretixbase/email/email_footer.html:3
#, python-format #, python-format
@@ -13457,7 +13389,7 @@ msgstr "כל השערים"
#: pretix/control/forms/checkin.py:222 #: pretix/control/forms/checkin.py:222
msgid "I am sure that the check-in state of the entire event should be reset." msgid "I am sure that the check-in state of the entire event should be reset."
msgstr "" msgstr "אני בטוח שמצב הצ'ק אין של האירוע צריך לעבור איפוס."
#: pretix/control/forms/event.py:91 #: pretix/control/forms/event.py:91
msgid "Use languages" msgid "Use languages"
@@ -13556,7 +13488,7 @@ msgstr "ברירת מחדל ({value})"
#: pretix/control/forms/event.py:381 #: pretix/control/forms/event.py:381
msgid "The currency cannot be changed because orders already exist." msgid "The currency cannot be changed because orders already exist."
msgstr "" msgstr "לא ניתן לבצע שינוי בסוג המטבע מאחר והזמנות כבר קיימות."
#: pretix/control/forms/event.py:392 pretix/control/forms/event.py:405 #: pretix/control/forms/event.py:392 pretix/control/forms/event.py:405
msgid "Domain" msgid "Domain"
@@ -13644,7 +13576,7 @@ msgstr "כולל כל המיסים"
#: pretix/control/forms/event.py:815 #: pretix/control/forms/event.py:815
msgid "Recommended if you sell tickets at least partly to consumers." msgid "Recommended if you sell tickets at least partly to consumers."
msgstr "" msgstr "מומלץ אם תמכור כרטיסים לפחות חלקית ללקוחות."
#: pretix/control/forms/event.py:819 #: pretix/control/forms/event.py:819
#, fuzzy #, fuzzy
@@ -13655,7 +13587,7 @@ msgstr "כל העתיד (לא כולל היום)"
#: pretix/control/forms/event.py:820 #: pretix/control/forms/event.py:820
msgid "Recommended only if you sell tickets primarily to business customers." msgid "Recommended only if you sell tickets primarily to business customers."
msgstr "" msgstr "מומלץ רק אם תמכור כרטיסים בעיקר ללקוחות עסקיים."
#: pretix/control/forms/event.py:856 #: pretix/control/forms/event.py:856
#, fuzzy #, fuzzy
@@ -13885,6 +13817,7 @@ msgid ""
"This will only be used if the invoice is sent to a different email address " "This will only be used if the invoice is sent to a different email address "
"or at a different time than the order confirmation." "or at a different time than the order confirmation."
msgstr "" msgstr ""
"זה ישומש רק אם החשבונית תישלח לכתובת מייל אחרת או בזמן אחר מאישור ההזמנה."
#: pretix/control/forms/event.py:1321 #: pretix/control/forms/event.py:1321
msgid "" msgid ""
@@ -14335,7 +14268,7 @@ msgstr "שולם"
#: pretix/control/forms/filter.py:1304 #: pretix/control/forms/filter.py:1304
msgctxt "subevent" msgctxt "subevent"
msgid "Date doesn't start in selected date range." msgid "Date doesn't start in selected date range."
msgstr "" msgstr "תאריך לא מתחיל בטווח התאריכים שנבחר."
#: pretix/control/forms/filter.py:1360 pretix/control/forms/filter.py:1827 #: pretix/control/forms/filter.py:1360 pretix/control/forms/filter.py:1827
msgid "Shop live and presale running" msgid "Shop live and presale running"
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n" "POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2025-12-19 00:00+0000\n" "PO-Revision-Date: 2026-02-10 16:49+0000\n"
"Last-Translator: Daniel Branda <daniel.branda.ad@gmail.com>\n" "Last-Translator: Michele Pagnozzi <michele.pagnozzi@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/" "Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
"it/>\n" "it/>\n"
"Language: it\n" "Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15\n" "X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -966,7 +966,7 @@ msgstr "Indirizzo di fatturazione dell'azienda"
#: pretix/base/exporters/orderlist.py:688 #: pretix/base/exporters/orderlist.py:688
#: pretix/base/exporters/orderlist.py:692 pretix/base/pdf.py:341 #: pretix/base/exporters/orderlist.py:692 pretix/base/pdf.py:341
msgid "Invoice address name" msgid "Invoice address name"
msgstr "Nome dell'indirizzo di fatturazione" msgstr "Indirizzo di fatturazione"
#: pretix/base/datasync/sourcefields.py:317 #: pretix/base/datasync/sourcefields.py:317
#: pretix/base/exporters/orderlist.py:694 pretix/base/pdf.py:351 #: pretix/base/exporters/orderlist.py:694 pretix/base/pdf.py:351
+6 -3
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2025-05-05 09:40+0000\n" "PO-Revision-Date: 2026-02-10 16:49+0000\n"
"Last-Translator: \"Luca Martinelli [Sannita]\" <sannita@gmail.com>\n" "Last-Translator: Raffaele Doretto <ced@comune.portogruaro.ve.it>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
"js/it/>\n" "js/it/>\n"
"Language: it\n" "Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.11.1\n" "X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -1134,6 +1134,9 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available " "add yourself to the waiting list. We will then notify if seats are available "
"again." "again."
msgstr "" msgstr ""
"Alcune o tutte le categorie di biglietti sono attualmente esaurite. Se lo "
"desideri, puoi aggiungerti alla lista d'attesa. Ti informeremo se i posti "
"saranno nuovamente disponibili."
#: pretix/static/pretixpresale/js/widget/widget.js:76 #: pretix/static/pretixpresale/js/widget/widget.js:76
msgctxt "widget" msgctxt "widget"
File diff suppressed because it is too large Load Diff
+53 -56
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-01-28 18:00+0000\n" "PO-Revision-Date: 2026-02-12 20:00+0000\n"
"Last-Translator: Ryo Tagami <rtagami@airstrip.jp>\n" "Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
"js/ja/>\n" "js/ja/>\n"
"Language: ja\n" "Language: ja\n"
@@ -27,7 +27,7 @@ msgstr "支払い済み"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
msgid "Comment:" msgid "Comment:"
msgstr "注釈:" msgstr "コメント:"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34 #: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
msgid "PayPal" msgid "PayPal"
@@ -157,7 +157,7 @@ msgstr "受注件数"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:63 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:63
msgid "Paid orders" msgid "Paid orders"
msgstr "支払い済みの注文" msgstr "支払い済み件数"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
@@ -167,7 +167,7 @@ msgstr "参加者 (注文済み)"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (paid)" msgid "Attendees (paid)"
msgstr "参加者 (有料)" msgstr "参加者 (支払い済み)"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
msgid "Total revenue" msgid "Total revenue"
@@ -215,7 +215,7 @@ msgstr "このチケットは特別な対応が必要です"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
msgid "Switch direction" msgid "Switch direction"
msgstr "方向転換" msgstr "向きを切り替え"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
msgid "Entry" msgid "Entry"
@@ -223,7 +223,7 @@ msgstr "入場"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
msgid "Exit" msgid "Exit"
msgstr "退" msgstr "退"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
msgid "Scan a ticket or search and press return…" msgid "Scan a ticket or search and press return…"
@@ -297,7 +297,7 @@ msgstr "不明なチケット"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
msgid "Ticket type not allowed here" msgid "Ticket type not allowed here"
msgstr "この種類のチケットは使用できません" msgstr "この種類のチケットはここでは使用できません"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
msgid "Entry not allowed" msgid "Entry not allowed"
@@ -321,7 +321,7 @@ msgstr "注文がキャンセルされました"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list" msgid "Ticket code is ambiguous on list"
msgstr "リストチケットコードは曖昧です" msgstr "リスト上でチケットコードが一意に特定できません"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
msgid "Order not approved" msgid "Order not approved"
@@ -358,7 +358,7 @@ msgstr "閉じる"
#: pretix/static/pretixbase/js/addressform.js:101 #: pretix/static/pretixbase/js/addressform.js:101
#: pretix/static/pretixpresale/js/ui/main.js:529 #: pretix/static/pretixpresale/js/ui/main.js:529
msgid "required" msgid "required"
msgstr "必" msgstr "必"
#: pretix/static/pretixbase/js/asynctask.js:13 #: pretix/static/pretixbase/js/asynctask.js:13
msgid "" msgid ""
@@ -370,8 +370,7 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:17 #: pretix/static/pretixbase/js/asynctask.js:17
msgid "Your request has been queued on the server and will soon be processed." msgid "Your request has been queued on the server and will soon be processed."
msgstr "" msgstr "お客様のリクエストはサーバーで受け付けられました。まもなく処理されます。"
"サーバへ送信されたリクエスト順にお応えしています。今しばらくお待ちください。"
#: pretix/static/pretixbase/js/asynctask.js:21 #: pretix/static/pretixbase/js/asynctask.js:21
msgid "" msgid ""
@@ -394,8 +393,7 @@ msgstr "{code} のエラーが発生しました。"
msgid "" msgid ""
"We currently cannot reach the server, but we keep trying. Last error code: " "We currently cannot reach the server, but we keep trying. Last error code: "
"{code}" "{code}"
msgstr "" msgstr "現在サーバへの接続ができませんが、接続試行中です。最新のエラーコード: {code}"
"現在サーバへの接続ができませんが、接続試行中です。エラーコード: {code}"
#: pretix/static/pretixbase/js/asynctask.js:162 #: pretix/static/pretixbase/js/asynctask.js:162
#: pretix/static/pretixcontrol/js/ui/mail.js:21 #: pretix/static/pretixcontrol/js/ui/mail.js:21
@@ -419,9 +417,8 @@ msgid ""
"than one minute, please check your internet connection and then reload this " "than one minute, please check your internet connection and then reload this "
"page and try again." "page and try again."
msgstr "" msgstr ""
"リクエストサーバへ送信されました。1分以上経っても応答がない場合は、イン" "現在リクエストサーバへ送信中です。1分以上経っても応答がない場合は、"
"ターネット接続を確認してください。確認完了後、ウェブページを再度読込み、再試" "インターネット接続を確認し、このページを再読み込みして再試行してください。"
"行してください。"
#: pretix/static/pretixbase/js/asynctask.js:276 #: pretix/static/pretixbase/js/asynctask.js:276
msgid "If this takes longer than a few minutes, please contact us." msgid "If this takes longer than a few minutes, please contact us."
@@ -429,7 +426,7 @@ msgstr "数分以上かかる場合は、お問い合わせください。"
#: pretix/static/pretixbase/js/asynctask.js:331 #: pretix/static/pretixbase/js/asynctask.js:331
msgid "Close message" msgid "Close message"
msgstr "閉じる" msgstr "メッセージを閉じる"
#: pretix/static/pretixcontrol/js/clipboard.js:23 #: pretix/static/pretixcontrol/js/clipboard.js:23
msgid "Copied!" msgid "Copied!"
@@ -443,7 +440,7 @@ msgstr "Ctrl-Cを押してコピー!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:18 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:18
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:24 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:24
msgid "is one of" msgid "is one of"
msgstr "の一つです" msgstr "次のいずれか"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:30 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:30
msgid "is before" msgid "is before"
@@ -523,7 +520,7 @@ msgstr "以下のすべての条件(AND"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
msgid "At least one of the conditions below (OR)" msgid "At least one of the conditions below (OR)"
msgstr "以下の条件のうち、最低1つ(または" msgstr "以下の条件のうち、最低1つ(OR"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
msgid "Event start" msgid "Event start"
@@ -564,16 +561,16 @@ msgstr "複製"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status" msgctxt "entry_status"
msgid "present" msgid "present"
msgstr "出席" msgstr "入場中"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
msgctxt "entry_status" msgctxt "entry_status"
msgid "absent" msgid "absent"
msgstr "欠席" msgstr "未入場"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!" msgid "Error: Product not found!"
msgstr "エラー:品が見つかりません!" msgstr "エラー:品が見つかりません!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!" msgid "Error: Variation not found!"
@@ -585,7 +582,7 @@ msgstr "チェックイン用QRコード"
#: pretix/static/pretixcontrol/js/ui/editor.js:549 #: pretix/static/pretixcontrol/js/ui/editor.js:549
msgid "The PDF background file could not be loaded for the following reason:" msgid "The PDF background file could not be loaded for the following reason:"
msgstr "以下の理由によりPDFファイルの読み込みに失敗しました:" msgstr "以下の理由によりPDF背景ファイルの読み込みに失敗しました:"
#: pretix/static/pretixcontrol/js/ui/editor.js:904 #: pretix/static/pretixcontrol/js/ui/editor.js:904
msgid "Group of objects" msgid "Group of objects"
@@ -593,7 +590,7 @@ msgstr "オブジェクトグループ"
#: pretix/static/pretixcontrol/js/ui/editor.js:909 #: pretix/static/pretixcontrol/js/ui/editor.js:909
msgid "Text object (deprecated)" msgid "Text object (deprecated)"
msgstr "テキストオブジェクト (廃止済)" msgstr "テキストオブジェクト(非推奨)"
#: pretix/static/pretixcontrol/js/ui/editor.js:911 #: pretix/static/pretixcontrol/js/ui/editor.js:911
msgid "Text box" msgid "Text box"
@@ -609,7 +606,7 @@ msgstr "画像エリア"
#: pretix/static/pretixcontrol/js/ui/editor.js:917 #: pretix/static/pretixcontrol/js/ui/editor.js:917
msgid "Powered by pretix" msgid "Powered by pretix"
msgstr "Pretixのイベントチケット売り場" msgstr "Powered by pretix"
#: pretix/static/pretixcontrol/js/ui/editor.js:919 #: pretix/static/pretixcontrol/js/ui/editor.js:919
msgid "Object" msgid "Object"
@@ -647,24 +644,24 @@ msgstr "不明なエラー。"
#: pretix/static/pretixcontrol/js/ui/main.js:309 #: pretix/static/pretixcontrol/js/ui/main.js:309
msgid "Your color has great contrast and will provide excellent accessibility." msgid "Your color has great contrast and will provide excellent accessibility."
msgstr "" msgstr ""
"あなたの色は素晴らしいコントラストを持ち、優れたアクセシビリティを提供しま" "選択した色は素晴らしいコントラストを持ち、優れたアクセシビリティを提供しま"
"。" "。"
#: pretix/static/pretixcontrol/js/ui/main.js:313 #: pretix/static/pretixcontrol/js/ui/main.js:313
msgid "" msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility " "Your color has decent contrast and is sufficient for minimum accessibility "
"requirements." "requirements."
msgstr "" msgstr ""
"あなたの色は適切なコントラストを持ち、最小限のアクセシビリティ要件に十分で" "選択した色は適切なコントラストを持ち、最小限のアクセシビリティ要件に十分で"
"。" "。"
#: pretix/static/pretixcontrol/js/ui/main.js:317 #: pretix/static/pretixcontrol/js/ui/main.js:317
msgid "" msgid ""
"Your color has insufficient contrast to white. Accessibility of your site " "Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted." "will be impacted."
msgstr "" msgstr ""
"あなたの色は白に対して十分なコントラストがありません。サイトのアクセシビリ" "選択した色は白に対して十分なコントラストがありません。サイトの"
"ティに影響します。" "アクセシビリティに影響します。"
#: pretix/static/pretixcontrol/js/ui/main.js:443 #: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463 #: pretix/static/pretixcontrol/js/ui/main.js:463
@@ -677,7 +674,7 @@ msgstr "全て"
#: pretix/static/pretixcontrol/js/ui/main.js:462 #: pretix/static/pretixcontrol/js/ui/main.js:462
msgid "None" msgid "None"
msgstr "な" msgstr "な"
#: pretix/static/pretixcontrol/js/ui/main.js:466 #: pretix/static/pretixcontrol/js/ui/main.js:466
msgid "Selected only" msgid "Selected only"
@@ -693,7 +690,7 @@ msgstr "無効なページ番号。"
#: pretix/static/pretixcontrol/js/ui/main.js:1000 #: pretix/static/pretixcontrol/js/ui/main.js:1000
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "内部で別の名前を使用してください" msgstr "内部で別の名前を使用する"
#: pretix/static/pretixcontrol/js/ui/main.js:1040 #: pretix/static/pretixcontrol/js/ui/main.js:1040
msgid "Click to close" msgid "Click to close"
@@ -722,15 +719,15 @@ msgstr "カウント"
#: pretix/static/pretixcontrol/js/ui/subevent.js:112 #: pretix/static/pretixcontrol/js/ui/subevent.js:112
msgid "(one more date)" msgid "(one more date)"
msgid_plural "({num} more dates)" msgid_plural "({num} more dates)"
msgstr[0] "({num}の日程)" msgstr[0] "(他に{num}の日程)"
#: pretix/static/pretixpresale/js/ui/cart.js:47 #: pretix/static/pretixpresale/js/ui/cart.js:47
msgid "" msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available." "complete your order as long as theyre available."
msgstr "" msgstr ""
"カートに入っている商品は現在売り切れです。在庫があれば、このまま注文を完了" "カート内のアイテムの予約が解除されました。在庫がある限り、引き続き注文を完了"
"ることができます。" "できます。"
#: pretix/static/pretixpresale/js/ui/cart.js:49 #: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired" msgid "Cart expired"
@@ -744,27 +741,27 @@ msgstr "カートの有効期限が近づいています。"
#: pretix/static/pretixpresale/js/ui/cart.js:62 #: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute." 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." msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "カート内の商品の予約は {num} 分以内に完了します。" msgstr[0] "カート内の商品はあと {num} 分間確保されています。"
#: pretix/static/pretixpresale/js/ui/cart.js:83 #: pretix/static/pretixpresale/js/ui/cart.js:83
msgid "Your cart has expired." msgid "Your cart has expired."
msgstr "カートの保存期限が切れています。" msgstr "カートの有効期限が切れています。"
#: pretix/static/pretixpresale/js/ui/cart.js:86 #: pretix/static/pretixpresale/js/ui/cart.js:86
msgid "" msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as they're available." "complete your order as long as they're available."
msgstr "" msgstr ""
"カート内の商品の予約期限が切れました。在庫があれば、このまま注文を完了するこ" "カート内の商品の確保期限が切れました。在庫があれば、このまま注文を完了するこ"
"とができます。" "とができます。"
#: pretix/static/pretixpresale/js/ui/cart.js:87 #: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?" msgid "Do you want to renew the reservation period?"
msgstr "予約の期間を更新しますか?" msgstr "確保期間を更新しますか?"
#: pretix/static/pretixpresale/js/ui/cart.js:90 #: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation" msgid "Renew reservation"
msgstr "予約を更新" msgstr "確保を更新"
#: pretix/static/pretixpresale/js/ui/main.js:194 #: pretix/static/pretixpresale/js/ui/main.js:194
msgid "The organizer keeps %(currency)s %(amount)s" msgid "The organizer keeps %(currency)s %(amount)s"
@@ -780,7 +777,7 @@ msgstr "主催者が留保する料金を入力してください。"
#: pretix/static/pretixpresale/js/ui/main.js:577 #: pretix/static/pretixpresale/js/ui/main.js:577
msgid "Your local time:" msgid "Your local time:"
msgstr "現地時間" msgstr "お使いの地域の時刻"
#: pretix/static/pretixpresale/js/walletdetection.js:39 #: pretix/static/pretixpresale/js/walletdetection.js:39
msgid "Google Pay" msgid "Google Pay"
@@ -863,7 +860,7 @@ msgstr "登録"
#: pretix/static/pretixpresale/js/widget/widget.js:30 #: pretix/static/pretixpresale/js/widget/widget.js:30
msgctxt "widget" msgctxt "widget"
msgid "Reserved" msgid "Reserved"
msgstr "予約完了" msgstr "予約済み"
#: pretix/static/pretixpresale/js/widget/widget.js:31 #: pretix/static/pretixpresale/js/widget/widget.js:31
msgctxt "widget" msgctxt "widget"
@@ -879,7 +876,7 @@ msgstr "%(currency)s %(price)sから"
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Image of %s" msgid "Image of %s"
msgstr "%sのイメージ" msgstr "%sの画像"
#: pretix/static/pretixpresale/js/widget/widget.js:34 #: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget" msgctxt "widget"
@@ -889,7 +886,7 @@ msgstr "%(rate)s% %(taxname)s込"
#: pretix/static/pretixpresale/js/widget/widget.js:35 #: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget" msgctxt "widget"
msgid "plus %(rate)s% %(taxname)s" msgid "plus %(rate)s% %(taxname)s"
msgstr "%(rate)s% %(taxname)s" msgstr "別途%(rate)s% %(taxname)s"
#: pretix/static/pretixpresale/js/widget/widget.js:36 #: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget" msgctxt "widget"
@@ -899,13 +896,13 @@ msgstr "税込"
#: pretix/static/pretixpresale/js/widget/widget.js:37 #: pretix/static/pretixpresale/js/widget/widget.js:37
msgctxt "widget" msgctxt "widget"
msgid "plus taxes" msgid "plus taxes"
msgstr "税" msgstr "税"
#: pretix/static/pretixpresale/js/widget/widget.js:38 #: pretix/static/pretixpresale/js/widget/widget.js:38
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "currently available: %s" msgid "currently available: %s"
msgstr "現在%s使用可能" msgstr "現在の残数: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:39 #: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget" msgctxt "widget"
@@ -921,7 +918,7 @@ msgstr "提供開始前"
#: pretix/static/pretixpresale/js/widget/widget.js:41 #: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget" msgctxt "widget"
msgid "Not available anymore" msgid "Not available anymore"
msgstr "今後の提供不可" msgstr "提供終了"
#: pretix/static/pretixpresale/js/widget/widget.js:42 #: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget" msgctxt "widget"
@@ -937,7 +934,7 @@ msgstr "最小注文数量:%s"
#: pretix/static/pretixpresale/js/widget/widget.js:45 #: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget" msgctxt "widget"
msgid "Close ticket shop" msgid "Close ticket shop"
msgstr "チケットショップ閉店" msgstr "チケットショップを閉じる"
#: pretix/static/pretixpresale/js/widget/widget.js:46 #: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget" msgctxt "widget"
@@ -950,8 +947,8 @@ msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop " "There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue." "in a new tab to continue."
msgstr "" msgstr ""
"現在チケットショップが混み合っています。新しいタブでチケットショップを開き続" "現在チケットショップが混み合っています。新しいタブでチケットショップを開いて"
"行してください。" "行してください。"
#: pretix/static/pretixpresale/js/widget/widget.js:49 #: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget" msgctxt "widget"
@@ -975,7 +972,7 @@ msgid ""
"this ticket shop. Please click \"Continue\" to retry in a new tab." "this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr "" msgstr ""
"現在チケットショップが混雑しているため、お客様のカートを作ることができません" "現在チケットショップが混雑しているため、お客様のカートを作ることができません"
"でした。新しいタブを開き「次へ」をクリックしてください。" "でした。\"続ける\"をクリックして、新しいタブで再試行してください。"
#: pretix/static/pretixpresale/js/widget/widget.js:54 #: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget" msgctxt "widget"
@@ -988,8 +985,8 @@ msgid ""
"You currently have an active cart for this event. If you select more " "You currently have an active cart for this event. If you select more "
"products, they will be added to your existing cart." "products, they will be added to your existing cart."
msgstr "" msgstr ""
"お客様のカートはイベントの申し込みに有効です。商品を選択し、カートへ追加して" "このイベントのカートに商品が入っています。商品を追加すると、既存のカートに追"
"ください。" "加されます。"
#: pretix/static/pretixpresale/js/widget/widget.js:57 #: pretix/static/pretixpresale/js/widget/widget.js:57
msgctxt "widget" msgctxt "widget"
@@ -1079,7 +1076,7 @@ msgstr "前週"
#: pretix/static/pretixpresale/js/widget/widget.js:74 #: pretix/static/pretixpresale/js/widget/widget.js:74
msgctxt "widget" msgctxt "widget"
msgid "Open seat selection" msgid "Open seat selection"
msgstr "座席一覧を開く" msgstr "座席選択を開く"
#: pretix/static/pretixpresale/js/widget/widget.js:75 #: pretix/static/pretixpresale/js/widget/widget.js:75
msgctxt "widget" msgctxt "widget"
+11 -10
View File
@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n" "POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2025-12-03 23:00+0000\n" "PO-Revision-Date: 2026-02-01 21:00+0000\n"
"Last-Translator: SJang1 <git@sjang.dev>\n" "Last-Translator: z3rrry <z3rrry@gmail.com>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/ko/" "Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/"
">\n" "ko/>\n"
"Language: ko\n" "Language: ko\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.14.3\n" "X-Generator: Weblate 5.15.2\n"
#: pretix/_base_settings.py:87 #: pretix/_base_settings.py:87
msgid "English" msgid "English"
@@ -2283,6 +2283,9 @@ msgid ""
"contain at least one position of this product. The order totals etc. still " "contain at least one position of this product. The order totals etc. still "
"include all products contained in the order." "include all products contained in the order."
msgstr "" msgstr ""
"아무것도 선택하지 않으면, 모든 상품이 포함됩니다. 상품이 1개 이상 포함되는 "
"경우에 주문이 포함됩니다. 주문 합계 등에는 주문에 포함된 모든 상품이 "
"포함됩니다."
#: pretix/base/exporters/orderlist.py:283 #: pretix/base/exporters/orderlist.py:283
#: pretix/base/exporters/orderlist.py:478 #: pretix/base/exporters/orderlist.py:478
@@ -2546,10 +2549,8 @@ msgid "Voucher"
msgstr "바우처" msgstr "바우처"
#: pretix/base/exporters/orderlist.py:653 #: pretix/base/exporters/orderlist.py:653
#, fuzzy
#| msgid "Voucher deleted"
msgid "Voucher budget usage" msgid "Voucher budget usage"
msgstr "바우처 제거됨" msgstr "바우처 예산 사용량"
#: pretix/base/exporters/orderlist.py:654 #: pretix/base/exporters/orderlist.py:654
msgid "Pseudonymization ID" msgid "Pseudonymization ID"
@@ -26784,11 +26785,11 @@ msgstr ""
#: pretix/control/views/dashboards.py:114 #: pretix/control/views/dashboards.py:114
msgid "Attendees (ordered)" msgid "Attendees (ordered)"
msgstr "" msgstr "참가자 (정렬된)"
#: pretix/control/views/dashboards.py:124 #: pretix/control/views/dashboards.py:124
msgid "Attendees (paid)" msgid "Attendees (paid)"
msgstr "" msgstr "참가자 (결제된)"
#: pretix/control/views/dashboards.py:136 #: pretix/control/views/dashboards.py:136
#, python-brace-format #, python-brace-format
+5 -5
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2025-11-18 17:00+0000\n" "PO-Revision-Date: 2026-02-01 21:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n" "Last-Translator: z3rrry <z3rrry@gmail.com>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix-js/"
"ko/>\n" "ko/>\n"
"Language: ko\n" "Language: ko\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.14.3\n" "X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -175,12 +175,12 @@ msgstr "유료 주문"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (ordered)" msgid "Attendees (ordered)"
msgstr "" msgstr "참가자 (정렬된)"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (paid)" msgid "Attendees (paid)"
msgstr "" msgstr "참가자 (결제된)"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
msgid "Total revenue" msgid "Total revenue"
File diff suppressed because it is too large Load Diff
+28 -28
View File
@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: 1\n" "Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-01-28 00:00+0000\n" "PO-Revision-Date: 2026-02-05 23:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n" "Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
"nl/>\n" "nl/>\n"
@@ -55,7 +55,7 @@ msgstr "Kredietkaart"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40 #: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40
msgid "PayPal Pay Later" msgid "PayPal Pay Later"
msgstr "PayPal Pay Later" msgstr "PayPal - Later betalen"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41 #: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
msgid "iDEAL" msgid "iDEAL"
@@ -186,15 +186,15 @@ msgstr "Verbinding maken met uw bank …"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
msgid "Select a check-in list" msgid "Select a check-in list"
msgstr "Kies een inchecklijst" msgstr "Kies een check-in-lijst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
msgid "No active check-in lists found." msgid "No active check-in lists found."
msgstr "Geen actieve check-inlijsten gevonden." msgstr "Geen actieve check-in-lijsten gevonden."
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
msgid "Switch check-in list" msgid "Switch check-in list"
msgstr "Andere inchecklijst kiezen" msgstr "Andere check-in-lijst kiezen"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
msgid "Search results" msgid "Search results"
@@ -251,7 +251,7 @@ msgstr "Bevestigd"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
msgid "Approval pending" msgid "Approval pending"
msgstr "Goedkeuring in afwachting" msgstr "Goedkeuring in behandeling"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
msgid "Redeemed" msgid "Redeemed"
@@ -336,7 +336,7 @@ msgstr "Geldige tickets"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70
msgid "Currently inside" msgid "Currently inside"
msgstr "Op dit moment binnen" msgstr "Nu binnen"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71
#: pretix/static/pretixcontrol/js/ui/question.js:136 #: pretix/static/pretixcontrol/js/ui/question.js:136
@@ -406,8 +406,8 @@ msgstr "De aanvraag duurde te lang. Probeer het opnieuw."
msgid "" msgid ""
"We currently cannot reach the server. Please try again. Error code: {code}" "We currently cannot reach the server. Please try again. Error code: {code}"
msgstr "" msgstr ""
"De server is op dit moment niet bereikbaar. Probeer het alstublieft opnieuw. " "De server is op dit moment niet bereikbaar. Probeer het opnieuw. Foutcode: "
"Foutcode: {code}" "{code}"
#: pretix/static/pretixbase/js/asynctask.js:216 #: pretix/static/pretixbase/js/asynctask.js:216
msgid "We are processing your request …" msgid "We are processing your request …"
@@ -602,11 +602,11 @@ msgstr "Tekstvak"
#: pretix/static/pretixcontrol/js/ui/editor.js:913 #: pretix/static/pretixcontrol/js/ui/editor.js:913
msgid "Barcode area" msgid "Barcode area"
msgstr "Barcode gebied" msgstr "Ruimte voor streepjescode"
#: pretix/static/pretixcontrol/js/ui/editor.js:915 #: pretix/static/pretixcontrol/js/ui/editor.js:915
msgid "Image area" msgid "Image area"
msgstr "Afbeeldingsgebied" msgstr "Ruimte voor afbeelding"
#: pretix/static/pretixcontrol/js/ui/editor.js:917 #: pretix/static/pretixcontrol/js/ui/editor.js:917
msgid "Powered by pretix" msgid "Powered by pretix"
@@ -664,8 +664,8 @@ msgid ""
"Your color has insufficient contrast to white. Accessibility of your site " "Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted." "will be impacted."
msgstr "" msgstr ""
"Uw kleur heeft te weinig contrast met wit. De toegankelijkheid van uw site " "Uw kleur heeft onvoldoende contrast met wit. Dit heeft invloed op de "
"wordt negatief beïnvloed." "toegankelijkheid van uw website."
#: pretix/static/pretixcontrol/js/ui/main.js:443 #: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463 #: pretix/static/pretixcontrol/js/ui/main.js:463
@@ -736,7 +736,7 @@ msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:49 #: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired" msgid "Cart expired"
msgstr "Winkelwagen is verlopen" msgstr "Winkelwagen verlopen"
#: pretix/static/pretixpresale/js/ui/cart.js:58 #: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84 #: pretix/static/pretixpresale/js/ui/cart.js:84
@@ -936,12 +936,12 @@ msgstr "Momenteel niet beschikbaar"
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "minimum amount to order: %s" msgid "minimum amount to order: %s"
msgstr "minimale hoeveelheid om te bestellen: %s" msgstr "minimale bestelhoeveelheid: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:45 #: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget" msgctxt "widget"
msgid "Close ticket shop" msgid "Close ticket shop"
msgstr "Sluit ticketverkoop" msgstr "Sluit ticketwinkel"
#: pretix/static/pretixpresale/js/widget/widget.js:46 #: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget" msgctxt "widget"
@@ -1160,51 +1160,51 @@ msgstr "zondag"
#: pretix/static/pretixpresale/js/widget/widget.js:94 #: pretix/static/pretixpresale/js/widget/widget.js:94
msgid "January" msgid "January"
msgstr "Januari" msgstr "januari"
#: pretix/static/pretixpresale/js/widget/widget.js:95 #: pretix/static/pretixpresale/js/widget/widget.js:95
msgid "February" msgid "February"
msgstr "Februari" msgstr "februari"
#: pretix/static/pretixpresale/js/widget/widget.js:96 #: pretix/static/pretixpresale/js/widget/widget.js:96
msgid "March" msgid "March"
msgstr "Maart" msgstr "maart"
#: pretix/static/pretixpresale/js/widget/widget.js:97 #: pretix/static/pretixpresale/js/widget/widget.js:97
msgid "April" msgid "April"
msgstr "April" msgstr "april"
#: pretix/static/pretixpresale/js/widget/widget.js:98 #: pretix/static/pretixpresale/js/widget/widget.js:98
msgid "May" msgid "May"
msgstr "Mei" msgstr "mei"
#: pretix/static/pretixpresale/js/widget/widget.js:99 #: pretix/static/pretixpresale/js/widget/widget.js:99
msgid "June" msgid "June"
msgstr "Juni" msgstr "juni"
#: pretix/static/pretixpresale/js/widget/widget.js:100 #: pretix/static/pretixpresale/js/widget/widget.js:100
msgid "July" msgid "July"
msgstr "Juli" msgstr "juli"
#: pretix/static/pretixpresale/js/widget/widget.js:101 #: pretix/static/pretixpresale/js/widget/widget.js:101
msgid "August" msgid "August"
msgstr "Augustus" msgstr "augustus"
#: pretix/static/pretixpresale/js/widget/widget.js:102 #: pretix/static/pretixpresale/js/widget/widget.js:102
msgid "September" msgid "September"
msgstr "September" msgstr "september"
#: pretix/static/pretixpresale/js/widget/widget.js:103 #: pretix/static/pretixpresale/js/widget/widget.js:103
msgid "October" msgid "October"
msgstr "Oktober" msgstr "oktober"
#: pretix/static/pretixpresale/js/widget/widget.js:104 #: pretix/static/pretixpresale/js/widget/widget.js:104
msgid "November" msgid "November"
msgstr "November" msgstr "november"
#: pretix/static/pretixpresale/js/widget/widget.js:105 #: pretix/static/pretixpresale/js/widget/widget.js:105
msgid "December" msgid "December"
msgstr "December" msgstr "december"
#~ msgid "Time zone:" #~ msgid "Time zone:"
#~ msgstr "Tijdzone:" #~ msgstr "Tijdzone:"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-01-28 18:00+0000\n" "PO-Revision-Date: 2026-01-29 19:43+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n" "Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/" "Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix-js/nl_Informal/>\n" "pretix-js/nl_Informal/>\n"
@@ -273,7 +273,7 @@ msgstr "Dit ticket is nog niet betaald. Wil je toch doorgaan?"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
msgid "Additional information required" msgid "Additional information required"
msgstr "Extra informatie nodig" msgstr "Aanvullende informatie vereist"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
msgid "Valid ticket" msgid "Valid ticket"
@@ -321,11 +321,11 @@ msgstr "Bestelling geannuleerd"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list" msgid "Ticket code is ambiguous on list"
msgstr "" msgstr "Ticketcode is dubbelzinnig op lijst"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
msgid "Order not approved" msgid "Order not approved"
msgstr "" msgstr "Bestelling niet goedgekeurd"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
msgid "Checked-in Tickets" msgid "Checked-in Tickets"
@@ -352,11 +352,8 @@ msgid "No"
msgstr "Nee" msgstr "Nee"
#: pretix/static/lightbox/js/lightbox.js:96 #: pretix/static/lightbox/js/lightbox.js:96
#, fuzzy
#| msgctxt "widget"
#| msgid "Close"
msgid "close" msgid "close"
msgstr "Sluiten" msgstr "sluiten"
#: pretix/static/pretixbase/js/addressform.js:101 #: pretix/static/pretixbase/js/addressform.js:101
#: pretix/static/pretixpresale/js/ui/main.js:529 #: pretix/static/pretixpresale/js/ui/main.js:529
@@ -428,7 +425,7 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:276 #: pretix/static/pretixbase/js/asynctask.js:276
msgid "If this takes longer than a few minutes, please contact us." msgid "If this takes longer than a few minutes, please contact us."
msgstr "" msgstr "Als dit langer dan een paar minuten duurt, neem dan contact met ons op."
#: pretix/static/pretixbase/js/asynctask.js:331 #: pretix/static/pretixbase/js/asynctask.js:331
msgid "Close message" msgid "Close message"
@@ -458,7 +455,7 @@ msgstr "is na"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:40 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:40
msgid "=" msgid "="
msgstr "" msgstr "="
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
msgid "Product" msgid "Product"
@@ -470,7 +467,7 @@ msgstr "Productvariant"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
msgid "Gate" msgid "Gate"
msgstr "" msgstr "Ingang"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:111 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:111
msgid "Current date and time" msgid "Current date and time"
@@ -478,11 +475,11 @@ msgstr "Huidige datum en tijd"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
msgid "Current day of the week (1 = Monday, 7 = Sunday)" msgid "Current day of the week (1 = Monday, 7 = Sunday)"
msgstr "" msgstr "Huidige dag van de week (1 = maandag, 7 = zondag)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
msgid "Current entry status" msgid "Current entry status"
msgstr "" msgstr "Huidige toegangsstatus"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:123 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:123
msgid "Number of previous entries" msgid "Number of previous entries"
@@ -493,40 +490,32 @@ msgid "Number of previous entries since midnight"
msgstr "Aantal eerdere binnenkomsten sinds middernacht" msgstr "Aantal eerdere binnenkomsten sinds middernacht"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:131 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:131
#, fuzzy
#| msgid "Number of previous entries"
msgid "Number of previous entries since" msgid "Number of previous entries since"
msgstr "Aantal eerdere binnenkomsten" msgstr "Aantal eerdere binnenkomsten sinds"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:135 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:135
#, fuzzy
#| msgid "Number of previous entries"
msgid "Number of previous entries before" msgid "Number of previous entries before"
msgstr "Aantal eerdere binnenkomsten" msgstr "Aantal eerdere binnenkomsten vóór"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:139 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:139
msgid "Number of days with a previous entry" msgid "Number of days with a previous entry"
msgstr "Aantal dagen met een eerdere binnenkomst" msgstr "Aantal dagen met een eerdere binnenkomst"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:143 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:143
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry since" msgid "Number of days with a previous entry since"
msgstr "Aantal dagen met een eerdere binnenkomst" msgstr "Aantal dagen met een eerdere binnenkomst sinds"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:147 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:147
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry before" msgid "Number of days with a previous entry before"
msgstr "Aantal dagen met een eerdere binnenkomst" msgstr "Aantal dagen met een eerdere binnenkomst voor"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:151 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:151
msgid "Minutes since last entry (-1 on first entry)" msgid "Minutes since last entry (-1 on first entry)"
msgstr "" msgstr "Minuten sinds laatste binnenkomst (-1 bij eerste binnenkomst)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:155 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:155
msgid "Minutes since first entry (-1 on first entry)" msgid "Minutes since first entry (-1 on first entry)"
msgstr "" msgstr "Minuten sinds eerste binnenkomst (-1 bij eerste binnenkomst)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:182 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:182
msgid "All of the conditions below (AND)" msgid "All of the conditions below (AND)"
@@ -570,25 +559,25 @@ msgstr "minuten"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
msgid "Duplicate" msgid "Duplicate"
msgstr "" msgstr "Duplicaat"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status" msgctxt "entry_status"
msgid "present" msgid "present"
msgstr "" msgstr "aanwezig"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
msgctxt "entry_status" msgctxt "entry_status"
msgid "absent" msgid "absent"
msgstr "" msgstr "afwezig"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!" msgid "Error: Product not found!"
msgstr "" msgstr "Fout: Product niet gevonden!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!" msgid "Error: Variation not found!"
msgstr "" msgstr "Fout: Variant niet gevonden!"
#: pretix/static/pretixcontrol/js/ui/editor.js:171 #: pretix/static/pretixcontrol/js/ui/editor.js:171
msgid "Check-in QR" msgid "Check-in QR"
@@ -604,16 +593,12 @@ msgid "Group of objects"
msgstr "Groep van objecten" msgstr "Groep van objecten"
#: pretix/static/pretixcontrol/js/ui/editor.js:909 #: pretix/static/pretixcontrol/js/ui/editor.js:909
#, fuzzy
#| msgid "Text object"
msgid "Text object (deprecated)" msgid "Text object (deprecated)"
msgstr "Tekstobject" msgstr "Tekstobject (verouderd)"
#: pretix/static/pretixcontrol/js/ui/editor.js:911 #: pretix/static/pretixcontrol/js/ui/editor.js:911
#, fuzzy
#| msgid "Text object"
msgid "Text box" msgid "Text box"
msgstr "Tekstobject" msgstr "Tekstveld"
#: pretix/static/pretixcontrol/js/ui/editor.js:913 #: pretix/static/pretixcontrol/js/ui/editor.js:913
msgid "Barcode area" msgid "Barcode area"
@@ -661,25 +646,26 @@ msgid "Unknown error."
msgstr "Onbekende fout." msgstr "Onbekende fout."
#: pretix/static/pretixcontrol/js/ui/main.js:309 #: pretix/static/pretixcontrol/js/ui/main.js:309
#, fuzzy
#| msgid "Your color has great contrast and is very easy to read!"
msgid "Your color has great contrast and will provide excellent accessibility." msgid "Your color has great contrast and will provide excellent accessibility."
msgstr "Je kleur heeft een goed contrast, en is gemakkelijk te lezen!" msgstr ""
"Je kleur heeft een groot contrast en zorgt voor een uitstekende "
"toegankelijkheid."
#: pretix/static/pretixcontrol/js/ui/main.js:313 #: pretix/static/pretixcontrol/js/ui/main.js:313
#, fuzzy
#| msgid "Your color has decent contrast and is probably good-enough to read!"
msgid "" msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility " "Your color has decent contrast and is sufficient for minimum accessibility "
"requirements." "requirements."
msgstr "" msgstr ""
"Je kleur heeft een redelijk contrast, en is waarschijnlijk goed te lezen!" "Je kleur heeft een behoorlijk contrast en voldoet aan de minimale "
"toegankelijkheidseisen."
#: pretix/static/pretixcontrol/js/ui/main.js:317 #: pretix/static/pretixcontrol/js/ui/main.js:317
msgid "" msgid ""
"Your color has insufficient contrast to white. Accessibility of your site " "Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted." "will be impacted."
msgstr "" msgstr ""
"Je kleur heeft onvoldoende contrast met wit. Dit heeft invloed op de "
"toegankelijkheid van je website."
#: pretix/static/pretixcontrol/js/ui/main.js:443 #: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463 #: pretix/static/pretixcontrol/js/ui/main.js:463
@@ -700,11 +686,11 @@ msgstr "Alleen geselecteerde"
#: pretix/static/pretixcontrol/js/ui/main.js:839 #: pretix/static/pretixcontrol/js/ui/main.js:839
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr "Voer een paginanummer in tussen 1 en %(max)s."
#: pretix/static/pretixcontrol/js/ui/main.js:842 #: pretix/static/pretixcontrol/js/ui/main.js:842
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr "Ongeldig paginanummer."
#: pretix/static/pretixcontrol/js/ui/main.js:1000 #: pretix/static/pretixcontrol/js/ui/main.js:1000
msgid "Use a different name internally" msgid "Use a different name internally"
@@ -723,10 +709,8 @@ msgid "Calculating default price…"
msgstr "Standaardprijs berekenen…" msgstr "Standaardprijs berekenen…"
#: pretix/static/pretixcontrol/js/ui/plugins.js:69 #: pretix/static/pretixcontrol/js/ui/plugins.js:69
#, fuzzy
#| msgid "Search results"
msgid "No results" msgid "No results"
msgstr "Zoekresultaten" msgstr "Geen resultaten"
#: pretix/static/pretixcontrol/js/ui/question.js:41 #: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others" msgid "Others"
@@ -734,7 +718,7 @@ msgstr "Andere"
#: pretix/static/pretixcontrol/js/ui/question.js:81 #: pretix/static/pretixcontrol/js/ui/question.js:81
msgid "Count" msgid "Count"
msgstr "Aantal" msgstr "Tellen"
#: pretix/static/pretixcontrol/js/ui/subevent.js:112 #: pretix/static/pretixcontrol/js/ui/subevent.js:112
msgid "(one more date)" msgid "(one more date)"
@@ -743,12 +727,12 @@ msgstr[0] "(één andere datum)"
msgstr[1] "({num} andere datums)" msgstr[1] "({num} andere datums)"
#: pretix/static/pretixpresale/js/ui/cart.js:47 #: pretix/static/pretixpresale/js/ui/cart.js:47
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid "" msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available." "complete your order as long as theyre available."
msgstr "De items in je winkelwagen zijn niet meer voor je gereserveerd." msgstr ""
"De artikelen in je winkelwagen zijn niet langer voor je gereserveerd. Je "
"kunt je bestelling nog steeds voltooien zolang ze beschikbaar zijn."
#: pretix/static/pretixpresale/js/ui/cart.js:49 #: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired" msgid "Cart expired"
@@ -757,41 +741,34 @@ msgstr "Winkelwagen is verlopen"
#: pretix/static/pretixpresale/js/ui/cart.js:58 #: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84 #: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire." msgid "Your cart is about to expire."
msgstr "" msgstr "Je winkelwagen verloopt bijna."
#: pretix/static/pretixpresale/js/ui/cart.js:62 #: pretix/static/pretixpresale/js/ui/cart.js:62
#, fuzzy
#| 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."
msgid "The items in your cart are reserved for you for one minute." 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." msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "" msgstr[0] "De artikelen in je winkelwagen worden één minuut voor je gereserveerd."
"De items in je winkelwagen zijn nog één minuut voor je gereserveerd."
msgstr[1] "" msgstr[1] ""
"De items in je winkelwagen zijn nog {num} minuten voor je gereserveerd." "De artikelen in je winkelwagen worden {num} minuten voor je gereserveerd."
#: pretix/static/pretixpresale/js/ui/cart.js:83 #: pretix/static/pretixpresale/js/ui/cart.js:83
#, fuzzy
#| msgid "Cart expired"
msgid "Your cart has expired." msgid "Your cart has expired."
msgstr "Winkelwagen is verlopen" msgstr "Je winkelwagen is verlopen."
#: pretix/static/pretixpresale/js/ui/cart.js:86 #: pretix/static/pretixpresale/js/ui/cart.js:86
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid "" msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as they're available." "complete your order as long as they're available."
msgstr "De items in je winkelwagen zijn niet meer voor je gereserveerd." msgstr ""
"De artikelen in je winkelwagen zijn niet langer voor je gereserveerd. Je "
"kunt je bestelling nog steeds voltooien zolang ze beschikbaar zijn."
#: pretix/static/pretixpresale/js/ui/cart.js:87 #: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?" msgid "Do you want to renew the reservation period?"
msgstr "" msgstr "Wil je de reserveringsperiode verlengen?"
#: pretix/static/pretixpresale/js/ui/cart.js:90 #: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation" msgid "Renew reservation"
msgstr "" msgstr "Reservering verlengen"
#: pretix/static/pretixpresale/js/ui/main.js:194 #: pretix/static/pretixpresale/js/ui/main.js:194
msgid "The organizer keeps %(currency)s %(amount)s" msgid "The organizer keeps %(currency)s %(amount)s"
@@ -811,71 +788,66 @@ msgstr "Je lokale tijd:"
#: pretix/static/pretixpresale/js/walletdetection.js:39 #: pretix/static/pretixpresale/js/walletdetection.js:39
msgid "Google Pay" msgid "Google Pay"
msgstr "" msgstr "Google Pay"
#: pretix/static/pretixpresale/js/widget/widget.js:16 #: pretix/static/pretixpresale/js/widget/widget.js:16
msgctxt "widget" msgctxt "widget"
msgid "Quantity" msgid "Quantity"
msgstr "" msgstr "Hoeveelheid"
#: pretix/static/pretixpresale/js/widget/widget.js:17 #: pretix/static/pretixpresale/js/widget/widget.js:17
msgctxt "widget" msgctxt "widget"
msgid "Decrease quantity" msgid "Decrease quantity"
msgstr "" msgstr "Hoeveelheid verminderen"
#: pretix/static/pretixpresale/js/widget/widget.js:18 #: pretix/static/pretixpresale/js/widget/widget.js:18
msgctxt "widget" msgctxt "widget"
msgid "Increase quantity" msgid "Increase quantity"
msgstr "" msgstr "Hoeveelheid verhogen"
#: pretix/static/pretixpresale/js/widget/widget.js:19 #: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget" msgctxt "widget"
msgid "Filter events by" msgid "Filter events by"
msgstr "" msgstr "Filter evenementen op"
#: pretix/static/pretixpresale/js/widget/widget.js:20 #: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget" msgctxt "widget"
msgid "Filter" msgid "Filter"
msgstr "" msgstr "Filteren"
#: pretix/static/pretixpresale/js/widget/widget.js:21 #: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget" msgctxt "widget"
msgid "Price" msgid "Price"
msgstr "" msgstr "Prijs"
#: pretix/static/pretixpresale/js/widget/widget.js:22 #: pretix/static/pretixpresale/js/widget/widget.js:22
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Original price: %s" msgid "Original price: %s"
msgstr "" msgstr "Oorspronkelijke prijs: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:23 #: pretix/static/pretixpresale/js/widget/widget.js:23
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "New price: %s" msgid "New price: %s"
msgstr "" msgstr "Nieuwe prijs: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:24 #: pretix/static/pretixpresale/js/widget/widget.js:24
#, fuzzy
#| msgid "Selected only"
msgctxt "widget" msgctxt "widget"
msgid "Select" msgid "Select"
msgstr "Alleen geselecteerde" msgstr "Selecteren"
#: pretix/static/pretixpresale/js/widget/widget.js:25 #: pretix/static/pretixpresale/js/widget/widget.js:25
#, fuzzy, javascript-format #, javascript-format
#| msgid "Selected only"
msgctxt "widget" msgctxt "widget"
msgid "Select %s" msgid "Select %s"
msgstr "Alleen geselecteerde" msgstr "Selecteer %s"
#: pretix/static/pretixpresale/js/widget/widget.js:26 #: pretix/static/pretixpresale/js/widget/widget.js:26
#, fuzzy, javascript-format #, javascript-format
#| msgctxt "widget"
#| msgid "See variations"
msgctxt "widget" msgctxt "widget"
msgid "Select variant %s" msgid "Select variant %s"
msgstr "Zie variaties" msgstr "Selecteer variant %s"
#: pretix/static/pretixpresale/js/widget/widget.js:27 #: pretix/static/pretixpresale/js/widget/widget.js:27
msgctxt "widget" msgctxt "widget"
@@ -911,7 +883,7 @@ msgstr "vanaf %(currency)s %(price)s"
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Image of %s" msgid "Image of %s"
msgstr "" msgstr "Afbeelding van %s"
#: pretix/static/pretixpresale/js/widget/widget.js:34 #: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget" msgctxt "widget"
@@ -946,25 +918,19 @@ msgstr "Alleen beschikbaar met een voucher"
#: pretix/static/pretixpresale/js/widget/widget.js:40 #: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:43 #: pretix/static/pretixpresale/js/widget/widget.js:43
#, fuzzy
#| msgctxt "widget"
#| msgid "currently available: %s"
msgctxt "widget" msgctxt "widget"
msgid "Not yet available" msgid "Not yet available"
msgstr "nu beschikbaar: %s" msgstr "Nog niet beschikbaar"
#: pretix/static/pretixpresale/js/widget/widget.js:41 #: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget" msgctxt "widget"
msgid "Not available anymore" msgid "Not available anymore"
msgstr "" msgstr "Niet meer beschikbaar"
#: pretix/static/pretixpresale/js/widget/widget.js:42 #: pretix/static/pretixpresale/js/widget/widget.js:42
#, fuzzy
#| msgctxt "widget"
#| msgid "currently available: %s"
msgctxt "widget" msgctxt "widget"
msgid "Currently not available" msgid "Currently not available"
msgstr "nu beschikbaar: %s" msgstr "Momenteel niet beschikbaar"
#: pretix/static/pretixpresale/js/widget/widget.js:44 #: pretix/static/pretixpresale/js/widget/widget.js:44
#, javascript-format #, javascript-format
@@ -997,12 +963,9 @@ msgid "Open ticket shop"
msgstr "Open de kaartjeswinkel" msgstr "Open de kaartjeswinkel"
#: pretix/static/pretixpresale/js/widget/widget.js:50 #: pretix/static/pretixpresale/js/widget/widget.js:50
#, fuzzy
#| msgctxt "widget"
#| msgid "Resume checkout"
msgctxt "widget" msgctxt "widget"
msgid "Checkout" msgid "Checkout"
msgstr "Doorgaan met afrekenen" msgstr "Afrekenen"
#: pretix/static/pretixpresale/js/widget/widget.js:51 #: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget" msgctxt "widget"
@@ -1061,17 +1024,14 @@ msgid "Close"
msgstr "Sluiten" msgstr "Sluiten"
#: pretix/static/pretixpresale/js/widget/widget.js:62 #: pretix/static/pretixpresale/js/widget/widget.js:62
#, fuzzy
#| msgctxt "widget"
#| msgid "Resume checkout"
msgctxt "widget" msgctxt "widget"
msgid "Close checkout" msgid "Close checkout"
msgstr "Doorgaan met afrekenen" msgstr "Afrekening afsluiten"
#: pretix/static/pretixpresale/js/widget/widget.js:63 #: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget" msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish." msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr "" msgstr "Je kunt deze bewerking niet annuleren. Wacht tot het laden voltooid is."
#: pretix/static/pretixpresale/js/widget/widget.js:64 #: pretix/static/pretixpresale/js/widget/widget.js:64
msgctxt "widget" msgctxt "widget"
@@ -1079,20 +1039,14 @@ msgid "Continue"
msgstr "Ga verder" msgstr "Ga verder"
#: pretix/static/pretixpresale/js/widget/widget.js:65 #: pretix/static/pretixpresale/js/widget/widget.js:65
#, fuzzy
#| msgctxt "widget"
#| msgid "See variations"
msgctxt "widget" msgctxt "widget"
msgid "Show variants" msgid "Show variants"
msgstr "Zie variaties" msgstr "Varianten weergeven"
#: pretix/static/pretixpresale/js/widget/widget.js:66 #: pretix/static/pretixpresale/js/widget/widget.js:66
#, fuzzy
#| msgctxt "widget"
#| msgid "See variations"
msgctxt "widget" msgctxt "widget"
msgid "Hide variants" msgid "Hide variants"
msgstr "Zie variaties" msgstr "Varianten verbergen"
#: pretix/static/pretixpresale/js/widget/widget.js:67 #: pretix/static/pretixpresale/js/widget/widget.js:67
msgctxt "widget" msgctxt "widget"
@@ -1141,6 +1095,9 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available " "add yourself to the waiting list. We will then notify if seats are available "
"again." "again."
msgstr "" msgstr ""
"Sommige of alle ticketcategorieën zijn momenteel uitverkocht. Als je dat "
"wilt, kun je je op de wachtlijst laten plaatsen. Wij zullen je dan op de "
"hoogte brengen als er weer plaatsen beschikbaar zijn."
#: pretix/static/pretixpresale/js/widget/widget.js:76 #: pretix/static/pretixpresale/js/widget/widget.js:76
msgctxt "widget" msgctxt "widget"
@@ -1177,79 +1134,79 @@ msgstr "Zo"
#: pretix/static/pretixpresale/js/widget/widget.js:85 #: pretix/static/pretixpresale/js/widget/widget.js:85
msgid "Monday" msgid "Monday"
msgstr "" msgstr "maandag"
#: pretix/static/pretixpresale/js/widget/widget.js:86 #: pretix/static/pretixpresale/js/widget/widget.js:86
msgid "Tuesday" msgid "Tuesday"
msgstr "" msgstr "dinsdag"
#: pretix/static/pretixpresale/js/widget/widget.js:87 #: pretix/static/pretixpresale/js/widget/widget.js:87
msgid "Wednesday" msgid "Wednesday"
msgstr "" msgstr "woensdag"
#: pretix/static/pretixpresale/js/widget/widget.js:88 #: pretix/static/pretixpresale/js/widget/widget.js:88
msgid "Thursday" msgid "Thursday"
msgstr "" msgstr "donderdag"
#: pretix/static/pretixpresale/js/widget/widget.js:89 #: pretix/static/pretixpresale/js/widget/widget.js:89
msgid "Friday" msgid "Friday"
msgstr "" msgstr "vrijdag"
#: pretix/static/pretixpresale/js/widget/widget.js:90 #: pretix/static/pretixpresale/js/widget/widget.js:90
msgid "Saturday" msgid "Saturday"
msgstr "" msgstr "zaterdag"
#: pretix/static/pretixpresale/js/widget/widget.js:91 #: pretix/static/pretixpresale/js/widget/widget.js:91
msgid "Sunday" msgid "Sunday"
msgstr "" msgstr "zondag"
#: pretix/static/pretixpresale/js/widget/widget.js:94 #: pretix/static/pretixpresale/js/widget/widget.js:94
msgid "January" msgid "January"
msgstr "Januari" msgstr "januari"
#: pretix/static/pretixpresale/js/widget/widget.js:95 #: pretix/static/pretixpresale/js/widget/widget.js:95
msgid "February" msgid "February"
msgstr "Februari" msgstr "februari"
#: pretix/static/pretixpresale/js/widget/widget.js:96 #: pretix/static/pretixpresale/js/widget/widget.js:96
msgid "March" msgid "March"
msgstr "Maart" msgstr "maart"
#: pretix/static/pretixpresale/js/widget/widget.js:97 #: pretix/static/pretixpresale/js/widget/widget.js:97
msgid "April" msgid "April"
msgstr "April" msgstr "april"
#: pretix/static/pretixpresale/js/widget/widget.js:98 #: pretix/static/pretixpresale/js/widget/widget.js:98
msgid "May" msgid "May"
msgstr "Mei" msgstr "mei"
#: pretix/static/pretixpresale/js/widget/widget.js:99 #: pretix/static/pretixpresale/js/widget/widget.js:99
msgid "June" msgid "June"
msgstr "Juni" msgstr "juni"
#: pretix/static/pretixpresale/js/widget/widget.js:100 #: pretix/static/pretixpresale/js/widget/widget.js:100
msgid "July" msgid "July"
msgstr "Juli" msgstr "juli"
#: pretix/static/pretixpresale/js/widget/widget.js:101 #: pretix/static/pretixpresale/js/widget/widget.js:101
msgid "August" msgid "August"
msgstr "Augustus" msgstr "augustus"
#: pretix/static/pretixpresale/js/widget/widget.js:102 #: pretix/static/pretixpresale/js/widget/widget.js:102
msgid "September" msgid "September"
msgstr "September" msgstr "september"
#: pretix/static/pretixpresale/js/widget/widget.js:103 #: pretix/static/pretixpresale/js/widget/widget.js:103
msgid "October" msgid "October"
msgstr "Oktober" msgstr "oktober"
#: pretix/static/pretixpresale/js/widget/widget.js:104 #: pretix/static/pretixpresale/js/widget/widget.js:104
msgid "November" msgid "November"
msgstr "November" msgstr "november"
#: pretix/static/pretixpresale/js/widget/widget.js:105 #: pretix/static/pretixpresale/js/widget/widget.js:105
msgid "December" msgid "December"
msgstr "December" msgstr "december"
#~ msgid "Time zone:" #~ msgid "Time zone:"
#~ msgstr "Tijdzone:" #~ msgstr "Tijdzone:"
File diff suppressed because it is too large Load Diff
+113 -102
View File
@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-01-28 09:00+0000\n" "PO-Revision-Date: 2026-01-29 13:00+0000\n"
"Last-Translator: Nate Horst <nate@agcthailand.org>\n" "Last-Translator: Nate Horst <nate@agcthailand.org>\n"
"Language-Team: Thai <https://translate.pretix.eu/projects/pretix/pretix-js/" "Language-Team: Thai <https://translate.pretix.eu/projects/pretix/pretix-js/"
"th/>\n" "th/>\n"
@@ -450,7 +450,6 @@ msgid "is after"
msgstr "อยู่ถัดจาก" msgstr "อยู่ถัดจาก"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:40 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:40
#, fuzzy
msgid "=" msgid "="
msgstr "=" msgstr "="
@@ -520,147 +519,150 @@ msgstr "ทุกเงื่อนไขด้านล่างนี้ (AND)
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
msgid "At least one of the conditions below (OR)" msgid "At least one of the conditions below (OR)"
msgstr "" msgstr "ตรงตามเงื่อนไขด้านล่างอย่างน้อยหนึ่งข้อ (OR)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
msgid "Event start" msgid "Event start"
msgstr "" msgstr "เริ่มงาน"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:185 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:185
msgid "Event end" msgid "Event end"
msgstr "" msgstr "จบงาน"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:186 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:186
msgid "Event admission" msgid "Event admission"
msgstr "" msgstr "เริ่มเปิดให้เข้างาน"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:187 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:187
msgid "custom date and time" msgid "custom date and time"
msgstr "" msgstr "กำหนดวันที่และเวลาเอง"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:188 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:188
msgid "custom time" msgid "custom time"
msgstr "" msgstr "กำหนดเวลาเอง"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:189 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:189
msgid "Tolerance (minutes)" msgid "Tolerance (minutes)"
msgstr "" msgstr "ระยะเวลาที่อนุโลม (นาที)"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:190 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:190
msgid "Add condition" msgid "Add condition"
msgstr "" msgstr "เพิ่มเงื่อนไข"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:191 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:191
msgid "minutes" msgid "minutes"
msgstr "" msgstr "นาที"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
msgid "Duplicate" msgid "Duplicate"
msgstr "" msgstr "ทำสำเนา"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status" msgctxt "entry_status"
msgid "present" msgid "present"
msgstr "" msgstr "มา"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
msgctxt "entry_status" msgctxt "entry_status"
msgid "absent" msgid "absent"
msgstr "" msgstr "ไม่มา"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!" msgid "Error: Product not found!"
msgstr "" msgstr "ข้อผิดพลาด: ไม่พบสินค้า!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!" msgid "Error: Variation not found!"
msgstr "" msgstr "ข้อผิดพลาด: ไม่พบรูปแบบสินค้า!"
#: pretix/static/pretixcontrol/js/ui/editor.js:171 #: pretix/static/pretixcontrol/js/ui/editor.js:171
msgid "Check-in QR" msgid "Check-in QR"
msgstr "" msgstr "QR สำหรับเช็คอิน"
#: pretix/static/pretixcontrol/js/ui/editor.js:549 #: pretix/static/pretixcontrol/js/ui/editor.js:549
msgid "The PDF background file could not be loaded for the following reason:" msgid "The PDF background file could not be loaded for the following reason:"
msgstr "" msgstr "ไม่สามารถโหลดไฟล์พื้นหลัง PDF ได้เนื่องจากสาเหตุต่อไปนี้:"
#: pretix/static/pretixcontrol/js/ui/editor.js:904 #: pretix/static/pretixcontrol/js/ui/editor.js:904
msgid "Group of objects" msgid "Group of objects"
msgstr "" msgstr "กลุ่มของวัตถุ"
#: pretix/static/pretixcontrol/js/ui/editor.js:909 #: pretix/static/pretixcontrol/js/ui/editor.js:909
msgid "Text object (deprecated)" msgid "Text object (deprecated)"
msgstr "" msgstr "วัตถุข้อความ (เลิกใช้งานแล้ว)"
#: pretix/static/pretixcontrol/js/ui/editor.js:911 #: pretix/static/pretixcontrol/js/ui/editor.js:911
msgid "Text box" msgid "Text box"
msgstr "" msgstr "กล่องข้อความ"
#: pretix/static/pretixcontrol/js/ui/editor.js:913 #: pretix/static/pretixcontrol/js/ui/editor.js:913
msgid "Barcode area" msgid "Barcode area"
msgstr "" msgstr "พื้นที่บาร์โค้ด"
#: pretix/static/pretixcontrol/js/ui/editor.js:915 #: pretix/static/pretixcontrol/js/ui/editor.js:915
msgid "Image area" msgid "Image area"
msgstr "" msgstr "พื้นที่รูปภาพ"
#: pretix/static/pretixcontrol/js/ui/editor.js:917 #: pretix/static/pretixcontrol/js/ui/editor.js:917
msgid "Powered by pretix" msgid "Powered by pretix"
msgstr "" msgstr "สนับสนุนโดย pretix"
#: pretix/static/pretixcontrol/js/ui/editor.js:919 #: pretix/static/pretixcontrol/js/ui/editor.js:919
msgid "Object" msgid "Object"
msgstr "" msgstr "วัตถุ"
#: pretix/static/pretixcontrol/js/ui/editor.js:923 #: pretix/static/pretixcontrol/js/ui/editor.js:923
msgid "Ticket design" msgid "Ticket design"
msgstr "" msgstr "การออกแบบตั๋ว"
#: pretix/static/pretixcontrol/js/ui/editor.js:1292 #: pretix/static/pretixcontrol/js/ui/editor.js:1292
msgid "Saving failed." msgid "Saving failed."
msgstr "" msgstr "การบันทึกล้มเหลว"
#: pretix/static/pretixcontrol/js/ui/editor.js:1361 #: pretix/static/pretixcontrol/js/ui/editor.js:1361
#: pretix/static/pretixcontrol/js/ui/editor.js:1412 #: pretix/static/pretixcontrol/js/ui/editor.js:1412
msgid "Error while uploading your PDF file, please try again." msgid "Error while uploading your PDF file, please try again."
msgstr "" msgstr "เกิดข้อผิดพลาดขณะอัปโหลดไฟล์ PDF ของคุณ โปรดลองอีกครั้ง"
#: pretix/static/pretixcontrol/js/ui/editor.js:1395 #: pretix/static/pretixcontrol/js/ui/editor.js:1395
msgid "Do you really want to leave the editor without saving your changes?" msgid "Do you really want to leave the editor without saving your changes?"
msgstr "" msgstr "คุณต้องการออกจากหน้าแก้ไขโดยไม่บันทึกการเปลี่ยนแปลงใช่หรือไม่?"
#: pretix/static/pretixcontrol/js/ui/mail.js:19 #: pretix/static/pretixcontrol/js/ui/mail.js:19
msgid "An error has occurred." msgid "An error has occurred."
msgstr "" msgstr "เกิดข้อผิดพลาดขึ้น"
#: pretix/static/pretixcontrol/js/ui/mail.js:52 #: pretix/static/pretixcontrol/js/ui/mail.js:52
msgid "Generating messages …" msgid "Generating messages …"
msgstr "" msgstr "กำลังสร้างข้อความ…"
#: pretix/static/pretixcontrol/js/ui/main.js:69 #: pretix/static/pretixcontrol/js/ui/main.js:69
msgid "Unknown error." msgid "Unknown error."
msgstr "" msgstr "ข้อผิดพลาดที่ไม่ทราบสาเหตุ"
#: pretix/static/pretixcontrol/js/ui/main.js:309 #: pretix/static/pretixcontrol/js/ui/main.js:309
msgid "Your color has great contrast and will provide excellent accessibility." msgid "Your color has great contrast and will provide excellent accessibility."
msgstr "" msgstr ""
"สีของคุณมีค่าความต่าง (Contrast) ที่ดีมาก และช่วยให้เข้าถึงข้อมูลได้ง่ายขึ้น"
#: pretix/static/pretixcontrol/js/ui/main.js:313 #: pretix/static/pretixcontrol/js/ui/main.js:313
msgid "" msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility " "Your color has decent contrast and is sufficient for minimum accessibility "
"requirements." "requirements."
msgstr "" msgstr ""
"สีของคุณมีความต่างที่เหมาะสม และเพียงพอต่อข้อกำหนดขั้นต่ำของการเข้าถึงข้อมูล"
#: pretix/static/pretixcontrol/js/ui/main.js:317 #: pretix/static/pretixcontrol/js/ui/main.js:317
msgid "" msgid ""
"Your color has insufficient contrast to white. Accessibility of your site " "Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted." "will be impacted."
msgstr "" msgstr ""
"สีของคุณมีความต่าง (Contrast) กับสีขาวไม่เพียงพอ ซึ่งจะส่งผลกระทบต่อการเข้าถึงข้อมูลของเว็บไซต์"
#: pretix/static/pretixcontrol/js/ui/main.js:443 #: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463 #: pretix/static/pretixcontrol/js/ui/main.js:463
msgid "Search query" msgid "Search query"
msgstr "" msgstr "คำค้นหา"
#: pretix/static/pretixcontrol/js/ui/main.js:461 #: pretix/static/pretixcontrol/js/ui/main.js:461
msgid "All" msgid "All"
@@ -668,47 +670,47 @@ msgstr "ทั้งหมด"
#: pretix/static/pretixcontrol/js/ui/main.js:462 #: pretix/static/pretixcontrol/js/ui/main.js:462
msgid "None" msgid "None"
msgstr "" msgstr "ไม่เลือก"
#: pretix/static/pretixcontrol/js/ui/main.js:466 #: pretix/static/pretixcontrol/js/ui/main.js:466
msgid "Selected only" msgid "Selected only"
msgstr "" msgstr "เฉพาะที่เลือก"
#: pretix/static/pretixcontrol/js/ui/main.js:839 #: pretix/static/pretixcontrol/js/ui/main.js:839
msgid "Enter page number between 1 and %(max)s." msgid "Enter page number between 1 and %(max)s."
msgstr "" msgstr "โปรดระบุหมายเลขหน้าตั้งแต่ 1 ถึง %(max)s"
#: pretix/static/pretixcontrol/js/ui/main.js:842 #: pretix/static/pretixcontrol/js/ui/main.js:842
msgid "Invalid page number." msgid "Invalid page number."
msgstr "" msgstr "หมายเลขหน้าไม่ถูกต้อง"
#: pretix/static/pretixcontrol/js/ui/main.js:1000 #: pretix/static/pretixcontrol/js/ui/main.js:1000
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "" msgstr "ใช้ชื่ออื่นสำหรับเรียกภายใน"
#: pretix/static/pretixcontrol/js/ui/main.js:1040 #: pretix/static/pretixcontrol/js/ui/main.js:1040
msgid "Click to close" msgid "Click to close"
msgstr "" msgstr "คลิกเพื่อปิด"
#: pretix/static/pretixcontrol/js/ui/main.js:1121 #: pretix/static/pretixcontrol/js/ui/main.js:1121
msgid "You have unsaved changes!" msgid "You have unsaved changes!"
msgstr "" msgstr "คุณมีสิ่งที่ยังไม่ได้บันทึกการเปลี่ยนแปลง!"
#: pretix/static/pretixcontrol/js/ui/orderchange.js:25 #: pretix/static/pretixcontrol/js/ui/orderchange.js:25
msgid "Calculating default price…" msgid "Calculating default price…"
msgstr "" msgstr "กำลังคำนวณราคาเริ่มต้น…"
#: pretix/static/pretixcontrol/js/ui/plugins.js:69 #: pretix/static/pretixcontrol/js/ui/plugins.js:69
msgid "No results" msgid "No results"
msgstr "" msgstr "ไม่พบผลลัพธ์"
#: pretix/static/pretixcontrol/js/ui/question.js:41 #: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others" msgid "Others"
msgstr "" msgstr "อื่น ๆ"
#: pretix/static/pretixcontrol/js/ui/question.js:81 #: pretix/static/pretixcontrol/js/ui/question.js:81
msgid "Count" msgid "Count"
msgstr "" msgstr "จำนวน"
#: pretix/static/pretixcontrol/js/ui/subevent.js:112 #: pretix/static/pretixcontrol/js/ui/subevent.js:112
msgid "(one more date)" msgid "(one more date)"
@@ -725,12 +727,12 @@ msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:49 #: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired" msgid "Cart expired"
msgstr "" msgstr "ตะกร้าสินค้าหมดเวลาแล้ว"
#: pretix/static/pretixpresale/js/ui/cart.js:58 #: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84 #: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire." msgid "Your cart is about to expire."
msgstr "" msgstr "ตะกร้าสินค้าของคุณกำลังจะหมดเวลา"
#: pretix/static/pretixpresale/js/ui/cart.js:62 #: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute." msgid "The items in your cart are reserved for you for one minute."
@@ -740,17 +742,19 @@ msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/cart.js:83 #: pretix/static/pretixpresale/js/ui/cart.js:83
msgid "Your cart has expired." msgid "Your cart has expired."
msgstr "" msgstr "ตะกร้าสินค้าของคุณหมดเวลาแล้ว"
#: pretix/static/pretixpresale/js/ui/cart.js:86 #: pretix/static/pretixpresale/js/ui/cart.js:86
msgid "" msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as they're available." "complete your order as long as they're available."
msgstr "" msgstr ""
"รายการในตะกร้าไม่ได้ถูกสำรองไว้ให้คุณแล้ว แต่คุณยังสามารถสั่งซื้อให้เสร็จสมบูรณ์ได้ตราบเท่าที่สินค้ายัง"
"มีอยู่"
#: pretix/static/pretixpresale/js/ui/cart.js:87 #: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?" msgid "Do you want to renew the reservation period?"
msgstr "" msgstr "คุณต้องการต่อเวลาการสำรองที่นั่ง/สินค้าหรือไม่?"
#: pretix/static/pretixpresale/js/ui/cart.js:90 #: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation" msgid "Renew reservation"
@@ -758,23 +762,23 @@ msgstr "ต่อเวลาการสำรอง"
#: pretix/static/pretixpresale/js/ui/main.js:194 #: pretix/static/pretixpresale/js/ui/main.js:194
msgid "The organizer keeps %(currency)s %(amount)s" msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "" msgstr "ผู้จัดงานจะเก็บเงินจำนวน %(currency)s %(amount)s"
#: pretix/static/pretixpresale/js/ui/main.js:202 #: pretix/static/pretixpresale/js/ui/main.js:202
msgid "You get %(currency)s %(amount)s back" msgid "You get %(currency)s %(amount)s back"
msgstr "" msgstr "คุณจะได้รับเงินคืนจำนวน %(currency)s %(amount)s"
#: pretix/static/pretixpresale/js/ui/main.js:218 #: pretix/static/pretixpresale/js/ui/main.js:218
msgid "Please enter the amount the organizer can keep." msgid "Please enter the amount the organizer can keep."
msgstr "" msgstr "โปรดระบุจำนวนเงินที่ผู้จัดงานสามารถเก็บไว้ได้"
#: pretix/static/pretixpresale/js/ui/main.js:577 #: pretix/static/pretixpresale/js/ui/main.js:577
msgid "Your local time:" msgid "Your local time:"
msgstr "" msgstr "เวลาท้องถิ่นของคุณ:"
#: pretix/static/pretixpresale/js/walletdetection.js:39 #: pretix/static/pretixpresale/js/walletdetection.js:39
msgid "Google Pay" msgid "Google Pay"
msgstr "" msgstr "Google Pay"
#: pretix/static/pretixpresale/js/widget/widget.js:16 #: pretix/static/pretixpresale/js/widget/widget.js:16
msgctxt "widget" msgctxt "widget"
@@ -784,155 +788,155 @@ msgstr "จำนวน"
#: pretix/static/pretixpresale/js/widget/widget.js:17 #: pretix/static/pretixpresale/js/widget/widget.js:17
msgctxt "widget" msgctxt "widget"
msgid "Decrease quantity" msgid "Decrease quantity"
msgstr "" msgstr "ลดจำนวน"
#: pretix/static/pretixpresale/js/widget/widget.js:18 #: pretix/static/pretixpresale/js/widget/widget.js:18
msgctxt "widget" msgctxt "widget"
msgid "Increase quantity" msgid "Increase quantity"
msgstr "" msgstr "เพิ่มจำนวน"
#: pretix/static/pretixpresale/js/widget/widget.js:19 #: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget" msgctxt "widget"
msgid "Filter events by" msgid "Filter events by"
msgstr "" msgstr "กรองกิจกรรมตาม"
#: pretix/static/pretixpresale/js/widget/widget.js:20 #: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget" msgctxt "widget"
msgid "Filter" msgid "Filter"
msgstr "" msgstr "กรอง"
#: pretix/static/pretixpresale/js/widget/widget.js:21 #: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget" msgctxt "widget"
msgid "Price" msgid "Price"
msgstr "" msgstr "ราคา"
#: pretix/static/pretixpresale/js/widget/widget.js:22 #: pretix/static/pretixpresale/js/widget/widget.js:22
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Original price: %s" msgid "Original price: %s"
msgstr "" msgstr "ราคาเดิม: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:23 #: pretix/static/pretixpresale/js/widget/widget.js:23
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "New price: %s" msgid "New price: %s"
msgstr "" msgstr "ราคาใหม่: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:24 #: pretix/static/pretixpresale/js/widget/widget.js:24
msgctxt "widget" msgctxt "widget"
msgid "Select" msgid "Select"
msgstr "" msgstr "เลือก"
#: pretix/static/pretixpresale/js/widget/widget.js:25 #: pretix/static/pretixpresale/js/widget/widget.js:25
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Select %s" msgid "Select %s"
msgstr "" msgstr "เลือก %s"
#: pretix/static/pretixpresale/js/widget/widget.js:26 #: pretix/static/pretixpresale/js/widget/widget.js:26
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Select variant %s" msgid "Select variant %s"
msgstr "" msgstr "เลือกรูปแบบ %s"
#: pretix/static/pretixpresale/js/widget/widget.js:27 #: pretix/static/pretixpresale/js/widget/widget.js:27
msgctxt "widget" msgctxt "widget"
msgid "Sold out" msgid "Sold out"
msgstr "" msgstr "ขายหมดแล้ว"
#: pretix/static/pretixpresale/js/widget/widget.js:28 #: pretix/static/pretixpresale/js/widget/widget.js:28
msgctxt "widget" msgctxt "widget"
msgid "Buy" msgid "Buy"
msgstr "" msgstr "ซื้อ"
#: pretix/static/pretixpresale/js/widget/widget.js:29 #: pretix/static/pretixpresale/js/widget/widget.js:29
msgctxt "widget" msgctxt "widget"
msgid "Register" msgid "Register"
msgstr "" msgstr "ลงทะเบียน"
#: pretix/static/pretixpresale/js/widget/widget.js:30 #: pretix/static/pretixpresale/js/widget/widget.js:30
msgctxt "widget" msgctxt "widget"
msgid "Reserved" msgid "Reserved"
msgstr "" msgstr "สำรองไว้แล้ว"
#: pretix/static/pretixpresale/js/widget/widget.js:31 #: pretix/static/pretixpresale/js/widget/widget.js:31
msgctxt "widget" msgctxt "widget"
msgid "FREE" msgid "FREE"
msgstr "" msgstr "ฟรี"
#: pretix/static/pretixpresale/js/widget/widget.js:32 #: pretix/static/pretixpresale/js/widget/widget.js:32
msgctxt "widget" msgctxt "widget"
msgid "from %(currency)s %(price)s" msgid "from %(currency)s %(price)s"
msgstr "" msgstr "เริ่มต้นที่ %(currency)s %(price)s"
#: pretix/static/pretixpresale/js/widget/widget.js:33 #: pretix/static/pretixpresale/js/widget/widget.js:33
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Image of %s" msgid "Image of %s"
msgstr "" msgstr "รูปภาพของ %s"
#: pretix/static/pretixpresale/js/widget/widget.js:34 #: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget" msgctxt "widget"
msgid "incl. %(rate)s% %(taxname)s" msgid "incl. %(rate)s% %(taxname)s"
msgstr "" msgstr "รวม %(taxname)s %(rate)s%"
#: pretix/static/pretixpresale/js/widget/widget.js:35 #: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget" msgctxt "widget"
msgid "plus %(rate)s% %(taxname)s" msgid "plus %(rate)s% %(taxname)s"
msgstr "" msgstr "บวก %(taxname)s %(rate)s% เพิ่ม"
#: pretix/static/pretixpresale/js/widget/widget.js:36 #: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget" msgctxt "widget"
msgid "incl. taxes" msgid "incl. taxes"
msgstr "" msgstr "รวมภาษีแล้ว"
#: pretix/static/pretixpresale/js/widget/widget.js:37 #: pretix/static/pretixpresale/js/widget/widget.js:37
msgctxt "widget" msgctxt "widget"
msgid "plus taxes" msgid "plus taxes"
msgstr "" msgstr "ยังไม่รวมภาษี"
#: pretix/static/pretixpresale/js/widget/widget.js:38 #: pretix/static/pretixpresale/js/widget/widget.js:38
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "currently available: %s" msgid "currently available: %s"
msgstr "" msgstr "จำนวนที่ว่างอยู่: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:39 #: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget" msgctxt "widget"
msgid "Only available with a voucher" msgid "Only available with a voucher"
msgstr "" msgstr "สำหรับผู้ที่มีเวาเชอร์เท่านั้น"
#: pretix/static/pretixpresale/js/widget/widget.js:40 #: pretix/static/pretixpresale/js/widget/widget.js:40
#: pretix/static/pretixpresale/js/widget/widget.js:43 #: pretix/static/pretixpresale/js/widget/widget.js:43
msgctxt "widget" msgctxt "widget"
msgid "Not yet available" msgid "Not yet available"
msgstr "" msgstr "ยังไม่เปิดให้จอง/ซื้อ"
#: pretix/static/pretixpresale/js/widget/widget.js:41 #: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget" msgctxt "widget"
msgid "Not available anymore" msgid "Not available anymore"
msgstr "" msgstr "ไม่สามารถจอง/ซื้อได้แล้ว"
#: pretix/static/pretixpresale/js/widget/widget.js:42 #: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget" msgctxt "widget"
msgid "Currently not available" msgid "Currently not available"
msgstr "" msgstr "ขณะนี้ยังไม่เปิดให้จอง/ซื้อ"
#: pretix/static/pretixpresale/js/widget/widget.js:44 #: pretix/static/pretixpresale/js/widget/widget.js:44
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "minimum amount to order: %s" msgid "minimum amount to order: %s"
msgstr "" msgstr "จำนวนสั่งซื้อขั้นต่ำ: %s"
#: pretix/static/pretixpresale/js/widget/widget.js:45 #: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget" msgctxt "widget"
msgid "Close ticket shop" msgid "Close ticket shop"
msgstr "" msgstr "ปิดหน้าร้านจำหน่ายตั๋ว"
#: pretix/static/pretixpresale/js/widget/widget.js:46 #: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget" msgctxt "widget"
msgid "The ticket shop could not be loaded." msgid "The ticket shop could not be loaded."
msgstr "" msgstr "ไม่สามารถโหลดหน้าร้านจำหน่ายตั๋วได้"
#: pretix/static/pretixpresale/js/widget/widget.js:47 #: pretix/static/pretixpresale/js/widget/widget.js:47
msgctxt "widget" msgctxt "widget"
@@ -940,21 +944,22 @@ msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop " "There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue." "in a new tab to continue."
msgstr "" msgstr ""
"ขณะนี้มีผู้ใช้งานหน้าร้านจำนวนมาก โปรดเปิดหน้าร้านในแท็บใหม่เพื่อดำเนินการต่อ"
#: pretix/static/pretixpresale/js/widget/widget.js:49 #: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget" msgctxt "widget"
msgid "Open ticket shop" msgid "Open ticket shop"
msgstr "" msgstr "เปิดหน้าร้านจำหน่ายตั๋ว"
#: pretix/static/pretixpresale/js/widget/widget.js:50 #: pretix/static/pretixpresale/js/widget/widget.js:50
msgctxt "widget" msgctxt "widget"
msgid "Checkout" msgid "Checkout"
msgstr "" msgstr "ชำระเงิน"
#: pretix/static/pretixpresale/js/widget/widget.js:51 #: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget" msgctxt "widget"
msgid "The cart could not be created. Please try again later" msgid "The cart could not be created. Please try again later"
msgstr "" msgstr "ไม่สามารถสร้างตะกร้าสินค้าได้ โปรดลองอีกครั้งในภายหลัง"
#: pretix/static/pretixpresale/js/widget/widget.js:52 #: pretix/static/pretixpresale/js/widget/widget.js:52
msgctxt "widget" msgctxt "widget"
@@ -962,6 +967,8 @@ msgid ""
"We could not create your cart, since there are currently too many users in " "We could not create your cart, since there are currently too many users in "
"this ticket shop. Please click \"Continue\" to retry in a new tab." "this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr "" msgstr ""
"ไม่สามารถสร้างตะกร้าสินค้าได้ เนื่องจากมีผู้ใช้งานหน้าร้านจำนวนมาก โปรดคลิก \"ดำเนินการต่อ\" เ"
"พื่อลองใหม่อีกครั้งในแท็บใหม่"
#: pretix/static/pretixpresale/js/widget/widget.js:54 #: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget" msgctxt "widget"
@@ -974,26 +981,28 @@ msgid ""
"You currently have an active cart for this event. If you select more " "You currently have an active cart for this event. If you select more "
"products, they will be added to your existing cart." "products, they will be added to your existing cart."
msgstr "" msgstr ""
"คุณมีรายการในตะกร้าสำหรับกิจกรรมนี้อยู่แล้ว หากคุณเลือกสินค้าเพิ่ม ระบบจะนำไปรวมในตะกร้าเดิมของ"
"คุณ"
#: pretix/static/pretixpresale/js/widget/widget.js:57 #: pretix/static/pretixpresale/js/widget/widget.js:57
msgctxt "widget" msgctxt "widget"
msgid "Resume checkout" msgid "Resume checkout"
msgstr "" msgstr "ดำเนินการชำระเงินต่อ"
#: pretix/static/pretixpresale/js/widget/widget.js:58 #: pretix/static/pretixpresale/js/widget/widget.js:58
msgctxt "widget" msgctxt "widget"
msgid "Redeem a voucher" msgid "Redeem a voucher"
msgstr "" msgstr "แลกใช้เวาเชอร์"
#: pretix/static/pretixpresale/js/widget/widget.js:59 #: pretix/static/pretixpresale/js/widget/widget.js:59
msgctxt "widget" msgctxt "widget"
msgid "Redeem" msgid "Redeem"
msgstr "" msgstr "แลกใช้"
#: pretix/static/pretixpresale/js/widget/widget.js:60 #: pretix/static/pretixpresale/js/widget/widget.js:60
msgctxt "widget" msgctxt "widget"
msgid "Voucher code" msgid "Voucher code"
msgstr "" msgstr "รหัสเวาเชอร์"
#: pretix/static/pretixpresale/js/widget/widget.js:61 #: pretix/static/pretixpresale/js/widget/widget.js:61
msgctxt "widget" msgctxt "widget"
@@ -1003,67 +1012,67 @@ msgstr "ปิด"
#: pretix/static/pretixpresale/js/widget/widget.js:62 #: pretix/static/pretixpresale/js/widget/widget.js:62
msgctxt "widget" msgctxt "widget"
msgid "Close checkout" msgid "Close checkout"
msgstr "" msgstr "ปิดหน้าชำระเงิน"
#: pretix/static/pretixpresale/js/widget/widget.js:63 #: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget" msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish." msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr "" msgstr "ไม่สามารถยกเลิกรายการนี้ได้ โปรดรอจนกว่าจะโหลดเสร็จสมบูรณ์"
#: pretix/static/pretixpresale/js/widget/widget.js:64 #: pretix/static/pretixpresale/js/widget/widget.js:64
msgctxt "widget" msgctxt "widget"
msgid "Continue" msgid "Continue"
msgstr "" msgstr "ดำเนินการต่อ"
#: pretix/static/pretixpresale/js/widget/widget.js:65 #: pretix/static/pretixpresale/js/widget/widget.js:65
msgctxt "widget" msgctxt "widget"
msgid "Show variants" msgid "Show variants"
msgstr "" msgstr "แสดงรูปแบบเพิ่มเติม"
#: pretix/static/pretixpresale/js/widget/widget.js:66 #: pretix/static/pretixpresale/js/widget/widget.js:66
msgctxt "widget" msgctxt "widget"
msgid "Hide variants" msgid "Hide variants"
msgstr "" msgstr "ซ่อนรูปแบบ"
#: pretix/static/pretixpresale/js/widget/widget.js:67 #: pretix/static/pretixpresale/js/widget/widget.js:67
msgctxt "widget" msgctxt "widget"
msgid "Choose a different event" msgid "Choose a different event"
msgstr "" msgstr "เลือกกิจกรรมอื่น"
#: pretix/static/pretixpresale/js/widget/widget.js:68 #: pretix/static/pretixpresale/js/widget/widget.js:68
msgctxt "widget" msgctxt "widget"
msgid "Choose a different date" msgid "Choose a different date"
msgstr "" msgstr "เลือกวันที่อื่น"
#: pretix/static/pretixpresale/js/widget/widget.js:69 #: pretix/static/pretixpresale/js/widget/widget.js:69
msgctxt "widget" msgctxt "widget"
msgid "Back" msgid "Back"
msgstr "" msgstr "ย้อนกลับ"
#: pretix/static/pretixpresale/js/widget/widget.js:70 #: pretix/static/pretixpresale/js/widget/widget.js:70
msgctxt "widget" msgctxt "widget"
msgid "Next month" msgid "Next month"
msgstr "" msgstr "เดือนถัดไป"
#: pretix/static/pretixpresale/js/widget/widget.js:71 #: pretix/static/pretixpresale/js/widget/widget.js:71
msgctxt "widget" msgctxt "widget"
msgid "Previous month" msgid "Previous month"
msgstr "" msgstr "เดือนก่อนหน้า"
#: pretix/static/pretixpresale/js/widget/widget.js:72 #: pretix/static/pretixpresale/js/widget/widget.js:72
msgctxt "widget" msgctxt "widget"
msgid "Next week" msgid "Next week"
msgstr "" msgstr "สัปดาห์ถัดไป"
#: pretix/static/pretixpresale/js/widget/widget.js:73 #: pretix/static/pretixpresale/js/widget/widget.js:73
msgctxt "widget" msgctxt "widget"
msgid "Previous week" msgid "Previous week"
msgstr "" msgstr "สัปดาห์ก่อนหน้า"
#: pretix/static/pretixpresale/js/widget/widget.js:74 #: pretix/static/pretixpresale/js/widget/widget.js:74
msgctxt "widget" msgctxt "widget"
msgid "Open seat selection" msgid "Open seat selection"
msgstr "" msgstr "เลือกที่นั่ง"
#: pretix/static/pretixpresale/js/widget/widget.js:75 #: pretix/static/pretixpresale/js/widget/widget.js:75
msgctxt "widget" msgctxt "widget"
@@ -1072,6 +1081,8 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available " "add yourself to the waiting list. We will then notify if seats are available "
"again." "again."
msgstr "" msgstr ""
"ขณะนี้ตั๋วบางประเภทหรือทุกประเภทขายหมดแล้ว หากคุณต้องการ คุณสามารถลงชื่อในรายชื่อรอ "
"(Waiting List) ได้ เราจะแจ้งให้คุณทราบทันทีหากมีที่นั่งว่างอีกครั้ง"
#: pretix/static/pretixpresale/js/widget/widget.js:76 #: pretix/static/pretixpresale/js/widget/widget.js:76
msgctxt "widget" msgctxt "widget"
+4 -11
View File
@@ -56,7 +56,6 @@ from pretix.base.models import (
) )
from pretix.base.payment import PaymentException from pretix.base.payment import PaymentException
from pretix.base.services.locking import LockTimeoutException from pretix.base.services.locking import LockTimeoutException
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import change_payment_provider from pretix.base.services.orders import change_payment_provider
from pretix.base.services.tasks import TransactionAwareTask from pretix.base.services.tasks import TransactionAwareTask
from pretix.celery_app import app from pretix.celery_app import app
@@ -72,13 +71,10 @@ def notify_incomplete_payment(o: Order):
email_context = get_email_context(event=o.event, order=o, pending_sum=o.pending_sum) email_context = get_email_context(event=o.event, order=o, pending_sum=o.pending_sum)
email_subject = o.event.settings.mail_subject_order_incomplete_payment email_subject = o.event.settings.mail_subject_order_incomplete_payment
try: o.send_mail(
o.send_mail( email_subject, email_template, email_context,
email_subject, email_template, email_context, 'pretix.event.order.email.expire_warning_sent'
'pretix.event.order.email.expire_warning_sent' )
)
except SendMailException:
logger.exception('Reminder email could not be sent')
def cancel_old_payments(order): def cancel_old_payments(order):
@@ -288,9 +284,6 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, regex_match_to_s
except Quota.QuotaExceededException: except Quota.QuotaExceededException:
# payment confirmed but order status could not be set, no longer problem of this plugin # payment confirmed but order status could not be set, no longer problem of this plugin
cancel_old_payments(order) cancel_old_payments(order)
except SendMailException:
# payment confirmed but order status could not be set, no longer problem of this plugin
cancel_old_payments(order)
else: else:
cancel_old_payments(order) cancel_old_payments(order)
@@ -21,10 +21,10 @@
<dt>{% trans "Reference code (important):" %}</dt><dd><b>{{ code }}</b></dd> <dt>{% trans "Reference code (important):" %}</dt><dd><b>{{ code }}</b></dd>
<dt>{% trans "Amount:" %}</dt><dd>{{ amount|money:event.currency }}</dd> <dt>{% trans "Amount:" %}</dt><dd>{{ amount|money:event.currency }}</dd>
{% if settings.bank_details_type == "sepa" %} {% if settings.bank_details_type == "sepa" %}
<dt>{% trans "Account holder" %}:</dt><dd>{{ settings.bank_details_sepa_name }}</dt> <dt>{% trans "Account holder" %}:</dt><dd>{{ settings.bank_details_sepa_name }}</dd>
<dt>{% trans "IBAN" %}:</dt><dd>{{ settings.bank_details_sepa_iban|ibanformat }}</dt> <dt>{% trans "IBAN" %}:</dt><dd>{{ settings.bank_details_sepa_iban|ibanformat }}</dd>
<dt>{% trans "BIC" %}:</dt><dd>{{ settings.bank_details_sepa_bic }}</dt> <dt>{% trans "BIC" %}:</dt><dd>{{ settings.bank_details_sepa_bic }}</dd>
<dt>{% trans "Bank" %}:</dt><dd>{{ settings.bank_details_sepa_bank }}</dt> <dt>{% trans "Bank" %}:</dt><dd>{{ settings.bank_details_sepa_bank }}</dd>
{% endif %} {% endif %}
</dl> </dl>
{% if details %} {% if details %}
@@ -38,4 +38,4 @@
{% if payment_qr_codes %} {% if payment_qr_codes %}
{% include "pretixpresale/event/payment_qr_codes.html" %} {% include "pretixpresale/event/payment_qr_codes.html" %}
{% endif %} {% endif %}
</div> </div>
-6
View File
@@ -58,7 +58,6 @@ from localflavor.generic.forms import BICFormField, IBANFormField
from pretix.base.forms.widgets import DatePickerWidget from pretix.base.forms.widgets import DatePickerWidget
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
from pretix.base.templatetags.money import money_filter from pretix.base.templatetags.money import money_filter
from pretix.control.permissions import ( from pretix.control.permissions import (
@@ -160,11 +159,6 @@ class ActionView(View):
p.confirm(user=self.request.user) p.confirm(user=self.request.user)
except Quota.QuotaExceededException: except Quota.QuotaExceededException:
pass pass
except SendMailException:
return JsonResponse({
'status': 'error',
'message': _('Problem sending email.')
})
trans.state = BankTransaction.STATE_VALID trans.state = BankTransaction.STATE_VALID
trans.save() trans.save()
trans.order.payments.filter( trans.order.payments.filter(
-4
View File
@@ -57,7 +57,6 @@ from pretix.base.decimal import round_decimal
from pretix.base.forms import SecretKeySettingsField from pretix.base.forms import SecretKeySettingsField
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.plugins.paypal.api import Api from pretix.plugins.paypal.api import Api
@@ -468,9 +467,6 @@ class Paypal(BasePaymentProvider):
payment_obj.confirm() payment_obj.confirm()
except Quota.QuotaExceededException as e: except Quota.QuotaExceededException as e:
raise PaymentException(str(e)) raise PaymentException(str(e))
except SendMailException:
messages.warning(request, _('There was an error sending the confirmation mail.'))
return None return None
def payment_pending_render(self, request, payment) -> str: def payment_pending_render(self, request, payment) -> str:
-4
View File
@@ -54,7 +54,6 @@ from pretix.base.forms import SecretKeySettingsField
from pretix.base.forms.questions import guess_country from pretix.base.forms.questions import guess_country
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from pretix.helpers.urls import build_absolute_uri as build_global_uri from pretix.helpers.urls import build_absolute_uri as build_global_uri
@@ -821,9 +820,6 @@ class PaypalMethod(BasePaymentProvider):
payment.confirm() payment.confirm()
except Quota.QuotaExceededException as e: except Quota.QuotaExceededException as e:
raise PaymentException(str(e)) raise PaymentException(str(e))
except SendMailException:
messages.warning(request, _('There was an error sending the confirmation mail.'))
finally: finally:
if 'payment_paypal_oid' in request.session: if 'payment_paypal_oid' in request.session:
del request.session['payment_paypal_oid'] del request.session['payment_paypal_oid']
+26 -33
View File
@@ -38,7 +38,6 @@ from pretix.base.models import (
fields, fields,
) )
from pretix.base.models.base import LoggingMixin from pretix.base.models.base import LoggingMixin
from pretix.base.services.mail import SendMailException
class ScheduledMail(models.Model): class ScheduledMail(models.Model):
@@ -180,13 +179,10 @@ class ScheduledMail(models.Model):
invoice_address=ia, invoice_address=ia,
event_or_subevent=self.subevent or e, event_or_subevent=self.subevent or e,
) )
try: o.send_mail(self.rule.subject, self.rule.template, email_ctx,
o.send_mail(self.rule.subject, self.rule.template, email_ctx, attach_ical=self.rule.attach_ical,
attach_ical=self.rule.attach_ical, log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent') o_sent = True
o_sent = True
except SendMailException:
... # ¯\_(ツ)_/¯
if send_to_attendees: if send_to_attendees:
if not self.rule.all_products: if not self.rule.all_products:
@@ -195,31 +191,28 @@ class ScheduledMail(models.Model):
positions = [p for p in positions if p.subevent_id == self.subevent_id] positions = [p for p in positions if p.subevent_id == self.subevent_id]
for p in positions: for p in positions:
try: if p.attendee_email and (p.attendee_email != o.email or not o_sent):
if p.attendee_email and (p.attendee_email != o.email or not o_sent): email_ctx = get_email_context(
email_ctx = get_email_context( event=e,
event=e, order=o,
order=o, invoice_address=ia,
invoice_address=ia, position=p,
position=p, event_or_subevent=self.subevent or e,
event_or_subevent=self.subevent or e, )
) p.send_mail(self.rule.subject, self.rule.template, email_ctx,
p.send_mail(self.rule.subject, self.rule.template, email_ctx, attach_ical=self.rule.attach_ical,
attach_ical=self.rule.attach_ical, log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent') elif not o_sent and o.email:
elif not o_sent and o.email: email_ctx = get_email_context(
email_ctx = get_email_context( event=e,
event=e, order=o,
order=o, invoice_address=ia,
invoice_address=ia, event_or_subevent=self.subevent or e,
event_or_subevent=self.subevent or e, )
) o.send_mail(self.rule.subject, self.rule.template, email_ctx,
o.send_mail(self.rule.subject, self.rule.template, email_ctx, attach_ical=self.rule.attach_ical,
attach_ical=self.rule.attach_ical, log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent') o_sent = True
o_sent = True
except SendMailException:
... # ¯\_(ツ)_/¯
self.last_successful_order_id = o.pk self.last_successful_order_id = o.pk
+37 -44
View File
@@ -41,7 +41,7 @@ from pretix.base.i18n import language
from pretix.base.models import ( from pretix.base.models import (
CachedFile, Checkin, Event, InvoiceAddress, Order, User, CachedFile, Checkin, Event, InvoiceAddress, Order, User,
) )
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import mail
from pretix.base.services.tasks import ProfiledEventTask from pretix.base.services.tasks import ProfiledEventTask
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers.format import format_map from pretix.helpers.format import format_map
@@ -61,7 +61,6 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list, recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list,
attachments: list = None, attach_tickets: bool = False, attachments: list = None, attach_tickets: bool = False,
attach_ical: bool = False) -> None: attach_ical: bool = False) -> None:
failures = []
user = User.objects.get(pk=user) if user else None user = User.objects.get(pk=user) if user else None
subject = LazyI18nString(subject) subject = LazyI18nString(subject)
message = LazyI18nString(message) message = LazyI18nString(message)
@@ -121,70 +120,64 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
if subevents_to and p.subevent.date_from >= subevents_to: if subevents_to and p.subevent.date_from >= subevents_to:
continue continue
try:
with language(o.locale, event.settings.region):
email_context = get_email_context(event=event, order=o, invoice_address=ia, position=p)
mail(
p.attendee_email,
subject,
message,
email_context,
event,
locale=o.locale,
order=o,
position=p,
attach_tickets=attach_tickets,
attach_ical=attach_ical,
attach_cached_files=attachments
)
o.log_action(
'pretix.plugins.sendmail.order.email.sent.attendee',
user=user,
data={
'position': p.positionid,
'subject': format_map(subject.localize(o.locale), email_context),
'message': format_map(message.localize(o.locale), email_context),
'recipient': p.attendee_email,
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': [],
'attach_cached_files': attachments_for_log,
}
)
except SendMailException:
failures.append(p.attendee_email)
if send_to_order and o.email:
try:
with language(o.locale, event.settings.region): with language(o.locale, event.settings.region):
email_context = get_email_context(event=event, order=o, invoice_address=ia) email_context = get_email_context(event=event, order=o, invoice_address=ia, position=p)
mail( mail(
o.email, p.attendee_email,
subject, subject,
message, message,
email_context, email_context,
event, event,
locale=o.locale, locale=o.locale,
order=o, order=o,
position=p,
attach_tickets=attach_tickets, attach_tickets=attach_tickets,
attach_ical=attach_ical, attach_ical=attach_ical,
attach_cached_files=attachments, attach_cached_files=attachments
) )
o.log_action( o.log_action(
'pretix.plugins.sendmail.order.email.sent', 'pretix.plugins.sendmail.order.email.sent.attendee',
user=user, user=user,
data={ data={
'position': p.positionid,
'subject': format_map(subject.localize(o.locale), email_context), 'subject': format_map(subject.localize(o.locale), email_context),
'message': format_map(message.localize(o.locale), email_context), 'message': format_map(message.localize(o.locale), email_context),
'recipient': o.email, 'recipient': p.attendee_email,
'attach_tickets': attach_tickets, 'attach_tickets': attach_tickets,
'attach_ical': attach_ical, 'attach_ical': attach_ical,
'attach_other_files': [], 'attach_other_files': [],
'attach_cached_files': attachments_for_log, 'attach_cached_files': attachments_for_log,
} }
) )
except SendMailException:
failures.append(o.email) if send_to_order and o.email:
with language(o.locale, event.settings.region):
email_context = get_email_context(event=event, order=o, invoice_address=ia)
mail(
o.email,
subject,
message,
email_context,
event,
locale=o.locale,
order=o,
attach_tickets=attach_tickets,
attach_ical=attach_ical,
attach_cached_files=attachments,
)
o.log_action(
'pretix.plugins.sendmail.order.email.sent',
user=user,
data={
'subject': format_map(subject.localize(o.locale), email_context),
'message': format_map(message.localize(o.locale), email_context),
'recipient': o.email,
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': [],
'attach_cached_files': attachments_for_log,
}
)
for chunk in _chunks(objects, 1000): for chunk in _chunks(objects, 1000):
orders = Order.objects.filter(pk__in=chunk, event=event) orders = Order.objects.filter(pk__in=chunk, event=event)
-4
View File
@@ -71,7 +71,6 @@ from pretix.base.payment import (
BasePaymentProvider, PaymentException, WalletQueries, BasePaymentProvider, PaymentException, WalletQueries,
) )
from pretix.base.plugins import get_all_plugins from pretix.base.plugins import get_all_plugins
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox from pretix.base.settings import SettingsSandbox
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from pretix.helpers.countries import CachedCountries from pretix.helpers.countries import CachedCountries
@@ -1000,9 +999,6 @@ class StripeMethod(BasePaymentProvider):
payment.confirm() payment.confirm()
except Quota.QuotaExceededException as e: except Quota.QuotaExceededException as e:
raise PaymentException(str(e)) raise PaymentException(str(e))
except SendMailException:
raise PaymentException(_('There was an error sending the confirmation mail.'))
elif intent.status == 'processing': elif intent.status == 'processing':
if request: if request:
messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the ' messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '
-5
View File
@@ -1654,11 +1654,6 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
order = Order.objects.get(id=value) order = Order.objects.get(id=value)
return self.get_order_url(order) return self.get_order_url(order)
def get_error_message(self, exception):
if exception.__class__.__name__ == 'SendMailException':
return _('There was an error sending the confirmation mail. Please try again later.')
return super().get_error_message(exception)
def get_error_url(self): def get_error_url(self):
return self.get_step_url(self.request) return self.get_step_url(self.request)
+1
View File
@@ -179,6 +179,7 @@ def _default_context(request):
ctx['html_page_header'] = "".join(h for h in _html_page_header if h) ctx['html_page_header'] = "".join(h for h in _html_page_header if h)
ctx['footer'] = _footer ctx['footer'] = _footer
ctx['site_url'] = settings.SITE_URL ctx['site_url'] = settings.SITE_URL
ctx['request_get_items'] = request.GET.items()
ctx['js_datetime_format'] = get_javascript_format_without_seconds('DATETIME_INPUT_FORMATS') ctx['js_datetime_format'] = get_javascript_format_without_seconds('DATETIME_INPUT_FORMATS')
ctx['js_date_format'] = get_javascript_format_without_seconds('DATE_INPUT_FORMATS') ctx['js_date_format'] = get_javascript_format_without_seconds('DATE_INPUT_FORMATS')
@@ -42,7 +42,7 @@
{% endif %} {% endif %}
<div id="invoice" class="profile-scope" <div id="invoice" class="profile-scope"
data-profiles-id="addresses_json" data-profiles-id="addresses_json"
data-address-information-url="{% url "js_helpers.address_form" %}?invoice=true&organizer={{ event.organizer.slug|urlencode }}&event={{ event.slug|urlencode }}"> data-address-information-url="{% url "js_helpers.address_form" %}?invoice=true&amp;organizer={{ event.organizer.slug|urlencode }}&amp;event={{ event.slug|urlencode }}&amp;locale={{ request.LANGUAGE_CODE }}">
<div class="panel-body"> <div class="panel-body">
{% if addresses_data %} {% if addresses_data %}
<div class="form-group profile-select-container js-do-not-copy-answers"> <div class="form-group profile-select-container js-do-not-copy-answers">
@@ -372,6 +372,19 @@
</article> </article>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if c.items_missing %}
<div class="product-appendix-row">
<p class="text-muted">
{% trans "There are products selected in this add-on category that currently cannot be changed because they are not on sale:" %}
</p>
<ul class="text-muted">
{% for itemvar, count in c.items_missing.items %}
<li>{{ count }}x {{ itemvar.0 }}{% if itemvar.1 %} {{ itemvar.1 }}{% endif %}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</fieldset> </fieldset>
{% endwith %} {% endwith %}
{% empty %} {% empty %}
@@ -493,7 +493,18 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if cart.show_rounding_info %}
<div class="text-muted">
<small>
{% icon "info-circle" %}
{% blocktrans trimmed %}
Since you entered a business address, your price was computed from the
VAT-exclusive price. Due to rounding, this caused a small change to the
total price.
{% endblocktrans %}
</small>
</div>
{% endif %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if not cart.is_ordered %} {% if not cart.is_ordered %}
@@ -15,7 +15,7 @@
</li> </li>
<li class="text-center"> <li class="text-center">
<form class="form-inline" method="get" id="monthselform" action="{% eventurl event "presale:event.index" cart_namespace=cart_namespace %}"> <form class="form-inline" method="get" id="monthselform" action="{% eventurl event "presale:event.index" cart_namespace=cart_namespace %}">
{% for f, v in request.GET.items %} {% for f, v in request_get_items %}
{% if f != "date" %} {% if f != "date" %}
<input type="hidden" name="{{ f }}" value="{{ v }}"> <input type="hidden" name="{{ f }}" value="{{ v }}">
{% endif %} {% endif %}
@@ -15,7 +15,7 @@
</li> </li>
<li class="text-center"> <li class="text-center">
<form class="form-inline" method="get" id="monthselform" action="{% eventurl event "presale:event.index" cart_namespace=cart_namespace %}"> <form class="form-inline" method="get" id="monthselform" action="{% eventurl event "presale:event.index" cart_namespace=cart_namespace %}">
{% for f, v in request.GET.items %} {% for f, v in request_get_items %}
{% if f != "date" %} {% if f != "date" %}
<input type="hidden" name="{{ f }}" value="{{ v }}"> <input type="hidden" name="{{ f }}" value="{{ v }}">
{% endif %} {% endif %}
@@ -304,7 +304,7 @@
<dt>{% trans "Phone number" %}</dt> <dt>{% trans "Phone number" %}</dt>
<dd>{{ order.phone|phone_format }}</dd> <dd>{{ order.phone|phone_format }}</dd>
{% endif %} {% endif %}
{% if invoice_address_asked %} {% if invoice_address_asked and order.invoice_address.is_business %}
<dt>{% trans "Company" %}</dt> <dt>{% trans "Company" %}</dt>
<dd>{{ order.invoice_address.company }}</dd> <dd>{{ order.invoice_address.company }}</dd>
{% endif %} {% endif %}
@@ -36,7 +36,7 @@
</h4> </h4>
</summary> </summary>
<div id="invoice" class="panel-collapse" <div id="invoice" class="panel-collapse"
data-address-information-url="{% url "js_helpers.address_form" %}?invoice=true&organizer={{ event.organizer.slug|urlencode }}&event={{ event.slug|urlencode }}"> data-address-information-url="{% url "js_helpers.address_form" %}?invoice=true&amp;organizer={{ event.organizer.slug|urlencode }}&amp;event={{ event.slug|urlencode }}&amp;locale={{ request.LANGUAGE_CODE }}">
<div class="panel-body"> <div class="panel-body">
{% if event.settings.invoice_address_explanation_text %} {% if event.settings.invoice_address_explanation_text %}
<div> <div>
@@ -1,14 +1,14 @@
{% extends "pretixpresale/event/base.html" %} {% extends "pretixpresale/event/base.html" %}
{% load i18n %} {% load i18n %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block title %}{% trans "Resend order links" %}{% endblock %} {% block title %}{% trans "Resend order link" %}{% endblock %}
{% block custom_header %} {% block custom_header %}
{{ block.super }} {{ block.super }}
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h2> <h2>
{% trans "Resend order links" %} {% trans "Resend order link" %}
</h2> </h2>
<p> <p>
{% blocktrans trimmed %} {% blocktrans trimmed %}
@@ -7,7 +7,7 @@
{% if filter_form.fields %} {% if filter_form.fields %}
<form class="event-list-filter-form" method="get" data-save-scrollpos> <form class="event-list-filter-form" method="get" data-save-scrollpos>
<input type="hidden" name="filtered" value="1"> <input type="hidden" name="filtered" value="1">
{% for f, v in request.GET.items %} {% for f, v in request_get_items %}
{% if f not in filter_form.fields and f != "page" %} {% if f not in filter_form.fields and f != "page" %}
<input type="hidden" name="{{ f }}" value="{{ v }}"> <input type="hidden" name="{{ f }}" value="{{ v }}">
{% endif %} {% endif %}

Some files were not shown because too many files have changed in this diff Show More