Compare commits

...

37 Commits

Author SHA1 Message Date
Raphael Michel
77380214a8 Model-based mail queuing 2025-09-04 16:09:07 +02:00
Raphael Michel
ad8ed599dc Fix a source of test flakiness 2025-09-02 16:54:28 +02:00
luelista
4c2efa0a97 Use different log action types per log_target for mail errors (Z#23204190) (#5422) 2025-09-02 15:37:44 +02:00
dependabot[bot]
6efcd4b983 Bump @babel/preset-env in /src/pretix/static/npm_dir (#5419)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.28.0 to 7.28.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.3/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-version: 7.28.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 14:50:54 +02:00
dependabot[bot]
c29b7f28f1 Bump @babel/core from 7.28.0 to 7.28.3 in /src/pretix/static/npm_dir (#5423)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.28.0 to 7.28.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.3/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 7.28.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 14:11:52 +02:00
Martin Gross
871a8a2620 Chore: Update requests to >= 2.32.* (Fixes #4180) 2025-09-02 13:38:41 +02:00
Richard Schreiber
b7803565d6 Fix PayPal2 payment creation for free cart (#5415) 2025-09-02 09:53:24 +02:00
Richard Schreiber
f3b6627e63 Fix handling zero-duration events in organizer day-calendar (#5414) 2025-09-02 09:51:05 +02:00
Renne Rocha
574513550d Translations: Update Portuguese (Brazil)
Currently translated at 91.0% (5525 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2025-09-02 09:23:02 +02:00
z3rrry
f145d447a2 Translations: Update Korean
Currently translated at 50.8% (3087 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ko/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
72b9b49b9d Translations: Update Spanish
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
6d20d0e840 Translations: Update Spanish
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2025-09-02 09:23:02 +02:00
patch-works-be
4a662a1aa1 Translations: Update French
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
8213b09847 Translations: Update French
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2025-09-02 09:23:02 +02:00
Yasunobu YesNo Kawaguchi
c54f776b39 Translations: Update Japanese
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
fdd03536f2 Translations: Update Spanish
Currently translated at 97.1% (5894 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/es/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
44303a0030 Translations: Update French
Currently translated at 100.0% (252 of 252 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/fr/

powered by weblate
2025-09-02 09:23:02 +02:00
Renne Rocha
5ba10416ce Translations: Update Portuguese (Brazil)
Currently translated at 90.7% (5507 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
efa117c836 Translations: Update French
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2025-09-02 09:23:02 +02:00
Yasunobu YesNo Kawaguchi
70cd2265db Translations: Update Japanese
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2025-09-02 09:23:02 +02:00
Renne Rocha
b5afbfa1bf Translations: Update Portuguese (Brazil)
Currently translated at 100.0% (252 of 252 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/pt_BR/

powered by weblate
2025-09-02 09:23:02 +02:00
Renne Rocha
2dffe0e2c8 Translations: Update Portuguese (Brazil)
Currently translated at 88.8% (5392 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
df0e0f9115 Translations: Update French
Currently translated at 99.2% (6023 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2025-09-02 09:23:02 +02:00
Mira
2fc47c5d71 Translations: Update French
Currently translated at 99.2% (6023 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2025-09-02 09:23:02 +02:00
CVZ-es
c23d2e5504 Translations: Update French
Currently translated at 98.3% (5965 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/fr/

powered by weblate
2025-09-02 09:23:02 +02:00
Mie Frydensbjerg
58c7e3d316 Translations: Update Danish
Currently translated at 46.1% (2802 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/da/

powered by weblate
2025-09-02 09:23:02 +02:00
Yasunobu YesNo Kawaguchi
2d5c3fbea6 Translations: Update Japanese
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2025-09-02 09:23:02 +02:00
Jan Van Haver
222851620e Translations: Update Dutch
Currently translated at 97.0% (5888 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2025-09-02 09:23:02 +02:00
Jan Van Haver
9ac772b2f3 Translations: Update Dutch
Currently translated at 96.8% (5876 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/nl/

powered by weblate
2025-09-02 09:23:02 +02:00
Yasunobu YesNo Kawaguchi
1408f31ec5 Translations: Update Japanese
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2025-09-02 09:23:02 +02:00
Yasunobu YesNo Kawaguchi
04f32284a8 Translations: Update Japanese
Currently translated at 100.0% (252 of 252 strings)

Translation: pretix/pretix (JavaScript parts)
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix-js/ja/

powered by weblate
2025-09-02 09:23:02 +02:00
Yasunobu YesNo Kawaguchi
318b80c3a5 Translations: Update Japanese
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2025-09-02 09:23:02 +02:00
Luca Sorace \"Stranck
102d172942 Translations: Update Italian
Currently translated at 37.0% (2251 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/it/

powered by weblate
2025-09-02 09:23:02 +02:00
Yasunobu YesNo Kawaguchi
c084698821 Translations: Update Japanese
Currently translated at 98.3% (5968 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2025-09-02 09:23:02 +02:00
Raphael Michel
edffe5c9dd Translations: Update German (informal) (de_Informal)
Currently translated at 100.0% (6068 of 6068 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/de_Informal/

powered by weblate
2025-09-02 09:23:02 +02:00
Richard Schreiber
09e9273a57 Fix unhandled not found error when manually managing sync jobs (#5412)
* Fix unhandled not found error when manually managing sync jobs

* Improve info text (suggestions from code review)

Co-authored-by: luelista <weller@rami.io>

---------

Co-authored-by: luelista <weller@rami.io>
2025-09-02 09:22:47 +02:00
Richard Schreiber
24ac588119 Remove unnecessary translation for daterange (Z#23205453) (#5410)
* Remove unnecessary translation for daterange

* fix flake8

* fix isort
2025-09-02 09:22:21 +02:00
49 changed files with 3696 additions and 4398 deletions

View File

@@ -90,7 +90,7 @@ dependencies = [
"qrcode==8.2",
"redis==6.4.*",
"reportlab==4.4.*",
"requests==2.31.*",
"requests==2.32.*",
"sentry-sdk==2.35.*",
"sepaxml==2.6.*",
"stripe==7.9.*",

View File

@@ -49,7 +49,7 @@ from pretix.base.plugins import (
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
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.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri
@@ -363,24 +363,21 @@ class TeamInviteSerializer(serializers.ModelSerializer):
)
def _send_invite(self, instance):
try:
mail(
instance.email,
_('pretix account invitation'),
'pretixcontrol/email/invitation.txt',
{
'user': self,
'organizer': self.context['organizer'].name,
'team': instance.team.name,
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
event=None,
locale=get_language_without_region() # TODO: expose?
)
except SendMailException:
pass # Already logged
mail(
instance.email,
_('pretix account invitation'),
'pretixcontrol/email/invitation.txt',
{
'user': self,
'organizer': self.context['organizer'].name,
'team': instance.team.name,
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
event=None,
locale=get_language_without_region() # TODO: expose?
)
def create(self, validated_data):
if 'email' in validated_data:

View File

@@ -90,7 +90,6 @@ from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
regenerate_invoice, transmit_invoice,
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderChangeManager, OrderError, _order_placed_email,
_order_placed_email_attendee, approve_order, cancel_order, deny_order,
@@ -438,8 +437,6 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
return self.retrieve(request, [], **kwargs)
return Response(
@@ -633,10 +630,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
order = self.get_object()
if not order.email:
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)
except SendMailException:
return Response({'detail': _('There was an error sending the mail. Please try again later.')}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
order.resend_link(user=self.request.user, auth=self.request.auth)
return Response(
status=status.HTTP_204_NO_CONTENT
@@ -1609,8 +1603,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
)
except Quota.QuotaExceededException:
pass
except SendMailException:
pass
serializer = OrderPaymentSerializer(r, context=serializer.context)
@@ -1648,8 +1640,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except PaymentException as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except SendMailException:
pass
return self.retrieve(request, [], **kwargs)
@action(detail=True, methods=['POST'])

View File

@@ -33,7 +33,7 @@ from pretix.base.invoicing.transmission import (
transmission_types,
)
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
@@ -133,41 +133,37 @@ class EmailTransmissionProvider(TransmissionProvider):
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)
try:
# Do not set to completed because that is done by the email sending task
subject = format_map(subject, context)
email_content = render_mail(template, context)
mail(
[recipient],
subject,
template,
context=context,
event=invoice.order.event,
locale=invoice.order.locale,
order=invoice.order,
invoices=[invoice],
attach_tickets=False,
auto_email=True,
attach_ical=False,
plain_text_only=True,
no_order_links=True,
)
except SendMailException:
raise
else:
invoice.order.log_action(
'pretix.event.order.email.invoice',
user=None,
auth=None,
data={
'subject': subject,
'message': email_content,
'position': None,
'recipient': recipient,
'invoices': [invoice.pk],
'attach_tickets': False,
'attach_ical': False,
'attach_other_files': [],
'attach_cached_files': [],
}
)
# Do not set to completed because that is done by the email sending task
subject = format_map(subject, context)
email_content = render_mail(template, context)
mail(
[recipient],
subject,
template,
context=context,
event=invoice.order.event,
locale=invoice.order.locale,
order=invoice.order,
invoices=[invoice],
attach_tickets=False,
auto_email=True,
attach_ical=False,
plain_text_only=True,
no_order_links=True,
)
invoice.order.log_action(
'pretix.event.order.email.invoice',
user=None,
auth=None,
data={
'subject': subject,
'message': email_content,
'position': None,
'recipient': recipient,
'invoices': [invoice.pk],
'attach_tickets': False,
'attach_ical': False,
'attach_other_files': [],
'attach_cached_files': [],
}
)

View File

@@ -0,0 +1,112 @@
# Generated by Django 4.2.17 on 2025-09-04 12:35
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0288_invoice_transmission"),
]
operations = [
migrations.CreateModel(
name="OutgoingMail",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("status", models.CharField(default="queued", max_length=200)),
("created", models.DateTimeField(auto_now_add=True)),
("sent", models.DateTimeField(blank=True, null=True)),
("error", models.TextField(null=True)),
("error_detail", models.TextField(null=True)),
("subject", models.TextField()),
("body_plain", models.TextField()),
("body_html", models.TextField()),
("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)),
("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=django.db.models.deletion.SET_NULL,
related_name="outgoing_mails",
to="pretixbase.customer",
),
),
(
"event",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="outgoing_mails",
to="pretixbase.event",
),
),
(
"order",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="outgoing_mails",
to="pretixbase.order",
),
),
(
"orderposition",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
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.SET_NULL,
related_name="outgoing_mails",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ("-created",),
},
),
]

View File

@@ -40,6 +40,7 @@ from .items import (
SubEventItem, SubEventItemVariation, itempicture_upload_to,
)
from .log import LogEntry
from .mail import OutgoingMail
from .media import ReusableMedium
from .memberships import Membership, MembershipType
from .notifications import NotificationSetting

View File

@@ -331,27 +331,24 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
return self.email
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):
msg = '- ' + '\n- '.join(str(m) for m in messages)
with language(self.locale):
msg = '- ' + '\n- '.join(str(m) for m in messages)
mail(
email or self.email,
_('Account information changed'),
'pretixcontrol/email/security_notice.txt',
{
'user': self,
'messages': msg,
'url': build_absolute_uri('control:user.settings')
},
event=None,
user=self,
locale=self.locale
)
except SendMailException:
pass # Already logged
mail(
email or self.email,
_('Account information changed'),
'pretixcontrol/email/security_notice.txt',
{
'user': self,
'messages': msg,
'url': build_absolute_uri('control:user.settings')
},
event=None,
user=self,
locale=self.locale
)
def send_password_reset(self):
from pretix.base.services.mail import mail

View File

@@ -0,0 +1,149 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io 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/>.
#
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
class OutgoingMail(models.Model):
STATUS_QUEUED = "queued"
STATUS_INFLIGHT = "inflight"
STATUS_AWAWITING_RETRY = "awaiting_retry"
STATUS_FAILED = "failed"
STATUS_SENT = "sent"
STATUS_CHOICES = (
(STATUS_QUEUED, _("queued")),
(STATUS_INFLIGHT, _("being sent")),
(STATUS_AWAWITING_RETRY, _("awaiting retry")),
(STATUS_FAILED, _("failed")),
(STATUS_SENT, _("sent")),
)
status = models.CharField(max_length=200, choices=STATUS_CHOICES, default=STATUS_QUEUED)
created = models.DateTimeField(auto_now_add=True)
sent = models.DateTimeField(null=True, blank=True)
error = models.TextField(null=True, blank=True)
error_detail = models.TextField(null=True, blank=True)
organizer = models.ForeignKey(
'pretixbase.Organizer',
on_delete=models.CASCADE,
related_name='outgoing_mails',
null=True, blank=True,
)
event = models.ForeignKey(
'pretixbase.Event',
on_delete=models.SET_NULL, # todo think, only for non-queued!
related_name='outgoing_mails',
null=True, blank=True,
)
order = models.ForeignKey(
'pretixbase.Order',
on_delete=models.SET_NULL,
related_name='outgoing_mails',
null=True, blank=True,
)
orderposition = models.ForeignKey(
'pretixbase.OrderPosition',
on_delete=models.SET_NULL,
related_name='outgoing_mails',
null=True, blank=True,
)
customer = models.ForeignKey(
'pretixbase.Customer',
on_delete=models.SET_NULL,
related_name='outgoing_mails',
null=True, blank=True,
)
user = models.ForeignKey(
'pretixbase.User',
on_delete=models.SET_NULL,
related_name='outgoing_mails',
null=True, blank=True,
)
subject = models.TextField()
body_plain = models.TextField()
body_html = models.TextField()
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)
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)
should_attach_cached_files = models.ManyToManyField(
'pretixbase.CachedFile',
related_name='outgoing_mails',
)
should_attach_other_files = models.JSONField(default=list)
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
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
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

View File

@@ -1162,9 +1162,7 @@ class Order(LockModel, LoggedModel):
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.
"""
from pretix.base.services.mail import (
SendMailException, mail, render_mail,
)
from pretix.base.services.mail import mail, render_mail
if not self.email and not (position and position.attendee_email):
return
@@ -1174,35 +1172,31 @@ class Order(LockModel, LoggedModel):
if position and position.attendee_email:
recipient = position.attendee_email
try:
email_content = render_mail(template, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event, self.locale, self, headers=headers, sender=sender,
invoices=invoices, attach_tickets=attach_tickets,
position=position, auto_email=auto_email, attach_ical=attach_ical,
attach_other_files=attach_other_files, attach_cached_files=attach_cached_files,
)
except SendMailException:
raise
else:
self.log_action(
log_entry_type,
user=user,
auth=auth,
data={
'subject': subject,
'message': email_content,
'position': position.positionid if position else None,
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else [],
'attach_tickets': attach_tickets,
'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 [],
}
)
email_content = render_mail(template, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event, self.locale, self, headers=headers, sender=sender,
invoices=invoices, attach_tickets=attach_tickets,
position=position, auto_email=auto_email, attach_ical=attach_ical,
attach_other_files=attach_other_files, attach_cached_files=attach_cached_files,
)
self.log_action(
log_entry_type,
user=user,
auth=auth,
data={
'subject': subject,
'message': email_content,
'position': position.positionid if position else None,
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else [],
'attach_tickets': attach_tickets,
'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):
with language(self.locale, self.event.settings.region):
@@ -1984,40 +1978,30 @@ class OrderPayment(models.Model):
transmit_invoice.apply_async(args=(self.order.event_id, invoice.pk, False))
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):
email_template = self.order.event.settings.mail_text_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)
try:
position.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_paid', user,
invoices=[],
attach_tickets=True,
attach_ical=self.order.event.settings.mail_attach_ical
)
except SendMailException:
logger.exception('Order paid email could not be sent')
position.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_paid', user,
invoices=[],
attach_tickets=True,
attach_ical=self.order.event.settings.mail_attach_ical
)
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):
email_template = self.order.event.settings.mail_text_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)
try:
self.order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_paid', user,
invoices=[invoice] if invoice else [],
attach_tickets=True,
attach_ical=self.order.event.settings.mail_attach_ical
)
except SendMailException:
logger.exception('Order paid email could not be sent')
self.order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_paid', user,
invoices=[invoice] if invoice else [],
attach_tickets=True,
attach_ical=self.order.event.settings.mail_attach_ical
)
@property
def refunded_amount(self):
@@ -2835,45 +2819,39 @@ class OrderPosition(AbstractPosition):
:param attach_tickets: Attach tickets of this order, if they are existing and ready to download
:param attach_ical: Attach relevant ICS files
"""
from pretix.base.services.mail import (
SendMailException, mail, render_mail,
)
from pretix.base.services.mail import mail, render_mail
if not self.attendee_email:
return
with language(self.order.locale, self.order.event.settings.region):
recipient = self.attendee_email
try:
email_content = render_mail(template, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
position=self,
invoices=invoices,
attach_tickets=attach_tickets,
attach_ical=attach_ical,
attach_other_files=attach_other_files,
)
except SendMailException:
raise
else:
self.order.log_action(
log_entry_type,
user=user,
auth=auth,
data={
'subject': subject,
'message': email_content,
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else [],
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': attach_other_files,
'attach_cached_files': [],
}
)
email_content = render_mail(template, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
position=self,
invoices=invoices,
attach_tickets=attach_tickets,
attach_ical=attach_ical,
attach_other_files=attach_other_files,
)
self.order.log_action(
log_entry_type,
user=user,
auth=auth,
data={
'subject': subject,
'message': email_content,
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else [],
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': attach_other_files,
'attach_cached_files': [],
}
)
def resend_link(self, user=None, auth=None):

View File

@@ -34,7 +34,7 @@ from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.email import get_email_context
from pretix.base.i18n import language
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 ...helpers.format import format_map
from ...helpers.names import build_name
@@ -265,34 +265,30 @@ class WaitingListEntry(LoggedModel):
with language(self.locale, self.event.settings.region):
recipient = self.email
try:
email_content = render_mail(template, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event,
self.locale,
headers=headers,
sender=sender,
auto_email=auto_email,
attach_other_files=attach_other_files,
attach_cached_files=attach_cached_files,
)
except SendMailException:
raise
else:
self.log_action(
log_entry_type,
user=user,
auth=auth,
data={
'subject': subject,
'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 [],
}
)
email_content = render_mail(template, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event,
self.locale,
headers=headers,
sender=sender,
auto_email=auto_email,
attach_other_files=attach_other_files,
attach_cached_files=attach_cached_files,
)
self.log_action(
log_entry_type,
user=user,
auth=auth,
data={
'subject': subject,
'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
def clean_itemvar(event, item, variation):

View File

@@ -35,7 +35,7 @@ from pretix.base.models import (
SubEvent, TaxRule, User, WaitingListEntry,
)
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 (
OrderChangeManager, OrderError, _cancel_order, _try_auto_refund,
)
@@ -51,17 +51,14 @@ logger = logging.getLogger(__name__)
def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent):
with language(wle.locale, wle.event.settings.region):
email_context = get_email_context(event_or_subevent=subevent or wle.event, event=wle.event)
try:
mail(
wle.email,
format_map(subject, email_context),
message,
email_context,
wle.event,
locale=wle.locale
)
except SendMailException:
logger.exception('Waiting list canceled email could not be sent')
mail(
wle.email,
format_map(subject, email_context),
message,
email_context,
wle.event,
locale=wle.locale
)
def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent,
@@ -75,14 +72,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,
order=order, position_or_address=ia, event=order.event)
real_subject = format_map(subject, email_context)
try:
order.send_mail(
real_subject, message, email_context,
'pretix.event.order.email.event_canceled',
user,
)
except SendMailException:
logger.exception('Order canceled email could not be sent')
order.send_mail(
real_subject, message, email_context,
'pretix.event.order.email.event_canceled',
user,
)
for p in positions:
if subevent and p.subevent_id != subevent.id:
@@ -95,15 +89,12 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
refund_amount=refund_amount,
position_or_address=p,
order=order, position=p)
try:
order.send_mail(
real_subject, message, email_context,
'pretix.event.order.email.event_canceled',
position=p,
user=user
)
except SendMailException:
logger.exception('Order canceled email could not be sent to attendee')
order.send_mail(
real_subject, message, email_context,
'pretix.event.order.email.event_canceled',
position=p,
user=user
)
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))

View File

@@ -42,7 +42,7 @@ import smtplib
import warnings
from email.mime.image import MIMEImage
from email.utils import formataddr
from typing import Any, Dict, List, Sequence, Union
from typing import Any, Dict, Sequence, Union
from urllib.parse import urljoin, urlparse
from zoneinfo import ZoneInfo
@@ -52,16 +52,13 @@ from celery import chain
from celery.exceptions import MaxRetriesExceededError
from django.conf import settings
from django.core.files.storage import default_storage
from django.core.mail import (
EmailMultiAlternatives, SafeMIMEMultipart, get_connection,
)
from django.core.mail import EmailMultiAlternatives, SafeMIMEMultipart
from django.core.mail.message import SafeMIMEText
from django.db import transaction
from django.template.loader import get_template
from django.utils.html import escape
from django.utils.timezone import now, override
from django.utils.translation import gettext as _, pgettext
from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString
from text_unidecode import unidecode
@@ -69,13 +66,15 @@ from pretix.base.email import ClassicMailRenderer
from pretix.base.i18n import language
from pretix.base.models import (
CachedFile, Customer, Event, Invoice, InvoiceAddress, Order, OrderPosition,
Organizer, User,
Organizer,
)
from pretix.base.models.mail import OutgoingMail
from pretix.base.services.invoices import invoice_pdf_task
from pretix.base.services.tasks import TransactionAwareTask
from pretix.base.services.tickets import get_tickets_for_order
from pretix.base.signals import email_filter, global_email_filter
from pretix.celery_app import app
from pretix.helpers import OF_SELF
from pretix.helpers.format import SafeFormatter, format_map
from pretix.helpers.hierarkey import clean_filename
from pretix.multidomain.urlreverse import build_absolute_uri
@@ -92,6 +91,9 @@ class TolerantDict(dict):
class SendMailException(Exception):
"""
Deprecated, not thrown any more.
"""
pass
@@ -203,161 +205,119 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
if no_order_links and not plain_text_only:
raise ValueError('If you set no_order_links, you also need to set plain_text_only.')
settings_holder = event or organizer
headers = headers or {}
if auto_email:
headers['X-Auto-Response-Suppress'] = 'OOF, NRN, AutoReply, RN'
headers['Auto-Submitted'] = 'auto-generated'
headers.setdefault('X-Mailer', 'pretix')
bcc = list(bcc or [])
if settings_holder and settings_holder.settings.mail_bcc:
for bcc_mail in settings_holder.settings.mail_bcc.split(','):
bcc.append(bcc_mail.strip())
if (settings_holder
and settings_holder.settings.mail_from in (settings.DEFAULT_FROM_EMAIL, settings.MAIL_FROM_ORGANIZERS)
and settings_holder.settings.contact_mail and not headers.get('Reply-To')):
headers['Reply-To'] = settings_holder.settings.contact_mail
if settings_holder:
timezone = settings_holder.timezone
elif user:
timezone = ZoneInfo(user.timezone)
else:
timezone = ZoneInfo(settings.TIME_ZONE)
if event and attach_tickets and not event.settings.mail_attach_tickets:
attach_tickets = False
with language(locale):
if isinstance(context, dict) and order:
try:
context.update({
'invoice_name': order.invoice_address.name,
'invoice_company': order.invoice_address.company
})
except InvoiceAddress.DoesNotExist:
context.update({
'invoice_name': '',
'invoice_company': ''
})
renderer = ClassicMailRenderer(None, organizer)
content_plain = body_plain = render_mail(template, context)
subject = str(subject).format_map(TolerantDict(context))
sender = (
sender or
(event.settings.get('mail_from') if event else None) or
(organizer.settings.get('mail_from') if organizer else None) or
settings.MAIL_FROM
)
if event:
sender_name = clean_sender_name(event.settings.mail_from_name or str(event.name))
sender = formataddr((sender_name, sender))
elif organizer:
sender_name = clean_sender_name(organizer.settings.mail_from_name or str(organizer.name))
sender = formataddr((sender_name, sender))
else:
sender = formataddr((clean_sender_name(settings.PRETIX_INSTANCE_NAME), sender))
subject = raw_subject = str(subject).replace('\n', ' ').replace('\r', '')[:900]
signature = ""
bcc = list(bcc or [])
settings_holder = event or organizer
if event:
timezone = event.timezone
elif user:
timezone = ZoneInfo(user.timezone)
elif organizer:
timezone = organizer.timezone
else:
timezone = ZoneInfo(settings.TIME_ZONE)
_autoextend_context(context, order)
# Build raw content
content_plain = render_mail(template, context)
if settings_holder:
if settings_holder.settings.mail_bcc:
for bcc_mail in settings_holder.settings.mail_bcc.split(','):
bcc.append(bcc_mail.strip())
if settings_holder.settings.mail_from in (settings.DEFAULT_FROM_EMAIL, settings.MAIL_FROM_ORGANIZERS) \
and settings_holder.settings.contact_mail and not headers.get('Reply-To'):
headers['Reply-To'] = settings_holder.settings.contact_mail
subject = prefix_subject(settings_holder, subject)
body_plain += "\r\n\r\n-- \r\n"
signature = str(settings_holder.settings.get('mail_text_signature'))
if signature:
signature = signature.format(event=event.name if event else '')
body_plain += signature
body_plain += "\r\n\r\n-- \r\n"
if event:
renderer = event.get_html_mail_renderer()
if order and order.testmode:
subject = "[TESTMODE] " + subject
if order and position and not no_order_links:
body_plain += _(
"You are receiving this email because someone placed an order for {event} for you."
).format(event=event.name)
body_plain += "\r\n"
body_plain += _(
"You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format(
event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order.position', kwargs={
'order': order.code,
'secret': position.web_secret,
'position': position.positionid,
}
)
)
elif order and not no_order_links:
body_plain += _(
"You are receiving this email because you placed an order for {event}."
).format(event=event.name)
body_plain += "\r\n"
body_plain += _(
"You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format(
event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order.open', kwargs={
'order': order.code,
'secret': order.secret,
'hash': order.email_confirm_secret()
}
)
)
body_plain += "\r\n"
with override(timezone):
try:
if plain_text_only:
body_html = None
elif 'context' in inspect.signature(renderer.render).parameters:
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
elif 'position' in inspect.signature(renderer.render).parameters:
# Backwards compatibility
warnings.warn('Email renderer called without context argument because context argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
else:
# Backwards compatibility
warnings.warn('Email renderer called without position argument because position argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order)
except:
logger.exception('Could not render HTML body')
body_html = None
else:
signature = ""
# Build full plain-text body
body_plain = _wrap_plain_body(content_plain, signature, event, order, position, no_order_links)
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
send_task = mail_send_task.si(
# Build subject
subject = str(subject).format_map(TolerantDict(context))
subject = raw_subject = subject.replace('\n', ' ').replace('\r', '')[:900]
if settings_holder:
subject = prefix_subject(settings_holder, subject)
if (order and order.testmode) or (not order and event and event.testmode):
subject = "[TESTMODE] " + subject
# Build sender
sender = _full_sender(sender, event, organizer)
# Build HTML body
if plain_text_only:
body_html = None
else:
if event:
renderer = event.get_html_mail_renderer()
else:
renderer = ClassicMailRenderer(None, organizer)
with override(timezone):
try:
if 'context' in inspect.signature(renderer.render).parameters:
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
elif 'position' in inspect.signature(renderer.render).parameters:
# Backwards compatibility
warnings.warn('Email renderer called without context argument because context argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
else:
# Backwards compatibility
warnings.warn('Email renderer called without position argument because position argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order)
except:
logger.exception('Could not render HTML body')
body_html = None
m = OutgoingMail.objects.create(
organizer=organizer,
event=event,
order=order,
orderposition=position,
customer=customer,
user=user,
to=[email] if isinstance(email, str) else list(email),
cc=cc,
bcc=bcc,
cc=cc or [],
bcc=bcc or [],
subject=subject,
body=body_plain,
html=body_html,
body_plain=body_plain,
body_html=body_html,
sender=sender,
event=event.id if event else None,
headers=headers,
invoices=[i.pk for i in invoices] if invoices and not position else [],
order=order.pk if order else None,
position=position.pk if position else None,
attach_tickets=attach_tickets,
attach_ical=attach_ical,
user=user.pk if user else None,
organizer=organizer.pk if organizer else None,
customer=customer.pk if customer else None,
attach_cached_files=[(cf.id if isinstance(cf, CachedFile) else cf) for cf in attach_cached_files] if attach_cached_files else [],
attach_other_files=attach_other_files,
should_attach_tickets=attach_tickets,
should_attach_ical=attach_ical,
should_attach_other_files=attach_other_files or [],
)
if invoices and not position:
m.should_attach_invoices.add(*invoices)
if attach_cached_files:
for cf in attach_cached_files:
if not isinstance(cf, CachedFile):
m.should_attach_cached_files.add(CachedFile.objects.get(pk=cf))
else:
m.should_attach_cached_files.add(cf)
send_task = mail_send_task.si(
outgoing_mail=m.id
)
if invoices:
@@ -392,169 +352,177 @@ class CustomEmail(EmailMultiAlternatives):
@app.task(base=TransactionAwareTask, bind=True, acks_late=True)
def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: str, sender: str,
event: int = None, position: int = None, headers: dict = None, cc: List[str] = None, bcc: List[str] = None,
invoices: List[int] = None, order: int = None, attach_tickets=False, user=None,
organizer=None, customer=None, attach_ical=False, attach_cached_files: List[int] = None,
attach_other_files: List[str] = None) -> bool:
email = CustomEmail(subject, body, sender, to=to, cc=cc, bcc=bcc, headers=headers)
if html is not None:
def mail_send_task(self, *args, outgoing_mail: int) -> bool:
with transaction.atomic():
outgoing_mail = OutgoingMail.objects.select_for_update(of=OF_SELF).get(pk=outgoing_mail)
if outgoing_mail.status == OutgoingMail.STATUS_INFLIGHT:
logger.info("Ignoring job for inflight email")
return False
elif outgoing_mail.status in (OutgoingMail.STATUS_SENT, OutgoingMail.STATUS_FAILED):
logger.info(f"Ignoring job for email in final state {outgoing_mail.status}")
return False
outgoing_mail.status = OutgoingMail.STATUS_INFLIGHT
outgoing_mail.save(update_fields=["status"])
email = CustomEmail(
subject=outgoing_mail.subject,
body=outgoing_mail.body_plain,
from_email=outgoing_mail.sender,
to=outgoing_mail.to,
cc=outgoing_mail.cc,
bcc=outgoing_mail.bcc,
headers=outgoing_mail.headers,
)
# Rewrite all <img> tags from real URLs or data URLs to inline attachments refered to by content ID
if outgoing_mail.body_html is not None:
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
html_with_cid, cid_images = replace_images_with_cid_paths(html)
html_with_cid, cid_images = replace_images_with_cid_paths(outgoing_mail.body_html)
html_message.attach(SafeMIMEText(html_with_cid, 'html', settings.DEFAULT_CHARSET))
attach_cid_images(html_message, cid_images, verify_ssl=True)
email.attach_alternative(html_message, "multipart/related")
if user:
user = User.objects.get(pk=user)
log_target, error_log_action_type = outgoing_mail.log_parameters()
invoices_attached = []
actual_attachments = []
if event:
with scopes_disabled():
event = Event.objects.get(id=event)
organizer = event.organizer
backend = event.get_mail_backend()
cm = lambda: scope(organizer=event.organizer) # noqa
elif organizer:
with scopes_disabled():
organizer = Organizer.objects.get(id=organizer)
backend = organizer.get_mail_backend()
cm = lambda: scope(organizer=organizer) # noqa
else:
backend = get_connection(fail_silently=False)
cm = lambda: scopes_disabled() # noqa
with cm():
if customer:
customer = Customer.objects.get(pk=customer)
log_target = user or customer
if event:
if order:
try:
order = event.orders.get(pk=order)
log_target = order
except Order.DoesNotExist:
order = None
else:
with language(order.locale, event.settings.region):
if not event.settings.mail_attach_tickets:
attach_tickets = False
if position:
try:
position = order.positions.get(pk=position)
except OrderPosition.DoesNotExist:
attach_tickets = False
if attach_tickets:
args = []
attach_size = 0
for name, ct in get_tickets_for_order(order, base_position=position):
try:
content = ct.file.read()
args.append((name, content, ct.type))
attach_size += len(content)
except:
# This sometimes fails e.g. with FileNotFoundError. We haven't been able to figure out
# why (probably some race condition with ticket cache invalidation?), so retry later.
try:
self.retry(max_retries=5, countdown=60)
except MaxRetriesExceededError:
# Well then, something is really wrong, let's send it without attachment before we
# don't sent at all
logger.exception('Could not attach invoice to email')
pass
if attach_size < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1:
# Do not attach more than (limit - 1 MB) in tickets (1MB space for invoice, email itself, …),
# it will bounce way to often.
for a in args:
try:
email.attach(*a)
except:
pass
else:
order.log_action(
'pretix.event.order.email.attachments.skipped',
data={
'subject': 'Attachments skipped',
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
'recipient': '',
'invoices': [],
}
)
if attach_ical:
fname = re.sub('[^a-zA-Z0-9 ]', '-', unidecode(pgettext('attachment_filename', 'Calendar invite')))
for i, cal in enumerate(get_private_icals(event, [position] if position else order.positions.all())):
email.attach('{}{}.ics'.format(fname, f'-{i + 1}' if i > 0 else ''), cal.serialize(), 'text/calendar')
email = email_filter.send_chained(event, 'message', message=email, order=order, user=user)
invoices_sent = []
if invoices:
invoices = Invoice.objects.filter(pk__in=invoices)
for inv in invoices:
if inv.file:
with outgoing_mail.scope_manager():
# Attach tickets
if outgoing_mail.should_attach_tickets and outgoing_mail.order:
with language(outgoing_mail.order.locale, outgoing_mail.event.settings.region):
args = []
attach_size = 0
for name, ct in get_tickets_for_order(outgoing_mail.order, base_position=outgoing_mail.orderposition):
try:
# We try to give the invoice a more human-readable name, e.g. "Invoice_ABC-123.pdf" instead of
# just "ABC-123.pdf", but we only do so if our currently selected language allows to do this
# as ASCII text. For example, we would not want a "فاتورة_" prefix for our filename since this
# has shown to cause deliverability problems of the email and deliverability wins.
with language(order.locale if order else inv.locale, event.settings.region if event else None):
filename = pgettext('invoice', 'Invoice {num}').format(num=inv.number).replace(' ', '_') + '.pdf'
if not re.match("^[a-zA-Z0-9-_%./,&:# ]+$", filename):
filename = inv.number.replace(' ', '_') + '.pdf'
filename = re.sub("[^a-zA-Z0-9-_.]+", "_", filename)
with language(inv.order.locale):
email.attach(
filename,
inv.file.file.read(),
'application/pdf'
)
invoices_sent.append(inv)
content = ct.file.read()
args.append((name, content, ct.type))
attach_size += len(content)
except:
logger.exception('Could not attach invoice to email')
pass
# This sometimes fails e.g. with FileNotFoundError. We haven't been able to figure out
# why (probably some race condition with ticket cache invalidation?), so retry later.
try:
self.retry(max_retries=5, countdown=60)
except MaxRetriesExceededError:
# Well then, something is really wrong, let's send it without attachment before we
# don't sent at all
logger.exception('Could not attach invoice to email')
pass
if attach_other_files:
for fname in attach_other_files:
ftype, _ = mimetypes.guess_type(fname)
data = default_storage.open(fname).read()
if attach_size < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1:
# Do not attach more than (limit - 1 MB) in tickets (1MB space for invoice, email itself, …),
# it will bounce way to often.
for a in args:
try:
email.attach(*a)
except:
pass
else:
outgoing_mail.order.log_action(
'pretix.event.order.email.attachments.skipped',
data={
'subject': 'Attachments skipped',
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
'recipient': '',
'invoices': [],
}
)
# Attach calendar files
if outgoing_mail.should_attach_ical and outgoing_mail.order:
fname = re.sub('[^a-zA-Z0-9 ]', '-', unidecode(pgettext('attachment_filename', 'Calendar invite')))
icals = get_private_icals(
outgoing_mail.event,
[outgoing_mail.orderposition] if outgoing_mail.orderposition else outgoing_mail.order.positions.all()
)
for i, cal in enumerate(icals):
name = '{}{}.ics'.format(fname, f'-{i + 1}' if i > 0 else '')
content = cal.serialize()
mimetype = 'text/calendar'
email.attach(name, content, mimetype)
for inv in outgoing_mail.should_attach_invoices.all():
if inv.file:
try:
# We try to give the invoice a more human-readable name, e.g. "Invoice_ABC-123.pdf" instead of
# just "ABC-123.pdf", but we only do so if our currently selected language allows to do this
# as ASCII text. For example, we would not want a "فاتورة_" prefix for our filename since this
# has shown to cause deliverability problems of the email and deliverability wins.
with language(outgoing_mail.order.locale if outgoing_mail.order else inv.locale, outgoing_mail.event.settings.region):
filename = pgettext('invoice', 'Invoice {num}').format(num=inv.number).replace(' ', '_') + '.pdf'
if not re.match("^[a-zA-Z0-9-_%./,&:# ]+$", filename):
filename = inv.number.replace(' ', '_') + '.pdf'
filename = re.sub("[^a-zA-Z0-9-_.]+", "_", filename)
content = inv.file.file.read()
with language(inv.order.locale):
email.attach(
filename,
content,
'application/pdf'
)
invoices_attached.append(inv)
except:
logger.exception('Could not attach invoice to email')
pass
for fname in outgoing_mail.should_attach_other_files:
ftype, _ = mimetypes.guess_type(fname)
data = default_storage.open(fname).read()
try:
email.attach(
clean_filename(os.path.basename(fname)),
data,
ftype
)
except:
logger.exception('Could not attach file to email')
pass
for cf in outgoing_mail.should_attach_cached_files.all():
if cf.file:
try:
email.attach(
clean_filename(os.path.basename(fname)),
data,
ftype
cf.filename,
cf.file.file.read(),
cf.type,
)
except:
logger.exception('Could not attach file to email')
pass
if attach_cached_files:
for cf in CachedFile.objects.filter(id__in=attach_cached_files):
if cf.file:
try:
email.attach(
cf.filename,
cf.file.file.read(),
cf.type,
)
except:
logger.exception('Could not attach file to email')
pass
if outgoing_mail.event:
with outgoing_mail.scope_manager():
email = email_filter.send_chained(
outgoing_mail.event, 'message', message=email, order=outgoing_mail.order, user=outgoing_mail.user
)
email = global_email_filter.send_chained(event, 'message', message=email, user=user, order=order,
organizer=organizer, customer=customer)
email = global_email_filter.send_chained(
outgoing_mail.event, 'message', message=email, user=outgoing_mail.user, order=outgoing_mail.order,
organizer=outgoing_mail.organizer, customer=outgoing_mail.customer
)
outgoing_mail.actual_attachments = [
{
"name": a[0],
"size": len(a[1]),
"type": a[2],
} for a in email.attachments
]
backend = outgoing_mail.get_mail_backend()
try:
backend.send_messages([email])
except (smtplib.SMTPResponseException, smtplib.SMTPSenderRefused) as e:
if e.smtp_code in (101, 111, 421, 422, 431, 432, 442, 447, 452):
if e.smtp_code == 432 and settings.HAS_REDIS:
# This is likely Microsoft Exchange Online which has a pretty bad rate limit of max. 3 concurrent
# SMTP connections which is *easily* exceeded with many celery threads. Just retrying with exponential
# backoff won't be good enough if we have a lot of emails, instead we'll need to make sure our retry
# intervals scatter such that the email won't all be retried at the same time again and cause the
# same problem.
# See also https://docs.microsoft.com/en-us/exchange/troubleshoot/send-emails/smtp-submission-improvements
except Exception as e:
logger.exception('Error sending email')
retry_strategy = _retry_strategy(e)
err, err_detail = _format_error(e)
outgoing_mail.error = err
outgoing_mail.error_detail = err_detail
outgoing_mail.sent = now()
# Run retries
try:
if retry_strategy == "microsoft_concurrency" and settings.HAS_REDIS:
from django_redis import get_redis_connection
redis_key = "pretix_mail_retry_" + hashlib.sha1(f"{getattr(backend, 'username', '_')}@{getattr(backend, 'host', '_')}".encode()).hexdigest()
@@ -564,100 +532,59 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
max_retries = 10
retry_after = min(30 + cnt * 10, 1800)
else:
# Most likely some other kind of temporary failure, retry again (but pretty soon)
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
self.retry(max_retries=max_retries, countdown=retry_after) # throws RetryException, ends function flow
elif retry_strategy in ("microsoft_concurrency", "quick"):
max_retries = 5
retry_after = [10, 30, 60, 300, 900, 900][self.request.retries]
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
self.retry(max_retries=max_retries, countdown=retry_after) # throws RetryException, ends function flow
try:
self.retry(max_retries=max_retries, countdown=retry_after)
except MaxRetriesExceededError:
if log_target:
log_target.log_action(
'pretix.email.error',
data={
'subject': 'SMTP code {}, max retries exceeded'.format(e.smtp_code),
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
'recipient': '',
'invoices': [],
}
)
raise e
elif retry_strategy == "slow":
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
self.retry(max_retries=5, countdown=[60, 300, 600, 1200, 1800, 1800][self.request.retries]) # throws RetryException, ends function flow
logger.exception('Error sending email')
except MaxRetriesExceededError:
if log_target:
log_target.log_action(
error_log_action_type,
data={
'subject': f'{err} (max retries exceeded)',
'message': err_detail,
'recipient': '',
'invoices': [],
}
)
return False
# If we reach this, it's a non-retryable error
outgoing_mail.status = OutgoingMail.STATUS_FAILED
outgoing_mail.sent = now()
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
if log_target:
log_target.log_action(
'pretix.email.error',
error_log_action_type,
data={
'subject': 'SMTP code {}'.format(e.smtp_code),
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
'subject': err,
'message': err_detail,
'recipient': '',
'invoices': [],
}
)
raise SendMailException('Failed to send an email to {}.'.format(to))
except smtplib.SMTPRecipientsRefused as e:
smtp_codes = [a[0] for a in e.recipients.values()]
if not any(c >= 500 for c in smtp_codes) or any(b'Message is too large' in a[1] for a in e.recipients.values()):
# This is not a permanent failure (mailbox full, service unavailable), retry later, but with large
# intervals. One would think that "Message is too lage" is a permanent failure, but apparently it is not.
# We have documented cases of emails to Microsoft returning the error occasionally and then later
# allowing the very same email.
try:
self.retry(max_retries=5, countdown=[60, 300, 600, 1200, 1800, 1800][self.request.retries])
except MaxRetriesExceededError:
# ignore and go on with logging the error
pass
logger.exception('Error sending email')
if log_target:
message = []
for e, val in e.recipients.items():
message.append(f'{e}: {val[0]} {val[1].decode()}')
log_target.log_action(
'pretix.email.error',
data={
'subject': 'SMTP error',
'message': '\n'.join(message),
'recipient': '',
'invoices': [],
}
)
raise SendMailException('Failed to send an email to {}.'.format(to))
except Exception as e:
if isinstance(e, OSError) and not isinstance(e, smtplib.SMTPNotSupportedError):
try:
self.retry(max_retries=5, countdown=[10, 30, 60, 300, 900, 900][self.request.retries])
except MaxRetriesExceededError:
if log_target:
log_target.log_action(
'pretix.email.error',
data={
'subject': 'Internal error',
'message': f'Max retries exceeded after error "{str(e)}"',
'recipient': '',
'invoices': [],
}
)
raise e
if log_target:
log_target.log_action(
'pretix.email.error',
data={
'subject': 'Internal error',
'message': str(e),
'recipient': '',
'invoices': [],
}
)
logger.exception('Error sending email')
raise SendMailException('Failed to send an email to {}.'.format(to))
return False
else:
for i in invoices_sent:
outgoing_mail.status = OutgoingMail.STATUS_SENT
outgoing_mail.error = None
outgoing_mail.error_detail = None
outgoing_mail.actual_attachments = actual_attachments
outgoing_mail.sent = now()
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent", "actual_attachments"])
for i in invoices_attached:
if i.transmission_type == "email":
# Mark invoice as sent when it was sent to the requested address *either* at the time of invoice
# creation *or* as of right now.
@@ -668,7 +595,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
expected_recipients.append((i.order.invoice_address.transmission_info or {}).get("transmission_email_address") or i.order.email)
except InvoiceAddress.DoesNotExist:
pass
if not any(t in expected_recipients for t in to):
if not any(t in expected_recipients for t in outgoing_mail.to):
continue
if i.transmission_status != Invoice.TRANSMISSION_STATUS_COMPLETED:
i.transmission_date = now()
@@ -677,7 +604,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
i.transmission_info = {
"sent": [
{
"recipients": to,
"recipients": outgoing_mail.to,
"datetime": now().isoformat(),
}
]
@@ -689,7 +616,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
elif i.transmission_provider == "email_pdf":
i.transmission_info["sent"].append(
{
"recipients": to,
"recipients": outgoing_mail.to,
"datetime": now().isoformat(),
}
)
@@ -703,7 +630,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
"transmission_provider": "email_pdf",
"transmission_type": "email",
"data": {
"recipients": [to],
"recipients": outgoing_mail.to,
},
}
)
@@ -826,3 +753,121 @@ def normalize_image_url(url):
else:
url = urljoin(settings.MEDIA_URL, url)
return url
def _autoextend_context(context, order):
try:
context.update({
'invoice_name': order.invoice_address.name,
'invoice_company': order.invoice_address.company
})
except InvoiceAddress.DoesNotExist:
context.update({
'invoice_name': '',
'invoice_company': ''
})
def _full_sender(sender_address, event, organizer):
sender_address = (
sender_address or
(event.settings.get('mail_from') if event else None) or
(organizer.settings.get('mail_from') if organizer else None) or
settings.MAIL_FROM
)
if event:
sender_name = event.settings.mail_from_name or str(event.name)
elif organizer:
sender_name = organizer.settings.mail_from_name or str(organizer.name)
else:
sender_name = settings.PRETIX_INSTANCE_NAME
sender = formataddr((clean_sender_name(sender_name), sender_address))
return sender
def _wrap_plain_body(content_plain, signature, event, order, position, no_order_links):
body_plain = content_plain
body_plain += "\r\n\r\n-- \r\n"
if signature:
signature = signature.format(event=event.name if event else '')
body_plain += signature
body_plain += "\r\n\r\n-- \r\n"
if event and order and position and not no_order_links:
body_plain += _(
"You are receiving this email because someone placed an order for {event} for you."
).format(event=event.name)
body_plain += "\r\n"
body_plain += _(
"You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format(
event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order.position', kwargs={
'order': order.code,
'secret': position.web_secret,
'position': position.positionid,
}
)
)
elif event and order and not no_order_links:
body_plain += _(
"You are receiving this email because you placed an order for {event}."
).format(event=event.name)
body_plain += "\r\n"
body_plain += _(
"You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format(
event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order.open', kwargs={
'order': order.code,
'secret': order.secret,
'hash': order.email_confirm_secret()
}
)
)
body_plain += "\r\n"
return body_plain
def _retry_strategy(e: Exception):
if isinstance(e, (smtplib.SMTPResponseException, smtplib.SMTPSenderRefused)):
if e.smtp_code == 432:
# This is likely Microsoft Exchange Online which has a pretty bad rate limit of max. 3 concurrent
# SMTP connections which is *easily* exceeded with many celery threads. Just retrying with exponential
# backoff won't be good enough if we have a lot of emails, instead we'll need to make sure our retry
# intervals scatter such that the email won't all be retried at the same time again and cause the
# same problem.
# See also https://docs.microsoft.com/en-us/exchange/troubleshoot/send-emails/smtp-submission-improvements
return "microsoft_concurrncy"
if e.smtp_code in (101, 111, 421, 422, 431, 432, 442, 447, 452):
return "quick"
elif isinstance(e, smtplib.SMTPRecipientsRefused):
smtp_codes = [a[0] for a in e.recipients.values()]
if not any(c >= 500 for c in smtp_codes) or any(b'Message is too large' in a[1] for a in e.recipients.values()):
# This is not a permanent failure (mailbox full, service unavailable), retry later, but with large
# intervals. One would think that "Message is too lage" is a permanent failure, but apparently it is not.
# We have documented cases of emails to Microsoft returning the error occasionally and then later
# allowing the very same email.
return "slow"
elif isinstance(e, OSError) and not isinstance(e, smtplib.SMTPNotSupportedError):
# Most likely some other kind of temporary failure, retry again (but pretty soon)
return "quick"
def _format_error(e: Exception):
if isinstance(e, (smtplib.SMTPResponseException, smtplib.SMTPSenderRefused)):
return 'SMTP code {}'.format(e.smtp_code), e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error)
elif isinstance(e, smtplib.SMTPRecipientsRefused):
message = []
for e, val in e.recipients.items():
message.append(f'{e}: {val[0]} {val[1].decode()}')
return 'SMTP recipients refudes', '\n'.join(message)
else:
return 'Internal error', str(e)

View File

@@ -90,7 +90,6 @@ from pretix.base.services.invoices import (
from pretix.base.services.locking import (
LOCK_TRUST_WINDOW, LockTimeoutException, lock_objects,
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.memberships import (
create_membership, validate_memberships_in_order,
)
@@ -421,33 +420,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_context = get_email_context(event=order.event, order=order)
try:
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_approved', user,
attach_tickets=True,
attach_ical=order.event.settings.mail_attach_ical and (
not order.event.settings.mail_attach_ical_paid_only or
order.total == Decimal('0.00') or
order.valid_if_pending
),
invoices=[invoice] if invoice and transmit_invoice_mail else []
)
except SendMailException:
logger.exception('Order approved email could not be sent')
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_approved', user,
attach_tickets=True,
attach_ical=order.event.settings.mail_attach_ical and (
not order.event.settings.mail_attach_ical_paid_only or
order.total == Decimal('0.00') or
order.valid_if_pending
),
invoices=[invoice] if invoice and transmit_invoice_mail else []
)
if email_attendees:
for p in order.positions.all():
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)
try:
p.send_mail(
email_attendee_subject, email_attendee_template, email_attendee_context,
'pretix.event.order.email.order_approved', user,
attach_tickets=True,
)
except SendMailException:
logger.exception('Order approved email could not be sent to attendee')
p.send_mail(
email_attendee_subject, email_attendee_template, email_attendee_context,
'pretix.event.order.email.order_approved', user,
attach_tickets=True,
)
return order.pk
@@ -484,13 +477,10 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None):
email_template = order.event.settings.mail_text_order_denied
email_subject = order.event.settings.mail_subject_order_denied
email_context = get_email_context(event=order.event, order=order, comment=comment)
try:
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_denied', user
)
except SendMailException:
logger.exception('Order denied email could not be sent')
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_denied', user
)
return order.pk
@@ -637,14 +627,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_subject = order.event.settings.mail_subject_order_canceled
email_context = get_email_context(event=order.event, order=order, comment=comment or "")
try:
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_canceled', user,
invoices=transmit_invoices_mail,
)
except SendMailException:
logger.exception('Order canceled email could not be sent')
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_canceled', user,
invoices=transmit_invoices_mail,
)
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING)):
try:
@@ -1099,46 +1086,40 @@ def _order_placed_email(event: Event, order: Order, email_template, subject_temp
log_entry: str, invoice, payments: List[OrderPayment], is_free=False):
email_context = get_email_context(event=event, order=order, payments=payments)
try:
order.send_mail(
subject_template, email_template, email_context,
log_entry,
invoices=[invoice] if invoice else [],
attach_tickets=True,
attach_ical=event.settings.mail_attach_ical and (
not event.settings.mail_attach_ical_paid_only or
is_free or
order.valid_if_pending
),
attach_other_files=[a for a in [
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
] if a],
)
except SendMailException:
logger.exception('Order received email could not be sent')
order.send_mail(
subject_template, email_template, email_context,
log_entry,
invoices=[invoice] if invoice else [],
attach_tickets=True,
attach_ical=event.settings.mail_attach_ical and (
not event.settings.mail_attach_ical_paid_only or
is_free or
order.valid_if_pending
),
attach_other_files=[a for a in [
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
] if a],
)
def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosition, email_template, subject_template,
log_entry: str, is_free=False):
email_context = get_email_context(event=event, order=order, position=position)
try:
position.send_mail(
subject_template, email_template, email_context,
log_entry,
invoices=[],
attach_tickets=True,
attach_ical=event.settings.mail_attach_ical and (
not event.settings.mail_attach_ical_paid_only or
is_free or
order.valid_if_pending
),
attach_other_files=[a for a in [
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
] if a],
)
except SendMailException:
logger.exception('Order received email could not be sent to attendee')
position.send_mail(
subject_template, email_template, email_context,
log_entry,
invoices=[],
attach_tickets=True,
attach_ical=event.settings.mail_attach_ical and (
not event.settings.mail_attach_ical_paid_only or
is_free or
order.valid_if_pending
),
attach_other_files=[a for a in [
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
] if a],
)
def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str],
@@ -1460,13 +1441,10 @@ def send_expiry_warnings(sender, **kwargs):
email_template = settings.mail_text_order_pending_warning
email_subject = settings.mail_subject_order_pending_warning
try:
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.expire_warning_sent'
)
except SendMailException:
logger.exception('Reminder email could not be sent')
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.expire_warning_sent'
)
@receiver(signal=periodic_task)
@@ -1527,14 +1505,11 @@ def send_download_reminders(sender, **kwargs):
email_template = event.settings.mail_text_download_reminder
email_subject = event.settings.mail_subject_download_reminder
email_context = get_email_context(event=event, order=o)
try:
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True
)
except SendMailException:
logger.exception('Reminder email could not be sent')
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True
)
if event.settings.mail_send_download_reminder_attendee:
for p in positions:
@@ -1548,14 +1523,11 @@ def send_download_reminders(sender, **kwargs):
email_template = event.settings.mail_text_download_reminder_attendee
email_subject = event.settings.mail_subject_download_reminder_attendee
email_context = get_email_context(event=event, order=o, position=p)
try:
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True, position=p
)
except SendMailException:
logger.exception('Reminder email could not be sent to attendee')
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True, position=p
)
def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
@@ -1563,13 +1535,10 @@ def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
email_template = order.event.settings.mail_text_order_changed
email_context = get_email_context(event=order.event, order=order)
email_subject = order.event.settings.mail_subject_order_changed
try:
order.send_mail(
email_subject, email_template, email_context,
'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')
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_changed', user, auth=auth, invoices=invoices, attach_tickets=True,
)
class OrderChangeManager:

View File

@@ -48,7 +48,7 @@ from django.utils.translation import gettext_lazy as _
from pretix.base.i18n import language
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.shredder import ShredError
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:
with language(user.locale):
try:
mail(
user.email,
_('Data shredding completed'),
'pretixbase/email/shred_completed.txt',
{
'user': user,
'organizer': event.organizer.name,
'event': str(event.name),
'start_time': date_format(parse(indexdata['time']).astimezone(event.timezone), 'SHORT_DATETIME_FORMAT'),
'shredders': ', '.join([str(s.verbose_name) for s in shredders])
},
event=None,
user=user,
locale=user.locale,
)
except SendMailException:
pass # Already logged
mail(
user.email,
_('Data shredding completed'),
'pretixbase/email/shred_completed.txt',
{
'user': user,
'organizer': event.organizer.name,
'event': str(event.name),
'start_time': date_format(parse(indexdata['time']).astimezone(event.timezone), 'SHORT_DATETIME_FORMAT'),
'shredders': ', '.join([str(s.verbose_name) for s in shredders])
},
event=None,
user=user,
locale=user.locale,
)

View File

@@ -719,6 +719,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.customer.anonymized': _('The account has been disabled and anonymized.'),
'pretix.customer.password.resetrequested': _('A new password has been requested.'),
'pretix.customer.password.set': _('A new password has been set.'),
'pretix.customer.email.error': _('Sending of an email has failed.'),
'pretix.reusable_medium.created': _('The reusable medium has been created.'),
'pretix.reusable_medium.created.auto': _('The reusable medium has been created automatically.'),
'pretix.reusable_medium.changed': _('The reusable medium has been changed.'),
@@ -752,6 +753,7 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.user.anonymized': _('This user has been anonymized.'),
'pretix.user.oauth.authorized': _('The application "{application_name}" has been authorized to access your '
'account.'),
'pretix.user.email.error': _('Sending of an email has failed.'),
'pretix.control.auth.user.forgot_password.mail_sent': _('Password reset mail sent.'),
'pretix.control.auth.user.forgot_password.recovered': _('The password has been reset.'),
'pretix.control.auth.user.forgot_password.denied.repeated': _('A repeated password reset has been denied, as '

View File

@@ -65,7 +65,6 @@ from pretix.base.forms.auth import (
)
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
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.security import handle_login_source
@@ -342,9 +341,6 @@ class Forgot(TemplateView):
except User.DoesNotExist:
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
except SendMailException:
logger.exception('Sending password reset email to \"' + email + '\" failed.')
except RepeatedResetDenied:
pass

View File

@@ -82,27 +82,35 @@ class ControlSyncJob(OrderView):
messages.success(self.request, _('The sync job has been enqueued and will run in the next minutes.'))
elif self.request.POST.get("cancel_job"):
with transaction.atomic():
job = self.order.queued_sync_jobs.select_for_update(of=OF_SELF).get(
pk=self.request.POST.get("cancel_job")
)
if job.in_flight:
messages.warning(self.request, _('The sync job is already in progress.'))
try:
job = self.order.queued_sync_jobs.select_for_update(of=OF_SELF).get(
pk=self.request.POST.get("cancel_job")
)
except OrderSyncQueue.DoesNotExist:
messages.info(self.request, _('The sync job could not be found. It may have been processed in the meantime.'))
else:
job.delete()
messages.success(self.request, _('The sync job has been canceled.'))
if job.in_flight:
messages.warning(self.request, _('The sync job is already in progress.'))
else:
job.delete()
messages.success(self.request, _('The sync job has been canceled.'))
elif self.request.POST.get("run_job_now"):
with transaction.atomic():
job = self.order.queued_sync_jobs.select_for_update(of=OF_SELF).get(
pk=self.request.POST.get("run_job_now")
)
if job.in_flight:
messages.success(self.request, _('The sync job is already in progress.'))
try:
job = self.order.queued_sync_jobs.select_for_update(of=OF_SELF).get(
pk=self.request.POST.get("run_job_now")
)
except OrderSyncQueue.DoesNotExist:
messages.info(self.request, _('The sync job could not be found. It may have been processed in the meantime.'))
else:
job.not_before = now()
job.need_manual_retry = None
job.save()
sync_single.apply_async(args=(job.pk,))
messages.success(self.request, _('The sync job has been set to run as soon as possible.'))
if job.in_flight:
messages.success(self.request, _('The sync job is already in progress.'))
else:
job.not_before = now()
job.need_manual_retry = None
job.save()
sync_single.apply_async(args=(job.pk,))
messages.success(self.request, _('The sync job has been set to run as soon as possible.'))
return redirect(self.get_order_url())

View File

@@ -96,9 +96,7 @@ from pretix.base.services.invoices import (
invoice_qualified, regenerate_invoice, transmit_invoice,
)
from pretix.base.services.locking import LockTimeoutException
from pretix.base.services.mail import (
SendMailException, prefix_subject, render_mail,
)
from pretix.base.services.mail import prefix_subject, render_mail
from pretix.base.services.orders import (
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
extend_order, mark_order_expired, mark_order_refunded,
@@ -1054,10 +1052,6 @@ class OrderPaymentConfirm(OrderView):
messages.error(self.request, str(e))
except PaymentException as 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:
messages.success(self.request, _('The payment has been marked as complete.'))
else:
@@ -1527,9 +1521,6 @@ class OrderTransition(OrderView):
'message': 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:
messages.success(self.request, _('The payment has been created successfully.'))
elif self.order.cancel_allowed() and to == 'c':
@@ -1748,15 +1739,11 @@ class OrderResendLink(OrderView):
permission = 'can_change_orders'
def post(self, *args, **kwargs):
try:
if 'position' in kwargs:
p = get_object_or_404(self.order.positions, pk=kwargs['position'])
p.resend_link(user=self.request.user)
else:
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())
if 'position' in kwargs:
p = get_object_or_404(self.order.positions, pk=kwargs['position'])
p.resend_link(user=self.request.user)
else:
self.order.resend_link(user=self.request.user)
messages.success(self.request, _('The email has been queued to be sent.'))
return redirect(self.get_order_url())
@@ -2399,24 +2386,18 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
}
return self.get(self.request, *self.args, **self.kwargs)
else:
try:
order.send_mail(
form.cleaned_data['subject'], email_template,
email_context, 'pretix.event.order.email.custom_sent',
self.request.user, auto_email=False,
attach_tickets=form.cleaned_data.get('attach_tickets', False),
invoices=form.cleaned_data.get('attach_invoices', []),
attach_other_files=[a for a in [
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 [],
)
messages.success(self.request,
_('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))
)
order.send_mail(
form.cleaned_data['subject'], email_template,
email_context, 'pretix.event.order.email.custom_sent',
self.request.user, auto_email=False,
attach_tickets=form.cleaned_data.get('attach_tickets', False),
invoices=form.cleaned_data.get('attach_invoices', []),
attach_other_files=[a for a in [
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 [],
)
messages.success(self.request,
_('Your message has been queued and will be sent to {}.'.format(order.email)))
return super(OrderSendMail, self).form_valid(form)
def get_success_url(self):
@@ -2469,23 +2450,19 @@ class OrderPositionSendMail(OrderSendMail):
}
return self.get(self.request, *self.args, **self.kwargs)
else:
try:
position.send_mail(
form.cleaned_data['subject'],
email_template,
email_context,
'pretix.event.order.position.email.custom_sent',
self.request.user,
attach_tickets=form.cleaned_data.get('attach_tickets', False),
attach_other_files=[a for a in [
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 [],
)
messages.success(self.request,
_('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)))
position.send_mail(
form.cleaned_data['subject'],
email_template,
email_context,
'pretix.event.order.position.email.custom_sent',
self.request.user,
attach_tickets=form.cleaned_data.get('attach_tickets', False),
attach_other_files=[a for a in [
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 [],
)
messages.success(self.request,
_('Your message has been queued and will be sent to {}.'.format(position.attendee_email)))
return super(OrderSendMail, self).form_valid(form)

View File

@@ -102,7 +102,7 @@ from pretix.base.plugins import (
PLUGIN_LEVEL_ORGANIZER,
)
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.templatetags.rich_text import markdown_compile_email
from pretix.base.views.tasks import AsyncAction
@@ -1036,24 +1036,21 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
return ctx
def _send_invite(self, instance):
try:
mail(
instance.email,
_('pretix account invitation'),
'pretixcontrol/email/invitation.txt',
{
'user': self,
'organizer': self.request.organizer.name,
'team': instance.team.name,
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
event=None,
locale=self.request.LANGUAGE_CODE
)
except SendMailException:
pass # Already logged
mail(
instance.email,
_('pretix account invitation'),
'pretixcontrol/email/invitation.txt',
{
'user': self,
'organizer': self.request.organizer.name,
'team': instance.team.name,
'url': build_global_uri('control:auth.invite', kwargs={
'token': instance.token
})
},
event=None,
locale=self.request.LANGUAGE_CODE
)
@transaction.atomic
def post(self, request, *args, **kwargs):

View File

@@ -41,7 +41,6 @@ from hijack import signals
from pretix.base.auth import get_auth_backends
from pretix.base.models import User
from pretix.base.services.mail import SendMailException
from pretix.control.forms.filter import UserFilterForm
from pretix.control.forms.users import UserEditForm
from pretix.control.permissions import AdministratorPermissionRequiredMixin
@@ -139,11 +138,7 @@ class UserResetView(AdministratorPermissionRequiredMixin, RecentAuthenticationRe
def post(self, request, *args, **kwargs):
self.object = get_object_or_404(User, pk=self.kwargs.get("id"))
try:
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.send_password_reset()
self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent',
user=request.user)

View File

@@ -34,9 +34,7 @@
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import (
get_language, gettext_lazy as _, pgettext_lazy,
)
from django.utils.translation import get_language, pgettext_lazy
from pretix.helpers.templatetags.date_fast import date_fast as _date
@@ -103,7 +101,7 @@ def daterange(df, dt, as_html=False):
until=until,
)
return _("{date_from}{until}{date_to}").format(
return "{date_from}{until}{date_to}".format(
date_from=_date(df, "DATE_FORMAT"),
date_to=_date(dt, "DATE_FORMAT"),
until=until,

View File

@@ -32,7 +32,7 @@ from django_countries.fields import Country
from geoip2.errors import AddressNotFoundError
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.urls import build_absolute_uri
@@ -159,21 +159,18 @@ def handle_login_source(user, request):
})
if user.known_login_sources.count() > 1:
# Do not send on first login or first login after introduction of this feature:
try:
with language(user.locale):
mail(
user.email,
_('Login from new source detected'),
'pretixcontrol/email/login_notice.txt',
{
'source': src,
'country': Country(str(country)).name if country else _('Unknown country'),
'instance': settings.PRETIX_INSTANCE_NAME,
'url': build_absolute_uri('control:user.settings')
},
event=None,
user=user,
locale=user.locale
)
except SendMailException:
pass # Not much we can do
with language(user.locale):
mail(
user.email,
_('Login from new source detected'),
'pretixcontrol/email/login_notice.txt',
{
'source': src,
'country': Country(str(country)).name if country else _('Unknown country'),
'instance': settings.PRETIX_INSTANCE_NAME,
'url': build_absolute_uri('control:user.settings')
},
event=None,
user=user,
locale=user.locale
)

View File

@@ -4,8 +4,8 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-07-21 21:00+0000\n"
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
"PO-Revision-Date: 2025-08-27 22:00+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/"
">\n"
"Language: da\n"
@@ -13,7 +13,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.12.2\n"
"X-Generator: Weblate 5.13\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -33244,14 +33244,12 @@ msgstr "Du skal have en gyldig voucherkode for at bestille dette produkt."
#: pretix/presale/templates/pretixpresale/event/fragment_availability.html:10
#: pretix/presale/templates/pretixpresale/event/fragment_availability.html:14
#, fuzzy
msgid "Not available yet."
msgstr "Ikke tilgængelig"
msgstr "Ikke tilgængelig endnu."
#: pretix/presale/templates/pretixpresale/event/fragment_availability.html:18
#, fuzzy
msgid "Not available any more."
msgstr "Ikke tilgængelig"
msgstr "Ikke tilgængelig længere."
#: pretix/presale/templates/pretixpresale/event/fragment_availability.html:23
#: pretix/presale/templates/pretixpresale/event/fragment_product_list.html:89

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-08-19 17:33+0000\n"
"PO-Revision-Date: 2025-08-22 16:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix/de_Informal/>\n"
@@ -34886,7 +34886,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/order.html:19
#: pretix/presale/templates/pretixpresale/event/order.html:50
msgid "We successfully received your payment. See below for details."
msgstr "Wir haben deine Zahlung erfolgreich erhalten."
msgstr "Wir haben deine Zahlung erfolgreich erhalten. Details siehe unten."
#: pretix/presale/templates/pretixpresale/event/order.html:35
msgid ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:02+0000\n"
"PO-Revision-Date: 2025-05-30 11:06+0000\n"
"PO-Revision-Date: 2025-08-28 23:00+0000\n"
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
"fr/>\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.11.4\n"
"X-Generator: Weblate 5.13\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -551,7 +551,7 @@ msgstr "minutes"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:192
msgid "Duplicate"
msgstr "Doublon"
msgstr "Dupliquer"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-08-19 17:46+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"PO-Revision-Date: 2025-08-23 21:00+0000\n"
"Last-Translator: \"Luca Sorace \\\"Stranck\\\"\" <strdjn@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
"it/>\n"
"Language: it\n"
@@ -89,7 +89,7 @@ msgstr "Greco"
#: pretix/_base_settings.py:104
msgid "Hebrew"
msgstr ""
msgstr "Ebraico"
#: pretix/_base_settings.py:105
msgid "Indonesian"
@@ -145,7 +145,7 @@ msgstr "Spagnolo"
#: pretix/_base_settings.py:118
msgid "Spanish (Latin America)"
msgstr ""
msgstr "Spagnolo (America Latina)"
#: pretix/_base_settings.py:119
msgid "Turkish"
@@ -632,6 +632,8 @@ msgid ""
"Only includes explicit changes to the voucher, not e.g. an increase of the "
"number of redemptions."
msgstr ""
"Include solamente cambiamenti espliciti al voucher, non, ad esempio, "
"l'aumento del numero di riscatti."
#: pretix/api/webhooks.py:421
#, fuzzy
@@ -825,6 +827,8 @@ msgid ""
"Field \"{field_name}\" is not valid for {available_inputs}. Please check "
"your {provider_name} settings."
msgstr ""
"Il campo {field_name} non è valido per {available_inputs}. Per favore, "
"controlla le impostazioni di {provider_name}."
#: pretix/base/datasync/datasync.py:267
#, python-brace-format
@@ -863,7 +867,7 @@ msgstr "Dati del prodotto"
#: pretix/control/templates/pretixcontrol/order/index.html:176
#: pretix/presale/templates/pretixpresale/event/order.html:22
msgid "Order details"
msgstr ""
msgstr "Dettagli dell'ordine"
#: pretix/base/datasync/sourcefields.py:133
#: pretix/base/datasync/sourcefields.py:299
@@ -2910,7 +2914,7 @@ msgstr "Ordini pagati"
#: pretix/base/exporters/orderlist.py:1152 pretix/control/views/item.py:975
msgid "Pending orders"
msgstr "Ordini pendenti"
msgstr "Ordini in attesa"
#: pretix/base/exporters/orderlist.py:1152
msgid "Blocking vouchers"
@@ -3379,13 +3383,12 @@ msgstr ""
"venditore."
#: pretix/base/forms/questions.py:1177
#, fuzzy
msgid "No invoice requested"
msgstr "Cancellazione"
msgstr "Fattura non richiesta"
#: pretix/base/forms/questions.py:1179
msgid "Invoice transmission method"
msgstr ""
msgstr "Metodo per le trasmettere le fatture"
#: pretix/base/forms/questions.py:1324
msgid "You need to provide a company name."
@@ -3400,26 +3403,28 @@ msgid ""
"If you enter an invoice address, you also need to select an invoice "
"transmission method."
msgstr ""
"Se inserisci l'indirizzo della fattura, devi anche specificare come "
"trasmetterla."
#: pretix/base/forms/questions.py:1380
#, fuzzy
#| msgid "The selected media type is not enabled in your organizer settings."
msgid ""
"The selected transmission type is not available in your country or for your "
"type of address."
msgstr ""
"Il tipo di supporto selezionato non è abilitato nelle tue impostazioni da "
"organizzatore."
"Il metodo selezionato per trasmettere la fattura non è disponibile nella "
"nazione specificata nel tuo indirizzo."
#: pretix/base/forms/questions.py:1389
msgid ""
"The selected type of invoice transmission requires a field that is currently "
"not available, please reach out to the organizer."
msgstr ""
"Il metodo selezionato per trasmettere la fattura richiede un campo non "
"disponibile, per favore contatta l'organizzatore."
#: pretix/base/forms/questions.py:1393
msgid "This field is required for the selected type of invoice transmission."
msgstr ""
msgstr "Questo campo è necessario per trasmettere la fattura come selezionato."
#: pretix/base/forms/user.py:51 pretix/control/forms/users.py:43
msgid ""
@@ -3533,7 +3538,7 @@ msgstr "Cliente individuale"
#: pretix/base/invoicing/email.py:50
msgid "Email invoice directly to accounting department"
msgstr ""
msgstr "Invia la fattura alla contabilità"
#: pretix/base/invoicing/email.py:51
#, fuzzy
@@ -3550,7 +3555,7 @@ msgstr "Indirizzo email verificato"
#: pretix/base/invoicing/email.py:91
msgid "PDF via email"
msgstr ""
msgstr "Invia PDF via email"
#: pretix/base/invoicing/national.py:37
msgctxt "italian_invoice"
@@ -3950,7 +3955,7 @@ msgstr "Sono state trovate più date corrispondenti."
#: pretix/base/modelimport_orders.py:73
msgid "Grouping"
msgstr ""
msgstr "Raggruppa"
#: pretix/base/modelimport_orders.py:75
msgid ""
@@ -3958,6 +3963,10 @@ msgid ""
"together...\". Lines with the same grouping value will be put in the same "
"order, but MUST be consecutive lines of the input file."
msgstr ""
"Usabile solamente quando \"Modo di importazione\" è impostato su "
"\"Raggruppa più righe insieme...\". Righe con lo stesso valore di "
"raggruppamento manterranno lo stesso ordine, ma DEVONO essere consecutive "
"nel file in input."
#: pretix/base/modelimport_orders.py:101
msgid "Enter a valid phone number."
@@ -3979,6 +3988,8 @@ msgstr "Devi selezionare una data."
msgid ""
"The product can be specified by its internal ID, full name or internal name."
msgstr ""
"Il prodotto può essere identificato usando il ID interno, nome completo o "
"nome interno."
#: pretix/base/modelimport_orders.py:149
#: pretix/base/modelimport_vouchers.py:194
@@ -3999,6 +4010,7 @@ msgstr "Variante prodotto"
#: pretix/base/modelimport_orders.py:161
msgid "The variation can be specified by its internal ID or full name."
msgstr ""
"La variante può essere identificata usando il suo ID interno o il suo nome."
#: pretix/base/modelimport_orders.py:181
#: pretix/base/modelimport_vouchers.py:225
@@ -4028,7 +4040,7 @@ msgstr "Inserire un codice paese valido."
#: 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."
msgstr ""
msgstr "Lo stato può essere indicato usando il suo nome completo o abbreviato."
#: pretix/base/modelimport_orders.py:300 pretix/base/modelimport_orders.py:450
msgid "States are not supported for this country."
@@ -4085,6 +4097,8 @@ msgid ""
"The sales channel can be specified by it's internal identifier or its full "
"name."
msgstr ""
"Il canale di vendita può essere indicato usando il suo identificatore "
"interno o il suo nome completo."
#: pretix/base/modelimport_orders.py:599 pretix/base/modelimport_orders.py:601
msgid "Please enter a valid sales channel."
@@ -4092,7 +4106,7 @@ msgstr "Inserire un canale di vendita valido."
#: pretix/base/modelimport_orders.py:611
msgid "The seat needs to be specified by its internal ID."
msgstr ""
msgstr "Il posto deve essere indicato usando il suo ID interno."
#: pretix/base/modelimport_orders.py:626
#: pretix/base/modelimport_vouchers.py:291
@@ -4567,19 +4581,20 @@ msgstr "Separa valori multipli con spazi"
#: pretix/base/models/datasync.py:53
msgid "Temporary error, auto-retry limit exceeded"
msgstr ""
msgstr "Errore momentaneo, limite di tentativi automatici superato"
#: pretix/base/models/datasync.py:54
msgid "Provider reported a permanent error"
msgstr ""
msgstr "Il provider ha riportato un errore permanente"
#: pretix/base/models/datasync.py:55
msgid "Misconfiguration, please check provider settings"
msgstr ""
"Configurazione errata, per favore controlla le impostazioni del provider"
#: pretix/base/models/datasync.py:56 pretix/base/models/datasync.py:57
msgid "System error, needs manual intervention"
msgstr ""
msgstr "Errore del sistema, richiesto intervento manuale"
#: pretix/base/models/devices.py:70 pretix/base/models/items.py:1675
msgid "Internal identifier"
@@ -4841,13 +4856,15 @@ msgstr "Opzionale. Nessun prodotto verrà venduto prima di questa data."
#: pretix/base/models/event.py:620
msgid "This event is remote or partially remote."
msgstr ""
msgstr "Questo evento è totalmente o parzialmente da remoto."
#: pretix/base/models/event.py:621
msgid ""
"This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times."
msgstr ""
"Verrà usato per informare gli utenti se l'evento si trova in un fuso orario "
"differente e permette di calcolare l'ora locale dei vari utenti."
#: pretix/base/models/event.py:641 pretix/base/models/organizer.py:97
#: pretix/control/navigation.py:65 pretix/control/navigation.py:499
@@ -5131,14 +5148,12 @@ msgid "Manual transaction"
msgstr "Transazione manuale"
#: pretix/base/models/invoices.py:120
#, fuzzy
#| msgid "Pending amount"
msgid "pending transmission"
msgstr "Ammontare rimanente"
msgstr "trasmissione in attesa"
#: pretix/base/models/invoices.py:121
msgid "currently being transmitted"
msgstr ""
msgstr "trasmissione in corso"
#: pretix/base/models/invoices.py:122
#, fuzzy
@@ -5155,11 +5170,11 @@ msgstr "fallito"
#: pretix/base/models/invoices.py:124
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/rule_list.html:56
msgid "unknown"
msgstr ""
msgstr "sconosciuto"
#: pretix/base/models/invoices.py:125
msgid "not transmitted due to test mode"
msgstr ""
msgstr "non trasmesso perché in modalità di test"
#: pretix/base/models/invoices.py:217
#, python-format
@@ -6113,6 +6128,9 @@ msgid ""
"with changing the type of question without data loss. Consider hiding this "
"question and creating a new one instead."
msgstr ""
"Sono già presenti delle risposte a questa domanda di tipo incompatibile "
"rispetto al nuovo. Il cambio avverrebbe perdendo informazioni. È possibile "
"invece procedere nascondendo questa domanda e creandone un'altra."
#: pretix/base/models/items.py:1961
#: pretix/control/templates/pretixcontrol/items/question.html:90
@@ -8243,7 +8261,7 @@ msgstr "Il tuo file di layout non è un layout valido. Messaggio di errore: {}"
#: pretix/base/plugins.py:136
#: pretix/control/templates/pretixcontrol/event/quick_setup.html:132
msgid "Features"
msgstr ""
msgstr "Funzionalità"
#: pretix/base/plugins.py:138
msgid "Integrations"
@@ -8261,7 +8279,7 @@ msgstr "Formato di esportazione"
#: pretix/base/plugins.py:141
msgid "API features"
msgstr ""
msgstr "Funzionalità dell'API"
#: pretix/base/reldate.py:38
msgid "Event start"
@@ -9147,6 +9165,8 @@ msgid ""
"The grouping \"%(value)s\" occurs on non-consecutive lines (seen again on "
"line %(row)s)."
msgstr ""
"Il raggruppamento \"%(value)s\" è stato trovato su righe non consecutive ("
"visto già su linea %(row)s)."
#: pretix/base/services/modelimport.py:151
#, python-brace-format
@@ -9154,6 +9174,9 @@ msgid ""
"Inconsistent data in row {row}: Column {col} contains value \"{val_line}\", "
"but for this order, the value has already been set to \"{val_order}\"."
msgstr ""
"Dati inconsistenti su riga {row}: La colonna {col} contiene il valore "
"\"{val_line}\", ma sullo stesso ordine è già presente il valore "
"\"{val_order}\"."
#: pretix/base/services/modelimport.py:165
#: pretix/base/services/modelimport.py:277
@@ -9752,19 +9775,19 @@ msgstr "Chiedi il nome dei partecipanti"
#: pretix/base/settings.py:359
msgid "Ask for a name for all personalized tickets."
msgstr ""
msgstr "Richiedi un nome per tutti i biglietti personalizzati."
#: pretix/base/settings.py:368
msgid "Require attendee names"
msgstr ""
msgstr "Richiedi i nomi dei partecipanti"
#: pretix/base/settings.py:369
msgid "Require customers to fill in the names of all attendees."
msgstr ""
msgstr "Obbliga i clienti a riempire i nomi di tutti i partecipanti."
#: pretix/base/settings.py:379
msgid "Ask for email addresses per ticket"
msgstr ""
msgstr "Chiedi l'indirizzo email per i biglietti"
#: pretix/base/settings.py:380
msgid ""
@@ -9777,10 +9800,18 @@ msgid ""
"primary email address, not to the per-attendee addresses. You can however "
"enable this in the email settings."
msgstr ""
"Normalmente Pretix chiede un indirizzo email per ordine, che verrà usato per "
"inviare la conferma dello stesso. Se attivi questa opzione, il sistema "
"chiederà in aggiunta un indirizzo email per ogni biglietto personalizzato. "
"Questo può essere utile per ottenere un indirizzo email per ogni "
"partecipante, anche in caso di ordini di gruppo. In ogni caso, di default "
"Pretix invierà la conferma dell'ordine solamente all'email principale, non "
"ad ogni partecipante. Puoi comunque cambiare questa opzione nelle "
"impostazioni della email."
#: pretix/base/settings.py:394
msgid "Require email addresses per ticket"
msgstr ""
msgstr "Richiedi un indirizzo email per ogni biglietto"
#: pretix/base/settings.py:395
msgid ""
@@ -9788,60 +9819,63 @@ msgid ""
"tickets. See the above option for more details. One email address for the "
"order confirmation will always be required regardless of this setting."
msgstr ""
"Obbliga i clienti ad indicare un indirizzo email per ogni biglietto "
"personalizzato. Guarda l'opzione sopra per maggiori informazioni. Verrà "
"comunque richiesto un indirizzo email per la conferma dell'ordine, a "
"prescindere da questa opzione."
#: pretix/base/settings.py:407
msgid "Ask for company per ticket"
msgstr ""
msgstr "Chiedi l'azienda su ogni biglietto"
#: pretix/base/settings.py:416
msgid "Require company per ticket"
msgstr ""
msgstr "Obbliga a inserire l'azienda su ogni biglietto"
#: pretix/base/settings.py:426
msgid "Ask for postal addresses per ticket"
msgstr ""
msgstr "Chiedi l'indirizzo postale su ogni biglietto"
#: pretix/base/settings.py:435
msgid "Require postal addresses per ticket"
msgstr ""
msgstr "Obbliga ad inserire l'indirizzo postale su ogni biglietto"
#: pretix/base/settings.py:445
msgid "Ask for the order email address twice"
msgstr ""
msgstr "Chiedi di inserire due volte l'indirizzo email"
#: pretix/base/settings.py:446
msgid ""
"Require customers to fill in the primary email address twice to avoid errors."
msgstr ""
msgstr "Obbliga i clienti di inserire due volte l'indirizzo email per conferma."
#: pretix/base/settings.py:455
msgid "Ask for a phone number per order"
msgstr ""
msgstr "Chiedi il numero di telefono"
#: pretix/base/settings.py:464
msgid "Require a phone number per order"
msgstr ""
msgstr "Obbliga l'inserimento del numero di telefono"
#: pretix/base/settings.py:474
msgid "Ask for invoice address"
msgstr ""
msgstr "Chiedi l'indirizzo di fattura"
#: pretix/base/settings.py:483
msgid "Do not ask for invoice address if an order is free"
msgstr ""
msgstr "Non chiedere l'indirizzo di fattura se un ordine è gratuito"
#: pretix/base/settings.py:492
msgid "Require customer name"
msgstr ""
msgstr "Obbliga l'inserimento del nome del cliente"
#: pretix/base/settings.py:501
msgid "Show attendee names on invoices"
msgstr ""
msgstr "Mostra il nome del partecipante sulla fattura"
#: pretix/base/settings.py:510
#, fuzzy
msgid "Show event location on invoices"
msgstr "Prevendita non ancora attiva"
msgstr "Mostra l'indirizzo dell'evento sulla fattura"
#: pretix/base/settings.py:511
msgid ""
@@ -9849,22 +9883,27 @@ msgid ""
"same for all lines. It will be shown on every line if there are different "
"locations."
msgstr ""
"L'indirizzo dell'evento verrà mostrato sotto la lista dei prodotti se è lo "
"stesso per tutti. Se l'evento si svolgerà in luoghi differenti, verrà "
"mostrato sotto ciascuno."
#: pretix/base/settings.py:521
#, fuzzy
msgid "Show exchange rates"
msgstr "Mostra il valore a"
msgstr "Mostra i tassi di cambio"
#: pretix/base/settings.py:524 pretix/base/settings.py:532
#: pretix/control/forms/item.py:626
msgid "Never"
msgstr ""
msgstr "Mai"
#: pretix/base/settings.py:525 pretix/base/settings.py:533
#, fuzzy
msgid ""
"Based on European Central Bank daily rates, whenever the invoice recipient "
"is in an EU country that uses a different currency."
msgstr ""
"In base ai cambio della Banca Centrale Europea, se il destinatario della "
"fattura è in una nazione dell'EU che usa una valuta differente."
#: pretix/base/settings.py:527 pretix/base/settings.py:535
msgid ""
@@ -9874,25 +9913,24 @@ msgstr ""
#: pretix/base/settings.py:545
msgid "Require invoice address"
msgstr ""
msgstr "Obbliga l'inserimento dell'indirizzo di fatturazione"
#: pretix/base/settings.py:555
#, fuzzy
msgid "Require a business address"
msgstr "Crea un nuovo organizzatore"
msgstr "Richiedi un indirizzo aziendale"
#: pretix/base/settings.py:556
msgid "This will require users to enter a company name."
msgstr ""
msgstr "Obbligherà gli utenti ad inserire il nome della propria azienda."
#: pretix/base/settings.py:566
msgid "Ask for beneficiary"
msgstr ""
msgstr "Chiedi il beneficiario"
#: pretix/base/settings.py:576
#, fuzzy
msgid "Custom recipient field label"
msgstr "Campo indirizzo personalizzato"
msgstr "Campo indirizzo del destinatario personalizzato"
#: pretix/base/settings.py:578
msgid ""
@@ -9902,11 +9940,15 @@ msgid ""
"details as well as for displaying the value on the invoice. It will be shown "
"on the invoice below the headline. The field will not be required."
msgstr ""
"Se vuoi aggiungere un campo di testo personalizzato, ad esempio un numero di "
"registrazione specifico per ogni nazione, al tuo modulo per la fattura, "
"inserisci il nome qui. Questo nome verrà usato sia per chiedere agli utenti "
"di inserire i propri dettagli, che per mostrare il valore sulla fattura, "
"sotto il titolo. Questo campo non è obbligatorio."
#: pretix/base/settings.py:591
#, fuzzy
msgid "Custom recipient field help text"
msgstr "Campo indirizzo personalizzato"
msgstr "Testo di aiuto per il campo indirizzo del destinatario personalizzato"
#: pretix/base/settings.py:593
msgid ""
@@ -9914,10 +9956,13 @@ msgid ""
"will be displayed underneath the field. It will not be displayed on the "
"invoice."
msgstr ""
"Se utilizzi il campo indirizzo del destinatario personalizzato, puoi "
"specificare un testo di aiuto che verrà mostrato al di sotto del campo "
"stesso. Non verrà, invece, mostrato sulla fattura."
#: pretix/base/settings.py:603
msgid "Ask for VAT ID"
msgstr ""
msgstr "Chiedi la partita IVA"
#: pretix/base/settings.py:605
#, python-brace-format
@@ -9926,10 +9971,14 @@ msgid ""
"only requested from business customers in the following countries: "
"{countries}"
msgstr ""
"Funzionerà solamente se viene richiesto anche l'indirizzo per la "
"fatturazione. La partita IVA non è mai un campo obbligatorio e verrà "
"richiesta solamente per i clienti aziendali dalle seguenti nazioni: "
"{countries}"
#: pretix/base/settings.py:618
msgid "Invoice address explanation"
msgstr ""
msgstr "Spiegazione dell'indirizzo di fattura"
#: pretix/base/settings.py:621
msgid "This text will be shown above the invoice address form during checkout."
@@ -9940,54 +9989,64 @@ msgstr ""
#: pretix/base/settings.py:630
msgid "Show paid amount on partially paid invoices"
msgstr ""
"Mostra quanto è stato già versato sugli ordini pagati solamente parzialmente"
#: pretix/base/settings.py:631
msgid ""
"If an invoice has already been paid partially, this option will add the paid "
"and pending amount to the invoice."
msgstr ""
"Se una fattura è già stata pagata parzialmente, questa opzione aggiungerà "
"quanto è stato pagato e quanto rimane da pagare alla stessa."
#: pretix/base/settings.py:641
msgid "Show free products on invoices"
msgstr ""
msgstr "Mostra i prodotti gratuiti sulla fattura"
#: pretix/base/settings.py:642
msgid ""
"Note that invoices will never be generated for orders that contain only free "
"products."
msgstr ""
"Nota che non verrà emessa alcuna fattura per gli ordini che contengono "
"solamente prodotti gratuiti."
#: pretix/base/settings.py:652
msgid "Show expiration date of order"
msgstr ""
msgstr "Mostra la data di scadenza degli ordini"
#: pretix/base/settings.py:653
msgid ""
"The expiration date will not be shown if the invoice is generated after the "
"order is paid."
msgstr ""
"La data di scadenza non verrà mostrata se la fattura verrà emessa dopo che "
"l'ordine è stato pagato."
#: pretix/base/settings.py:663
msgid "Minimum length of invoice number after prefix"
msgstr ""
msgstr "Lunghezza minima del numero di fattura, escluso il prefisso"
#: pretix/base/settings.py:664
msgid ""
"The part of your invoice number after your prefix will be filled up with "
"leading zeros up to this length, e.g. INV-001 or INV-00001."
msgstr ""
"La parte del tuo numero di fattura dopo il prefisso verrà riempita con zeri "
"iniziali per raggiungere la lunghezza impostata, ad esempio: INV-001 oppure "
"INV-00001."
#: pretix/base/settings.py:675
msgid "Generate invoices with consecutive numbers"
msgstr ""
msgstr "Genera le fatture seguendo una numerazione consecutiva"
#: pretix/base/settings.py:676
msgid "If deactivated, the order code will be used in the invoice number."
msgstr ""
msgstr "Se disattivata, il codice d'ordine verrà usato come numero di fattura."
#: pretix/base/settings.py:685
msgid "Invoice number prefix"
msgstr ""
msgstr "Prefisso del numero di fattura"
#: pretix/base/settings.py:686
msgid ""
@@ -9999,6 +10058,14 @@ msgid ""
"can use %Y (with century) %y (without century) to insert the year of the "
"invoice, or %m and %d for the day of month."
msgstr ""
"Verrà anteposto ai numeri di fattura. Se non riempi questo campo, verrà "
"usato lo slug dell'evento, seguito da -. Attenzione: se più eventi della "
"stessa organizzazione usano lo stesso valore in questo campo, condivideranno "
"la stessa numerazione: Un singolo numero di fattura verrà usato una ed una "
"sola volta per ogni evento che condivide lo stesso prefisso. Le modifiche a "
"questo campo si applicheranno solamente alle fatture future. Puoi usare %Y ("
"con secolo) %y (senza secolo) per inserire l'anno della fattura, oppure %m e "
"%d per il mese ed il giorno."
#: pretix/base/settings.py:698 pretix/base/settings.py:720
#, python-brace-format
@@ -10007,7 +10074,7 @@ msgstr "Per favore, utilizza soltanto i caratteri {allowed} in questo campo."
#: pretix/base/settings.py:711
msgid "Invoice number prefix for cancellations"
msgstr ""
msgstr "Prefisso per i numeri di fattura per le cancellazioni"
#: pretix/base/settings.py:712
msgid ""
@@ -10015,42 +10082,48 @@ msgid ""
"this field empty, the same numbering scheme will be used that you configured "
"for regular invoices."
msgstr ""
"Verrà anteposto al numero di fatturazione per le cancellazioni. Se lasci "
"questo campo vuoto, verrà utilizzato lo stesso schema di numerazione "
"configurato per le fatture normali."
#: pretix/base/settings.py:733
msgid "Highlight order code to make it stand out visibly"
msgstr ""
msgstr "Evidenzia il codice d'ordine per renderlo più visibile"
#: pretix/base/settings.py:734 pretix/base/settings.py:745
msgid "Only respected by some invoice renderers."
msgstr ""
msgstr "Funziona solamente con alcuni renderizzatori di fatture."
#: pretix/base/settings.py:744 pretix/base/settings.py:2959
#: pretix/control/templates/pretixcontrol/pdf/index.html:436
msgid "Font"
msgstr ""
msgstr "Font"
#: pretix/base/settings.py:770
#, fuzzy
msgid "Length of ticket codes"
msgstr "Codice biglietto"
msgstr "Lunghezza dei codici dei biglietti"
#: pretix/base/settings.py:797
msgid "Reservation period"
msgstr ""
msgstr "Durata del carrello"
#: pretix/base/settings.py:799
msgid ""
"The number of minutes the items in a user's cart are reserved for this user."
msgstr ""
"Numero di minuti per cui un prodotto rimane riservato nel carrello di un "
"utente."
#: pretix/base/settings.py:808
msgid ""
"Directly redirect to check-out after a product has been added to the cart."
msgstr ""
"Reindirizza direttamente al check-out dopo che un prodotto viene aggiunto al "
"carrello."
#: pretix/base/settings.py:817
msgid "End of presale text"
msgstr ""
msgstr "Testo per la fine della prevendita"
#: pretix/base/settings.py:820
msgid ""
@@ -10058,16 +10131,21 @@ msgid ""
"timeframe for this event is over. You can use it to describe other options "
"to get a ticket, such as a box office."
msgstr ""
"Questo testo verrà mostrato sopra lo shop una volta che il termine per la "
"vendita dei biglietti viene raggiunto. Puoi utilizzarlo per descrivere "
"alternative per l'acquisto dei biglietti, come ad esempio un box office."
#: pretix/base/settings.py:834
msgid "Guidance text"
msgstr ""
msgstr "Guida ai pagamenti"
#: pretix/base/settings.py:835
msgid ""
"This text will be shown above the payment options. You can explain the "
"choices to the user here, if you want."
msgstr ""
"Questo testo apparirà sopra le opzioni di pagamento. Puoi usarlo per "
"spiegare le varie opzioni agli utenti, se vuoi."
#: pretix/base/settings.py:846 pretix/base/settings.py:855
msgid "in days"
@@ -10078,19 +10156,21 @@ msgid "in minutes"
msgstr "in minuti"
#: pretix/base/settings.py:851
#, fuzzy
msgid "Set payment term"
msgstr "Nascondi metodo di pagamento"
msgstr "Termine dei pagamenti"
#: pretix/base/settings.py:858
msgid ""
"If using days, the order will expire at the end of the last day. Using "
"minutes is more exact, but should only be used for real-time payment methods."
msgstr ""
"Utilizzando i giorni, l'ordine scadrà alla fine dell'ultimo giorno. Usando "
"invece i minuti, si può essere più precisi, ma questa opzione dovrebbe "
"essere utilizzata solamente per i metodi di pagamento in tempo reale."
#: pretix/base/settings.py:868
msgid "Payment term in days"
msgstr ""
msgstr "Termine di pagamento in giorni"
#: pretix/base/settings.py:875
msgid ""
@@ -10099,10 +10179,15 @@ msgid ""
"recommend 14 days. If you only use real-time payment methods, we recommend "
"still setting two or three days to allow people to retry failed payments."
msgstr ""
"Il numero di giorni in cui un utente può pagare un ordine già fatto per "
"mantenere la propria prenotazione. Se utilizzi metodi di pagamento lenti "
"come i bonifici, raccomandiamo un valore di 14 giorni. Se usi invece metodi "
"di pagamento in tempo reale, raccomandiamo di lasciare agli utenti 2-3 "
"giorni per permettergli di riprovare pagamenti falliti."
#: pretix/base/settings.py:893
msgid "Only end payment terms on weekdays"
msgstr ""
msgstr "Fai cadere il termine dei pagamenti solamente nei giorni feriali"
#: pretix/base/settings.py:894
msgid ""
@@ -10111,11 +10196,14 @@ msgid ""
"some countries by civil law. This will not effect the last date of payments "
"configured below."
msgstr ""
"Se questa opzione è attiva e il termine di pagamento cade di Sabato o "
"Domenica, verrà spostato al Lunedì successivo. Questo è legalmente richiesto "
"in alcune nazioni. Questa opzione non avrà comunque effetto sull'ultima data "
"di pagamento, configurabile sotto."
#: pretix/base/settings.py:910
#, fuzzy
msgid "Payment term in minutes"
msgstr "ID Pagamento"
msgstr "Termine di pagamento in minuti"
#: pretix/base/settings.py:911
msgid ""
@@ -10124,6 +10212,11 @@ msgid ""
"methods. Please note that for technical reasons, the actual time frame might "
"be a few minutes longer before the order is marked as expired."
msgstr ""
"Il numero di minuti in cui un utente può pagare un ordine già fatto per "
"mantenere la propria prenotazione. Usa questa opzione solo se usi "
"esclusivamente metodi di pagamento in tempo reale. Tieni in mente che per "
"ragioni tecniche, il termine potrebbe essere esteso automaticamente di "
"alcuni minuti."
#: pretix/base/settings.py:934
msgid "Last date of payments"
@@ -10135,10 +10228,14 @@ msgid ""
"configured above. If you use the event series feature and an order contains "
"tickets for multiple dates, the earliest date will be used."
msgstr ""
"L'ultimo giorno in cui i pagamenti vengono accettati. Questa opzione ha "
"precedenza rispetto a tutte le altre configurate sopra. Se usi la "
"funzionalità per avere una serie di eventi ed un ordine contiene biglietti "
"per più date, verrà usata la prima."
#: pretix/base/settings.py:946
msgid "Automatically expire unpaid orders"
msgstr ""
msgstr "Fai scadere automaticamente gli ordini non pagati"
#: pretix/base/settings.py:947
msgid ""
@@ -10146,11 +10243,14 @@ msgid ""
"'expired' after the end of their payment deadline. This means that those "
"tickets go back to the pool and can be ordered by other people."
msgstr ""
"Se selezionata, tutti gli ordini non pagati passeranno automaticamente da "
"\"in attesa\" a \"scaduti\", dopo aver raggiunto il termine di pagamento. "
"Questo implica che i biglietti torneranno indietro nella quota e che "
"potranno essere riordinati da altre persone."
#: pretix/base/settings.py:958
#, fuzzy
msgid "Expiration delay"
msgstr "Carrello scaduto"
msgstr "Ritardo nella scadenza"
#: pretix/base/settings.py:959
msgid ""
@@ -10160,6 +10260,12 @@ msgid ""
"beyond the \"last date of payments\" configured above, which is always "
"enforced."
msgstr ""
"L'ordine scadrà davvero solamente dopo i giorni specificati a partire dalla "
"data di scadenza comunicata al cliente. Se selezioni "
"\"Fai cadere il termine dei pagamenti solamente nei giorni feriali\", verrà "
"rispettato anche in questo caso. Tieni in considerazione che il ritardo non "
"scavallerà l'\"ultima data per i pagamenti\" impostata sopra, che verrà "
"sempre rispettata."
#: pretix/base/settings.py:980
msgid "Hide \"payment pending\" state on customer-facing pages"
@@ -14910,7 +15016,7 @@ msgstr ""
#: pretix/control/forms/modelimport.py:76
msgid "Import mode"
msgstr "modo importazione"
msgstr "Modo di importazione"
#: pretix/control/forms/modelimport.py:78
msgid "Create a separate order for each line"

File diff suppressed because it is too large Load Diff

View File

@@ -8,16 +8,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:02+0000\n"
"PO-Revision-Date: 2025-07-23 01:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
"js/ja/>\n"
"PO-Revision-Date: 2025-08-24 16:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/"
"pretix-js/ja/>\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.12.2\n"
"X-Generator: Weblate 5.13\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -209,7 +209,7 @@ msgstr "方向転換"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
msgid "Entry"
msgstr "入口"
msgstr "エントリー"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
msgid "Exit"
@@ -644,7 +644,8 @@ msgstr ""
msgid ""
"Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted."
msgstr ""
msgstr "あなたの色は白に対して十分なコントラストがありません。サイトのアクセシビリテ"
"ィに影響します。"
#: pretix/static/pretixcontrol/js/ui/main.js:417
#: pretix/static/pretixcontrol/js/ui/main.js:437
@@ -719,7 +720,7 @@ msgstr "カートの有効期限が切れています"
#: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire."
msgstr ""
msgstr "カートの有効期限が近づいています。"
#: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute."
@@ -784,12 +785,12 @@ msgstr "数量を増やす"
#: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget"
msgid "Filter events by"
msgstr ""
msgstr "イベントをフィルタ"
#: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget"
msgid "Filter"
msgstr ""
msgstr "フィルタ"
#: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget"
@@ -974,7 +975,7 @@ msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js:57
msgctxt "widget"
msgid "Resume checkout"
msgstr "購入を続行する"
msgstr "チェックアウトを続行する"
#: pretix/static/pretixpresale/js/widget/widget.js:58
msgctxt "widget"
@@ -997,17 +998,14 @@ msgid "Close"
msgstr "閉じる"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#, fuzzy
#| msgctxt "widget"
#| msgid "Resume checkout"
msgctxt "widget"
msgid "Close checkout"
msgstr "購入を続行する"
msgstr "チェックアウトを閉じる"
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr ""
msgstr "この操作はキャンセルできません。読み込みが完了するまでお待ちください。"
#: pretix/static/pretixpresale/js/widget/widget.js:64
msgctxt "widget"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-06-25 06:56+0000\n"
"Last-Translator: 조정화 <junghwa.jo@om.org>\n"
"PO-Revision-Date: 2025-09-01 18:00+0000\n"
"Last-Translator: z3rrry <z3rrry@gmail.com>\n"
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/ko/"
">\n"
"Language: ko\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.11.4\n"
"X-Generator: Weblate 5.13\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -145,7 +145,7 @@ msgstr "스페인어"
#: pretix/_base_settings.py:118
msgid "Spanish (Latin America)"
msgstr ""
msgstr "스페인어 (라틴 아메리카)"
#: pretix/_base_settings.py:119
msgid "Turkish"
@@ -617,7 +617,8 @@ msgstr "바우처 할당"
msgid ""
"Only includes explicit changes to the voucher, not e.g. an increase of the "
"number of redemptions."
msgstr ""
msgstr "쿠폰에 대한 명시적인 변경 사항만 포함하며, 예를 들어 사용 횟수 증가 등은 "
"포함하지 않습니다."
#: pretix/api/webhooks.py:421
#, fuzzy
@@ -729,18 +730,16 @@ msgstr ""
"있습니다."
#: pretix/base/context.py:38
#, fuzzy, python-brace-format
#| msgid "powered by {name} based on <a {a_attr}>pretix</a>"
#, python-brace-format
msgid "<a {a_name_attr}>powered by {name}</a> <a {a_attr}>based on pretix</a>"
msgstr ""
"이 서비스는 {name}에 의해 제공되며 pretix(온라인 이벤트 티켓팅 및 등록 시스"
"템) 시스템 기반으로 작동합니다"
"이 서비스는 <a {a_name_attr}>{name}</a>에 의해 제공되며 <a {a_attr}>pretix</"
"a> 기반으로 작동합니다"
#: pretix/base/context.py:48
#, fuzzy, python-brace-format
#| msgid "powered by {name} based on <a {a_attr}>pretix</a>"
#, python-brace-format
msgid "<a {a_attr}>powered by {name} based on pretix</a>"
msgstr "이 서비스는 {name}에서 제공하며 pretix를 기반으로 합니다."
msgstr "이 서비스는 <a {a_attr}>{name}</a>에서 제공하며 pretix를 기반으로 합니다."
#: pretix/base/context.py:55
#, python-format
@@ -812,13 +811,16 @@ msgid ""
"Field \"{field_name}\" is not valid for {available_inputs}. Please check "
"your {provider_name} settings."
msgstr ""
"필드 \"{field_name}\"은 {available_inputs}에 유효하지 않습니다. "
"{provider_name} 설정을 확인해 주세요."
#: pretix/base/datasync/datasync.py:267
#, python-brace-format
msgid ""
"Please update value mapping for field \"{field_name}\" - option \"{val}\" "
"not assigned"
msgstr ""
msgstr "필드 \"{field_name}\"의 값 매핑을 업데이트해 주세요 - 옵션 \"{val}\"이 "
"할당되지 않았습니다"
#: pretix/base/datasync/sourcefields.py:128
#, fuzzy
@@ -850,7 +852,7 @@ msgstr "상품 데이터"
#: pretix/control/templates/pretixcontrol/order/index.html:176
#: pretix/presale/templates/pretixpresale/event/order.html:22
msgid "Order details"
msgstr ""
msgstr "주문 내역"
#: pretix/base/datasync/sourcefields.py:133
#: pretix/base/datasync/sourcefields.py:299

View File

@@ -7,16 +7,16 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:35+0000\n"
"PO-Revision-Date: 2025-07-06 01:00+0000\n"
"PO-Revision-Date: 2025-08-26 19:00+0000\n"
"Last-Translator: Jan Van Haver <jan.van.haver@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
">\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.11.4\n"
"X-Generator: Weblate 5.13\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -144,7 +144,7 @@ msgstr "Spaans"
#: pretix/_base_settings.py:118
msgid "Spanish (Latin America)"
msgstr ""
msgstr "Spaans (Latijns-Amerika)"
#: pretix/_base_settings.py:119
msgid "Turkish"
@@ -559,22 +559,16 @@ msgid "Event series date deleted"
msgstr "Evenementenreeks: datum verwijderd"
#: pretix/api/webhooks.py:374
#, fuzzy
#| msgid "Product name"
msgid "Product changed"
msgstr "Productnaam"
msgstr "Product is veranderd"
#: pretix/api/webhooks.py:375
#, fuzzy
#| msgid ""
#| "Product changed (including product added or deleted and including changes "
#| "to nested objects like variations or bundles)"
msgid ""
"This includes product added or deleted and changes to nested objects like "
"variations or bundles."
msgstr ""
"Product veranderd (inclusief product toegevoegd of verwijderd en inclusief "
"veranderingen aan geneste objecten zoals variaties of bundels)"
"Dit omvat toegevoegd of verwijderde product en wijzigingen in geneste "
"objecten zoals variaties of bundels."
#: pretix/api/webhooks.py:380
msgid "Shop taken live"
@@ -609,28 +603,24 @@ msgid "Waiting list entry received voucher"
msgstr "Wachtlijstitem heeft voucher ontvangen"
#: pretix/api/webhooks.py:412
#, fuzzy
#| msgid "Voucher code"
msgid "Voucher added"
msgstr "Vouchercode"
msgstr "Voucher toegevoegd"
#: pretix/api/webhooks.py:416
#, fuzzy
#| msgid "Voucher assigned"
msgid "Voucher changed"
msgstr "Voucher toegewezen"
msgstr "Voucher gewijzigd"
#: pretix/api/webhooks.py:417
msgid ""
"Only includes explicit changes to the voucher, not e.g. an increase of the "
"number of redemptions."
msgstr ""
"Omvat alleen expliciete wijzigingen aan de voucher, niet bijvoorbeeld een "
"toename van het aantal inwisselingen."
#: pretix/api/webhooks.py:421
#, fuzzy
#| msgid "Voucher redeemed"
msgid "Voucher deleted"
msgstr "Voucher verzilverd"
msgstr "Voucher verwijderd"
#: pretix/api/webhooks.py:425
msgid "Customer account created"
@@ -874,17 +864,13 @@ msgid "Invoice address"
msgstr "Factuuradres"
#: pretix/base/datasync/sourcefields.py:134
#, fuzzy
#| msgid "Meta information"
msgid "Event information"
msgstr "Meta-informatie"
msgstr "Event-informatie"
#: pretix/base/datasync/sourcefields.py:135
#, fuzzy
#| msgid "Send recovery information"
msgctxt "subevent"
msgid "Event or date information"
msgstr "Stuur herstelinformatie"
msgstr "Event- of datuminformatie"
#: pretix/base/datasync/sourcefields.py:175
#: pretix/base/exporters/orderlist.py:604
@@ -909,10 +895,8 @@ msgstr "Naam van aanwezige"
#: pretix/base/datasync/sourcefields.py:187
#: pretix/base/datasync/sourcefields.py:604
#: pretix/base/datasync/sourcefields.py:628
#, fuzzy
#| msgid "Attendee name"
msgid "Attendee"
msgstr "Naam van aanwezige"
msgstr "Aanwezige"
#: pretix/base/datasync/sourcefields.py:207
#: pretix/base/exporters/orderlist.py:611 pretix/base/forms/questions.py:685
@@ -926,10 +910,8 @@ msgid "Attendee email"
msgstr "E-mailadres van aanwezige"
#: pretix/base/datasync/sourcefields.py:219
#, fuzzy
#| msgid "Attendee email"
msgid "Attendee or order email"
msgstr "E-mailadres van aanwezige"
msgstr "E-mailadres van de aanwezige of de bestelling"
#: pretix/base/datasync/sourcefields.py:232 pretix/base/pdf.py:186
#: pretix/control/templates/pretixcontrol/order/index.html:595
@@ -941,28 +923,20 @@ msgid "Attendee company"
msgstr "Bedrijf aanwezige"
#: pretix/base/datasync/sourcefields.py:241
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address street"
msgstr "Adres van gast"
msgstr "Adres aanwezige: straat"
#: pretix/base/datasync/sourcefields.py:250
#, fuzzy
#| msgid "Attendee ZIP code"
msgid "Attendee address ZIP code"
msgstr "Adres gast: postcode"
msgstr "Adres aanwezige: postcode"
#: pretix/base/datasync/sourcefields.py:259
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address city"
msgstr "Adres van gast"
msgstr "Adres aanwezige: plaats"
#: pretix/base/datasync/sourcefields.py:268
#, fuzzy
#| msgid "Attendee address"
msgid "Attendee address country"
msgstr "Adres van gast"
msgstr "Adres aanwezige: land"
#: pretix/base/datasync/sourcefields.py:279 pretix/base/pdf.py:344
msgid "Invoice address company"
@@ -997,16 +971,12 @@ msgid "Invoice address country"
msgstr "Factuuradres: land"
#: pretix/base/datasync/sourcefields.py:353
#, fuzzy
#| msgid "Order details"
msgid "Order email"
msgstr "Bestellingsdetails"
msgstr "Emailadres bestelling"
#: pretix/base/datasync/sourcefields.py:362
#, fuzzy
#| msgid "Organizer domain"
msgid "Order email domain"
msgstr "Domein van de organisator"
msgstr "E-maildomein van de bestelling"
#: pretix/base/datasync/sourcefields.py:371
#: pretix/base/exporters/invoices.py:201 pretix/base/exporters/invoices.py:328
@@ -1037,10 +1007,8 @@ msgid "Order code"
msgstr "Bestelcode"
#: pretix/base/datasync/sourcefields.py:380
#, fuzzy
#| msgid "End order date"
msgid "Event and order code"
msgstr "Einddatum bestelling"
msgstr "Code van event en bestelling"
#: pretix/base/datasync/sourcefields.py:389
#: pretix/base/exporters/orderlist.py:262 pretix/base/notifications.py:201
@@ -1052,10 +1020,8 @@ msgid "Order total"
msgstr "Totaalbedrag van bestelling"
#: pretix/base/datasync/sourcefields.py:398
#, fuzzy
#| msgid "Product name and variation"
msgid "Product and variation name"
msgstr "Productnaam en variant"
msgstr "Product- en variantnaam"
#: pretix/base/datasync/sourcefields.py:410 pretix/base/exporters/items.py:57
#: pretix/base/exporters/orderlist.py:597

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-19 16:02+0000\n"
"PO-Revision-Date: 2025-02-27 18:00+0000\n"
"PO-Revision-Date: 2025-08-28 13:43+0000\n"
"Last-Translator: Renne Rocha <renne@rocha.dev.br>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix-js/pt_BR/>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.10.1\n"
"X-Generator: Weblate 5.13\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -143,7 +143,7 @@ msgstr "Continuar"
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:317
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:341
msgid "Confirming your payment …"
msgstr "Confirmado o seu pagamento …"
msgstr "Confirmando seu pagamento …"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:254
msgid "Payment method unavailable"
@@ -165,7 +165,7 @@ msgstr "Receita total"
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:15
msgid "Contacting Stripe …"
msgstr "Contactando Stripe …"
msgstr "Contatando Stripe …"
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:72
msgid "Total"
@@ -173,7 +173,7 @@ msgstr "Total"
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:291
msgid "Contacting your bank …"
msgstr "Contactando o seu banco …"
msgstr "Contatando o seu banco …"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
msgid "Select a check-in list"
@@ -217,7 +217,7 @@ msgstr "Saída"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
msgid "Scan a ticket or search and press return…"
msgstr "Escanear um ingresso ou pesquisar e pressionar enter…"
msgstr "Escanear um ingresso ou pesquisar e pressionar Enter…"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
msgid "Load more"
@@ -370,9 +370,9 @@ msgid ""
"If this takes longer than two minutes, please contact us or go back in your "
"browser and try again."
msgstr ""
"Sua solicitação chegou ao nosso servidor mas ainda precisamos esperar ela "
"ser processada. Se isto tomar mais do que dois minutos, por favor entre em "
"contato conosco ou retorne no seu navegador e tente novamente."
"Sua solicitação chegou ao servidor, mas ainda esperamos que ela seja "
"processada. Se isso demorar mais de dois minutos, entre em contato conosco "
"ou volte ao seu navegador e tente novamente."
#: pretix/static/pretixbase/js/asynctask.js:119
#: pretix/static/pretixbase/js/asynctask.js:176
@@ -386,8 +386,8 @@ msgid ""
"We currently cannot reach the server, but we keep trying. Last error code: "
"{code}"
msgstr ""
"Não conseguimos acessar o servidor, mas continuaremos tentando. Último "
"código do erro: {code}"
"No momento, não podemos acessar o servidor, mas continuamos tentando. Último "
"código de erro: {code}"
#: pretix/static/pretixbase/js/asynctask.js:156
#: pretix/static/pretixcontrol/js/ui/mail.js:21
@@ -399,8 +399,8 @@ msgstr "A solicitação demorou muito. Por favor, tente novamente."
msgid ""
"We currently cannot reach the server. Please try again. Error code: {code}"
msgstr ""
"Não conseguimos acessar o servidor. Por favor, tente novamente. Código do "
"erro: {code}"
"No momento, não podemos acessar o servidor. Por favor, tente novamente. "
"Código de erro: {code}"
#: pretix/static/pretixbase/js/asynctask.js:210
msgid "We are processing your request …"
@@ -418,7 +418,7 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:270
msgid "If this takes longer than a few minutes, please contact us."
msgstr ""
msgstr "Se isso demorar mais do que alguns minutos, entre em contato conosco."
#: pretix/static/pretixbase/js/asynctask.js:325
msgid "Close message"
@@ -630,26 +630,24 @@ msgid "Unknown error."
msgstr "Erro desconhecido."
#: pretix/static/pretixcontrol/js/ui/main.js:292
#, fuzzy
#| msgid "Your color has great contrast and is very easy to read!"
msgid "Your color has great contrast and will provide excellent accessibility."
msgstr "Sua cor tem grande contraste e é muito fácil de ler!"
msgstr "Sua cor tem ótimo contraste e proporcionará excelente acessibilidade."
#: pretix/static/pretixcontrol/js/ui/main.js:296
#, fuzzy
#| msgid "Your color has decent contrast and is probably good-enough to read!"
msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility "
"requirements."
msgstr ""
"Sua cor tem um contraste aceitável e provavelmente é boa o suficiente para "
"ler!"
"Sua cor tem contraste decente e é suficiente para requisitos mínimos de "
"acessibilidade."
#: pretix/static/pretixcontrol/js/ui/main.js:300
msgid ""
"Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted."
msgstr ""
"Sua cor não tem contraste suficiente com o branco. A acessibilidade do seu "
"site será afetada."
#: pretix/static/pretixcontrol/js/ui/main.js:417
#: pretix/static/pretixcontrol/js/ui/main.js:437
@@ -693,10 +691,8 @@ msgid "Calculating default price…"
msgstr "Calculando o preço padrão…"
#: pretix/static/pretixcontrol/js/ui/plugins.js:69
#, fuzzy
#| msgid "Search results"
msgid "No results"
msgstr "Resultados da busca"
msgstr "Sem resultados"
#: pretix/static/pretixcontrol/js/ui/question.js:41
msgid "Others"
@@ -727,7 +723,7 @@ msgstr "Carrinho expirado"
#: pretix/static/pretixpresale/js/ui/cart.js:58
#: pretix/static/pretixpresale/js/ui/cart.js:84
msgid "Your cart is about to expire."
msgstr ""
msgstr "Seu carrinho está para expirar."
#: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute."
@@ -737,16 +733,10 @@ msgstr[1] ""
"Os itens em seu carrinho estão reservados para você por {num} minutos."
#: pretix/static/pretixpresale/js/ui/cart.js:83
#, fuzzy
#| msgid "Cart expired"
msgid "Your cart has expired."
msgstr "Carrinho expirado"
msgstr "Seu carrinho expirou."
#: pretix/static/pretixpresale/js/ui/cart.js:86
#, fuzzy
#| msgid ""
#| "The items in your cart are no longer reserved for you. You can still "
#| "complete your order as long as theyre available."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as they're available."
@@ -756,11 +746,11 @@ msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?"
msgstr ""
msgstr "Você quer renovar o seu período de reserva?"
#: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation"
msgstr ""
msgstr "Renovar reserva"
#: pretix/static/pretixpresale/js/ui/main.js:194
msgid "The organizer keeps %(currency)s %(amount)s"
@@ -800,12 +790,12 @@ msgstr "Aumentar quantidade"
#: pretix/static/pretixpresale/js/widget/widget.js:19
msgctxt "widget"
msgid "Filter events by"
msgstr ""
msgstr "Filtrar eventos por"
#: pretix/static/pretixpresale/js/widget/widget.js:20
msgctxt "widget"
msgid "Filter"
msgstr ""
msgstr "Filtro"
#: pretix/static/pretixpresale/js/widget/widget.js:21
msgctxt "widget"
@@ -833,7 +823,7 @@ msgstr "Selecione"
#, javascript-format
msgctxt "widget"
msgid "Select %s"
msgstr "Selecion %s"
msgstr "Selecione %s"
#: pretix/static/pretixpresale/js/widget/widget.js:26
#, javascript-format
@@ -955,12 +945,9 @@ msgid "Open ticket shop"
msgstr "Abrir loja de ingressos"
#: pretix/static/pretixpresale/js/widget/widget.js:50
#, fuzzy
#| msgctxt "widget"
#| msgid "Resume checkout"
msgctxt "widget"
msgid "Checkout"
msgstr "Retomar checkout"
msgstr "Checkout"
#: pretix/static/pretixpresale/js/widget/widget.js:51
msgctxt "widget"
@@ -1017,17 +1004,14 @@ msgid "Close"
msgstr "Fechar"
#: pretix/static/pretixpresale/js/widget/widget.js:62
#, fuzzy
#| msgctxt "widget"
#| msgid "Resume checkout"
msgctxt "widget"
msgid "Close checkout"
msgstr "Retomar checkout"
msgstr "Fechar checkout"
#: pretix/static/pretixpresale/js/widget/widget.js:63
msgctxt "widget"
msgid "You cannot cancel this operation. Please wait for loading to finish."
msgstr ""
msgstr "Você não pode cancelar esta operação. Aguarde o carregamento terminar."
#: pretix/static/pretixpresale/js/widget/widget.js:64
msgctxt "widget"
@@ -1130,31 +1114,31 @@ msgstr "Dom"
#: pretix/static/pretixpresale/js/widget/widget.js:85
msgid "Monday"
msgstr ""
msgstr "Segunda-feira"
#: pretix/static/pretixpresale/js/widget/widget.js:86
msgid "Tuesday"
msgstr ""
msgstr "Terça-feira"
#: pretix/static/pretixpresale/js/widget/widget.js:87
msgid "Wednesday"
msgstr ""
msgstr "Quarta-feira"
#: pretix/static/pretixpresale/js/widget/widget.js:88
msgid "Thursday"
msgstr ""
msgstr "Quinta-feira"
#: pretix/static/pretixpresale/js/widget/widget.js:89
msgid "Friday"
msgstr ""
msgstr "Sexta-feira"
#: pretix/static/pretixpresale/js/widget/widget.js:90
msgid "Saturday"
msgstr ""
msgstr "Sábado"
#: pretix/static/pretixpresale/js/widget/widget.js:91
msgid "Sunday"
msgstr ""
msgstr "Domingo"
#: pretix/static/pretixpresale/js/widget/widget.js:94
msgid "January"

View File

@@ -54,7 +54,6 @@ from pretix.base.models import (
)
from pretix.base.payment import PaymentException
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.tasks import TransactionAwareTask
from pretix.celery_app import app
@@ -70,13 +69,10 @@ def notify_incomplete_payment(o: Order):
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
try:
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.expire_warning_sent'
)
except SendMailException:
logger.exception('Reminder email could not be sent')
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.expire_warning_sent'
)
def cancel_old_payments(order):
@@ -279,9 +275,6 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = N
except Quota.QuotaExceededException:
# payment confirmed but order status could not be set, no longer problem of this plugin
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:
cancel_old_payments(order)

View File

@@ -58,7 +58,6 @@ from localflavor.generic.forms import BICFormField, IBANFormField
from pretix.base.forms.widgets import DatePickerWidget
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.templatetags.money import money_filter
from pretix.control.permissions import (
@@ -160,11 +159,6 @@ class ActionView(View):
p.confirm(user=self.request.user)
except Quota.QuotaExceededException:
pass
except SendMailException:
return JsonResponse({
'status': 'error',
'message': _('Problem sending email.')
})
trans.state = BankTransaction.STATE_VALID
trans.save()
trans.order.payments.filter(

View File

@@ -57,7 +57,6 @@ from pretix.base.decimal import round_decimal
from pretix.base.forms import SecretKeySettingsField
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.plugins.paypal.api import Api
@@ -468,9 +467,6 @@ class Paypal(BasePaymentProvider):
payment_obj.confirm()
except Quota.QuotaExceededException as e:
raise PaymentException(str(e))
except SendMailException:
messages.warning(request, _('There was an error sending the confirmation mail.'))
return None
def payment_pending_render(self, request, payment) -> str:

View File

@@ -54,7 +54,6 @@ from pretix.base.forms import SecretKeySettingsField
from pretix.base.forms.questions import guess_country
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
from pretix.helpers import OF_SELF
from pretix.helpers.urls import build_absolute_uri as build_global_uri
@@ -565,7 +564,7 @@ class PaypalMethod(BasePaymentProvider):
)
request.session['payment_paypal_payment'] = None
else:
pass
return None
try:
paymentreq = OrdersCreateRequest()
@@ -821,9 +820,6 @@ class PaypalMethod(BasePaymentProvider):
payment.confirm()
except Quota.QuotaExceededException as e:
raise PaymentException(str(e))
except SendMailException:
messages.warning(request, _('There was an error sending the confirmation mail.'))
finally:
if 'payment_paypal_oid' in request.session:
del request.session['payment_paypal_oid']

View File

@@ -38,7 +38,6 @@ from pretix.base.models import (
fields,
)
from pretix.base.models.base import LoggingMixin
from pretix.base.services.mail import SendMailException
class ScheduledMail(models.Model):
@@ -180,13 +179,10 @@ class ScheduledMail(models.Model):
invoice_address=ia,
event_or_subevent=self.subevent or e,
)
try:
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
o_sent = True
except SendMailException:
... # ¯\_(ツ)_/¯
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
o_sent = True
if send_to_attendees:
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]
for p in positions:
try:
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
position=p,
event_or_subevent=self.subevent or e,
)
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
elif not o_sent and o.email:
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
event_or_subevent=self.subevent or e,
)
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
o_sent = True
except SendMailException:
... # ¯\_(ツ)_/¯
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
position=p,
event_or_subevent=self.subevent or e,
)
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
elif not o_sent and o.email:
email_ctx = get_email_context(
event=e,
order=o,
invoice_address=ia,
event_or_subevent=self.subevent or e,
)
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
attach_ical=self.rule.attach_ical,
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
o_sent = True
self.last_successful_order_id = o.pk

View File

@@ -41,7 +41,7 @@ from pretix.base.i18n import language
from pretix.base.models import (
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.celery_app import app
from pretix.helpers.format import format_map
@@ -53,7 +53,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,
attachments: list = None, attach_tickets: bool = False,
attach_ical: bool = False) -> None:
failures = []
user = User.objects.get(pk=user) if user else None
orders = Order.objects.filter(pk__in=objects, event=event)
subject = LazyI18nString(subject)
@@ -114,70 +113,64 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
if subevents_to and p.subevent.date_from >= subevents_to:
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):
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(
o.email,
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,
attach_cached_files=attachments
)
o.log_action(
'pretix.plugins.sendmail.order.email.sent',
'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': o.email,
'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(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,
}
)
@app.task(base=ProfiledEventTask, acks_late=True)

View File

@@ -71,7 +71,6 @@ from pretix.base.payment import (
BasePaymentProvider, PaymentException, WalletQueries,
)
from pretix.base.plugins import get_all_plugins
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
from pretix.helpers import OF_SELF
from pretix.helpers.countries import CachedCountries
@@ -980,9 +979,6 @@ class StripeMethod(BasePaymentProvider):
payment.confirm()
except Quota.QuotaExceededException as e:
raise PaymentException(str(e))
except SendMailException:
raise PaymentException(_('There was an error sending the confirmation mail.'))
elif intent.status == 'processing':
if request:
messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '

View File

@@ -1643,11 +1643,6 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
order = Order.objects.get(id=value)
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):
return self.get_step_url(self.request)

View File

@@ -75,7 +75,6 @@ from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_pdf_task,
invoice_qualified,
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderChangeManager, OrderError, _try_auto_refund, cancel_order,
change_payment_provider, error_messages,
@@ -595,10 +594,7 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView):
amount=Decimal('0.00'),
fee=None
)
try:
p.confirm()
except SendMailException:
pass
p.confirm()
else:
p._mark_order_paid(
payment_refund_sum=self.order.payment_refund_sum

View File

@@ -976,7 +976,7 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
return ctx
events = ebd[self.date]
shortest_duration = self._get_shortest_duration(events).total_seconds() // 60
shortest_duration = max(self._get_shortest_duration(events).total_seconds() // 60, 1)
# pick the next biggest tick_duration based on shortest_duration, max. 180 minutes
tick_duration = next((d for d in [5, 10, 15, 30, 60, 120, 180] if d >= shortest_duration), 180)

View File

@@ -32,8 +32,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import logging
from django.conf import settings
from django.contrib import messages
from django.utils.functional import cached_property
@@ -42,7 +40,7 @@ from django.views import View
from django.views.generic import TemplateView
from pretix.base.email import get_email_context
from pretix.base.services.mail import INVALID_ADDRESS, SendMailException, mail
from pretix.base.services.mail import INVALID_ADDRESS, mail
from pretix.helpers.http import redirect_to_url
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.forms.user import ResendLinkForm
@@ -83,13 +81,7 @@ class ResendLinkView(EventViewMixin, TemplateView):
subject = self.request.event.settings.mail_subject_resend_all_links
template = self.request.event.settings.mail_text_resend_all_links
context = get_email_context(event=self.request.event, orders=orders)
try:
mail(user, subject, template, context, event=self.request.event, locale=self.request.LANGUAGE_CODE)
except SendMailException:
logger = logging.getLogger('pretix.presale.user')
logger.exception('A mail resending order links to {} could not be sent.'.format(user))
messages.error(self.request, _('We have trouble sending emails right now, please check back later.'))
return self.get(request, *args, **kwargs)
mail(user, subject, template, context, event=self.request.event, locale=self.request.LANGUAGE_CODE)
messages.success(self.request, _('If there were any orders by this user, they will receive an email with their order codes.'))
return redirect_to_url(eventreverse(self.request.event, 'presale:event.index'))

View File

@@ -8,8 +8,8 @@
"name": "pretix",
"version": "0.0.0",
"dependencies": {
"@babel/core": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^16.0.1",
"rollup": "^2.79.1",
@@ -53,20 +53,20 @@
}
},
"node_modules/@babel/core": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
"@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.27.3",
"@babel/helpers": "^7.27.6",
"@babel/parser": "^7.28.0",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.3",
"@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.0",
"@babel/types": "^7.28.0",
"@babel/traverse": "^7.28.3",
"@babel/types": "^7.28.2",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -101,12 +101,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
"integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"dependencies": {
"@babel/parser": "^7.28.0",
"@babel/types": "^7.28.0",
"@babel/parser": "^7.28.3",
"@babel/types": "^7.28.2",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -173,17 +173,16 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz",
"integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==",
"license": "MIT",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
"integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1",
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-member-expression-to-functions": "^7.27.1",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/traverse": "^7.27.1",
"@babel/traverse": "^7.28.3",
"semver": "^6.3.1"
},
"engines": {
@@ -277,14 +276,13 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"license": "MIT",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
"@babel/traverse": "^7.27.3"
"@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
@@ -403,23 +401,23 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.6"
"@babel/types": "^7.28.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
"dependencies": {
"@babel/types": "^7.28.0"
"@babel/types": "^7.28.2"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -492,13 +490,12 @@
}
},
"node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz",
"integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==",
"license": "MIT",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz",
"integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.27.1"
"@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
@@ -657,12 +654,11 @@
}
},
"node_modules/@babel/plugin-transform-class-static-block": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz",
"integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==",
"license": "MIT",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz",
"integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.27.1",
"@babel/helper-create-class-features-plugin": "^7.28.3",
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
@@ -673,16 +669,16 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz",
"integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
"integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/traverse": "^7.28.0"
"@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
@@ -1177,9 +1173,9 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.28.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz",
"integrity": "sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz",
"integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -1361,9 +1357,9 @@
}
},
"node_modules/@babel/preset-env": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz",
"integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
"integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
"dependencies": {
"@babel/compat-data": "^7.28.0",
"@babel/helper-compilation-targets": "^7.27.2",
@@ -1373,7 +1369,7 @@
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
"@babel/plugin-syntax-import-assertions": "^7.27.1",
"@babel/plugin-syntax-import-attributes": "^7.27.1",
@@ -1384,8 +1380,8 @@
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
"@babel/plugin-transform-block-scoping": "^7.28.0",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-class-static-block": "^7.27.1",
"@babel/plugin-transform-classes": "^7.28.0",
"@babel/plugin-transform-class-static-block": "^7.28.3",
"@babel/plugin-transform-classes": "^7.28.3",
"@babel/plugin-transform-computed-properties": "^7.27.1",
"@babel/plugin-transform-destructuring": "^7.28.0",
"@babel/plugin-transform-dotall-regex": "^7.27.1",
@@ -1417,7 +1413,7 @@
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-transform-property-literals": "^7.27.1",
"@babel/plugin-transform-regenerator": "^7.28.0",
"@babel/plugin-transform-regenerator": "^7.28.3",
"@babel/plugin-transform-regexp-modifiers": "^7.27.1",
"@babel/plugin-transform-reserved-words": "^7.27.1",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
@@ -1479,16 +1475,16 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
"@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.0",
"@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.0",
"@babel/types": "^7.28.2",
"debug": "^4.3.1"
},
"engines": {
@@ -3831,20 +3827,20 @@
"integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="
},
"@babel/core": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz",
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"requires": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
"@babel/generator": "^7.28.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.27.3",
"@babel/helpers": "^7.27.6",
"@babel/parser": "^7.28.0",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.3",
"@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.0",
"@babel/types": "^7.28.0",
"@babel/traverse": "^7.28.3",
"@babel/types": "^7.28.2",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -3865,12 +3861,12 @@
}
},
"@babel/generator": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
"integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
"requires": {
"@babel/parser": "^7.28.0",
"@babel/types": "^7.28.0",
"@babel/parser": "^7.28.3",
"@babel/types": "^7.28.2",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -3928,16 +3924,16 @@
}
},
"@babel/helper-create-class-features-plugin": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz",
"integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
"integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.27.1",
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-member-expression-to-functions": "^7.27.1",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/traverse": "^7.27.1",
"@babel/traverse": "^7.28.3",
"semver": "^6.3.1"
},
"dependencies": {
@@ -4001,13 +3997,13 @@
}
},
"@babel/helper-module-transforms": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
"requires": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
"@babel/traverse": "^7.27.3"
"@babel/traverse": "^7.28.3"
}
},
"@babel/helper-optimise-call-expression": {
@@ -4078,20 +4074,20 @@
}
},
"@babel/helpers": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz",
"integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==",
"requires": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.6"
"@babel/types": "^7.28.2"
}
},
"@babel/parser": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
"integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
"requires": {
"@babel/types": "^7.28.0"
"@babel/types": "^7.28.2"
}
},
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
@@ -4130,12 +4126,12 @@
}
},
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz",
"integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz",
"integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.27.1"
"@babel/traverse": "^7.28.3"
}
},
"@babel/plugin-proposal-private-property-in-object": {
@@ -4223,25 +4219,25 @@
}
},
"@babel/plugin-transform-class-static-block": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz",
"integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz",
"integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==",
"requires": {
"@babel/helper-create-class-features-plugin": "^7.27.1",
"@babel/helper-create-class-features-plugin": "^7.28.3",
"@babel/helper-plugin-utils": "^7.27.1"
}
},
"@babel/plugin-transform-classes": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz",
"integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz",
"integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==",
"requires": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
"@babel/traverse": "^7.28.0"
"@babel/traverse": "^7.28.3"
}
},
"@babel/plugin-transform-computed-properties": {
@@ -4517,9 +4513,9 @@
}
},
"@babel/plugin-transform-regenerator": {
"version": "7.28.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.1.tgz",
"integrity": "sha512-P0QiV/taaa3kXpLY+sXla5zec4E+4t4Aqc9ggHlfZ7a2cp8/x/Gv08jfwEtn9gnnYIMvHx6aoOZ8XJL8eU71Dg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz",
"integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==",
"requires": {
"@babel/helper-plugin-utils": "^7.27.1"
}
@@ -4618,9 +4614,9 @@
}
},
"@babel/preset-env": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz",
"integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
"integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
"requires": {
"@babel/compat-data": "^7.28.0",
"@babel/helper-compilation-targets": "^7.27.2",
@@ -4630,7 +4626,7 @@
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
"@babel/plugin-syntax-import-assertions": "^7.27.1",
"@babel/plugin-syntax-import-attributes": "^7.27.1",
@@ -4641,8 +4637,8 @@
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
"@babel/plugin-transform-block-scoping": "^7.28.0",
"@babel/plugin-transform-class-properties": "^7.27.1",
"@babel/plugin-transform-class-static-block": "^7.27.1",
"@babel/plugin-transform-classes": "^7.28.0",
"@babel/plugin-transform-class-static-block": "^7.28.3",
"@babel/plugin-transform-classes": "^7.28.3",
"@babel/plugin-transform-computed-properties": "^7.27.1",
"@babel/plugin-transform-destructuring": "^7.28.0",
"@babel/plugin-transform-dotall-regex": "^7.27.1",
@@ -4674,7 +4670,7 @@
"@babel/plugin-transform-private-methods": "^7.27.1",
"@babel/plugin-transform-private-property-in-object": "^7.27.1",
"@babel/plugin-transform-property-literals": "^7.27.1",
"@babel/plugin-transform-regenerator": "^7.28.0",
"@babel/plugin-transform-regenerator": "^7.28.3",
"@babel/plugin-transform-regexp-modifiers": "^7.27.1",
"@babel/plugin-transform-reserved-words": "^7.27.1",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
@@ -4722,16 +4718,16 @@
}
},
"@babel/traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz",
"integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==",
"requires": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.0",
"@babel/generator": "^7.28.3",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.0",
"@babel/parser": "^7.28.3",
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.0",
"@babel/types": "^7.28.2",
"debug": "^4.3.1"
}
},

View File

@@ -4,8 +4,8 @@
"private": true,
"scripts": {},
"dependencies": {
"@babel/core": "^7.28.0",
"@babel/preset-env": "^7.28.0",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^16.0.1",
"vue": "^2.7.16",

View File

@@ -0,0 +1,10 @@
import smtplib
from django.core.mail.backends.locmem import EmailBackend
class FailingEmailBackend(EmailBackend):
def send_messages(self, email_messages):
raise smtplib.SMTPRecipientsRefused({
'recipient@example.org': (450, b'Recipient unknown')
})

View File

@@ -71,7 +71,7 @@ def env():
t.members.add(user)
t.limit_events.add(event)
o = Order.objects.create(
code='FOO', event=event, email='dummy@dummy.test',
code='ABC32', event=event, email='dummy@dummy.test',
status=Order.STATUS_PENDING,
datetime=now(), expires=now() + timedelta(days=10),
total=14, locale='en',
@@ -105,47 +105,48 @@ def env():
@pytest.mark.django_db
def test_order_list(client, env):
o = env[2]
with scopes_disabled():
otherticket = Item.objects.create(event=env[0], name='Early-bird ticket',
category=None, default_price=23,
admission=True, personalized=True)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?query=peter')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?query=hans')
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?query=dummy')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?status=p')
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?status=n')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?status=ne')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?item=%s' % otherticket.id)
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?item=%s' % env[3].id)
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?provider=free')
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?provider=banktransfer')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?status=o')
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
env[2].expires = now() - timedelta(days=10)
env[2].save()
response = client.get('/control/event/dummy/dummy/orders/?status=o')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?status=pa')
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
env[2].require_approval = True
env[2].save()
response = client.get('/control/event/dummy/dummy/orders/?status=pa')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
with scopes_disabled():
q = Question.objects.create(event=env[0], question="Q", type="N", required=True)
@@ -153,9 +154,9 @@ def test_order_list(client, env):
op = env[2].positions.first()
qa = QuestionAnswer.objects.create(question=q, orderposition=op, answer="12")
response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=12' % q.pk)
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=13' % q.pk)
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
q.type = "C"
q.save()
@@ -164,24 +165,24 @@ def test_order_list(client, env):
qo2 = q.options.create(answer="Bar")
qa.options.add(qo1)
response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=%d' % (q.pk, qo1.pk))
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?question=%d&answer=%d' % (q.pk, qo2.pk))
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
response = client.get('/control/event/dummy/dummy/orders/?status=testmode')
assert 'FOO' not in response.content.decode()
assert o.code not in response.content.decode()
assert 'TEST MODE' not in response.content.decode()
env[2].testmode = True
env[2].save()
response = client.get('/control/event/dummy/dummy/orders/?status=testmode')
assert 'FOO' in response.content.decode()
assert o.code in response.content.decode()
assert 'TEST MODE' in response.content.decode()
@pytest.mark.django_db
def test_order_detail(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/')
response = client.get('/control/event/dummy/dummy/orders/ABC32/')
assert 'Early-bird' in response.content.decode()
assert 'Peter' in response.content.decode()
assert 'Lukas Gelöscht' in response.content.decode()
@@ -193,7 +194,7 @@ def test_order_detail_show_test_mode(client, env):
env[2].testmode = True
env[2].save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/')
response = client.get('/control/event/dummy/dummy/orders/ABC32/')
assert 'TEST MODE' in response.content.decode()
@@ -203,7 +204,7 @@ def test_order_set_contact(client, env):
q = Quota.objects.create(event=env[0], size=0)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/contact', {
client.post('/control/event/dummy/dummy/orders/ABC32/contact', {
'email': 'admin@rami.io'
})
with scopes_disabled():
@@ -218,7 +219,7 @@ def test_order_set_customer(client, env):
c = org.customers.create(email='foo@example.org')
org.settings.customer_accounts = True
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/contact', {
client.post('/control/event/dummy/dummy/orders/ABC32/contact', {
'email': 'admin@rami.io',
'customer': c.pk
}, follow=True)
@@ -233,7 +234,7 @@ def test_order_set_locale(client, env):
q = Quota.objects.create(event=env[0], size=0)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/locale', {
client.post('/control/event/dummy/dummy/orders/ABC32/locale', {
'locale': 'de'
})
with scopes_disabled():
@@ -247,7 +248,7 @@ def test_order_set_locale_with_invalid_locale_value(client, env):
q = Quota.objects.create(event=env[0], size=0)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/locale', {
client.post('/control/event/dummy/dummy/orders/ABC32/locale', {
'locale': 'fr'
})
with scopes_disabled():
@@ -261,7 +262,7 @@ def test_order_set_comment(client, env):
q = Quota.objects.create(event=env[0], size=0)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/comment', {
client.post('/control/event/dummy/dummy/orders/ABC32/comment', {
'comment': 'Foo'
})
with scopes_disabled():
@@ -275,7 +276,7 @@ def test_order_transition_to_expired_success(client, env):
q = Quota.objects.create(event=env[0], size=0)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'e'
})
with scopes_disabled():
@@ -289,7 +290,7 @@ def test_order_transition_to_paid_in_time_success(client, env):
q = Quota.objects.create(event=env[0], size=0)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'amount': str(env[2].pending_sum),
'payment_date': now().date().isoformat(),
'status': 'p'
@@ -308,7 +309,7 @@ def test_order_transition_to_paid_expired_quota_left(client, env):
q = Quota.objects.create(event=env[0], size=10)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
res = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
res = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'p',
'payment_date': now().date().isoformat(),
'amount': str(o.pending_sum),
@@ -329,7 +330,7 @@ def test_order_approve(client, env):
q = Quota.objects.create(event=env[0], size=10)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
res = client.post('/control/event/dummy/dummy/orders/FOO/approve', {
res = client.post('/control/event/dummy/dummy/orders/ABC32/approve', {
})
with scopes_disabled():
o = Order.objects.get(id=env[2].id)
@@ -348,7 +349,7 @@ def test_order_deny(client, env):
q = Quota.objects.create(event=env[0], size=10)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
res = client.post('/control/event/dummy/dummy/orders/FOO/deny', {
res = client.post('/control/event/dummy/dummy/orders/ABC32/deny', {
})
with scopes_disabled():
o = Order.objects.get(id=env[2].id)
@@ -360,10 +361,10 @@ def test_order_deny(client, env):
@pytest.mark.django_db
def test_order_delete_require_testmode(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
res = client.get('/control/event/dummy/dummy/orders/FOO/delete', {}, follow=True)
res = client.get('/control/event/dummy/dummy/orders/ABC32/delete', {}, follow=True)
assert 'alert-danger' in res.content.decode()
assert 'Only orders created in test mode can be deleted' in res.content.decode()
client.post('/control/event/dummy/dummy/orders/FOO/delete', {}, follow=True)
client.post('/control/event/dummy/dummy/orders/ABC32/delete', {}, follow=True)
with scopes_disabled():
assert Order.objects.get(id=env[2].id)
@@ -375,7 +376,7 @@ def test_order_delete(client, env):
o.testmode = True
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/delete', {}, follow=True)
client.post('/control/event/dummy/dummy/orders/ABC32/delete', {}, follow=True)
with scopes_disabled():
assert not Order.objects.filter(id=env[2].id).exists()
@@ -401,8 +402,8 @@ def test_order_transition(client, env, process):
o.status = process[0]
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=' + process[1])
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=' + process[1])
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'amount': str(o.pending_sum),
'payment_date': now().date().isoformat(),
'status': process[1]
@@ -423,8 +424,8 @@ def test_order_cancel_free(client, env):
o.total = Decimal('0.00')
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c')
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'c'
})
with scopes_disabled():
@@ -441,8 +442,8 @@ def test_order_cancel_paid_keep_fee(client, env):
o.save()
o.event.tax_rules.create(rate=Decimal('7.00'), default=True)
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c')
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'c',
'cancellation_fee': '6.00'
})
@@ -471,8 +472,8 @@ def test_order_cancel_paid_keep_fee_taxed(client, env):
o.save()
tr7 = o.event.tax_rules.create(rate=Decimal('7.00'), default=True)
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c')
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'c',
'cancellation_fee': '6.00'
})
@@ -509,8 +510,8 @@ def test_order_cancel_paid_keep_fee_tax_split(client, env):
op2._calculate_tax(tax_rule=tr19)
op2.save()
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c')
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'c',
'cancellation_fee': '6.00'
})
@@ -543,8 +544,8 @@ def test_order_cancel_pending_keep_fee(client, env):
o.status = Order.STATUS_PENDING
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c')
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'c',
'cancellation_fee': '6.00'
})
@@ -568,8 +569,8 @@ def test_order_cancel_pending_fee_too_high(client, env):
o.status = Order.STATUS_PENDING
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c')
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'c',
'cancellation_fee': '26.00'
})
@@ -584,8 +585,8 @@ def test_order_cancel_pending_fee_too_high(client, env):
@pytest.mark.django_db
def test_order_cancel_unpaid_fees_allowed(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c')
client.post('/control/event/dummy/dummy/orders/FOO/transition', {
client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c')
client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'c',
'cancellation_fee': '6.00'
})
@@ -601,7 +602,7 @@ def test_order_cancel_unpaid_fees_allowed(client, env):
def test_order_invoice_create_forbidden(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
env[0].settings.set('invoice_generate', 'no')
response = client.post('/control/event/dummy/dummy/orders/FOO/invoice', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoice', {}, follow=True)
assert 'alert-danger' in response.content.decode()
@@ -611,7 +612,7 @@ def test_order_invoice_create_duplicate(client, env):
with scopes_disabled():
generate_invoice(env[2])
env[0].settings.set('invoice_generate', 'admin')
response = client.post('/control/event/dummy/dummy/orders/FOO/invoice', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoice', {}, follow=True)
assert 'alert-danger' in response.content.decode()
@@ -619,7 +620,7 @@ def test_order_invoice_create_duplicate(client, env):
def test_order_invoice_create_ok(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
env[0].settings.set('invoice_generate', 'admin')
response = client.post('/control/event/dummy/dummy/orders/FOO/invoice', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoice', {}, follow=True)
assert 'alert-success' in response.content.decode()
with scopes_disabled():
assert env[2].invoices.exists()
@@ -632,7 +633,7 @@ def test_order_invoice_retransmit(client, env):
i = generate_invoice(env[2])
i.transmission_status = Invoice.TRANSMISSION_STATUS_FAILED
i.save()
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/retransmit' % i.pk, {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoices/%d/retransmit' % i.pk, {}, follow=True)
assert 'alert-success' in response.content.decode()
i.refresh_from_db()
assert i.transmission_status == Invoice.TRANSMISSION_STATUS_PENDING
@@ -645,7 +646,7 @@ def test_order_invoice_regenerate(client, env):
i = generate_invoice(env[2])
InvoiceAddress.objects.create(name_parts={'full_name': 'Foo', "_scheme": "full"}, order=env[2])
env[0].settings.set('invoice_generate', 'admin')
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/regenerate' % i.pk, {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoices/%d/regenerate' % i.pk, {}, follow=True)
assert 'alert-success' in response.content.decode()
i.refresh_from_db()
assert 'Foo' in i.invoice_to
@@ -659,14 +660,14 @@ def test_order_invoice_regenerate_canceled(client, env):
with scopes_disabled():
i = generate_invoice(env[2])
generate_cancellation(i)
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/regenerate' % i.pk, {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoices/%d/regenerate' % i.pk, {}, follow=True)
assert 'alert-danger' in response.content.decode()
@pytest.mark.django_db
def test_order_invoice_regenerate_unknown(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/regenerate' % 3, {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoices/%d/regenerate' % 3, {}, follow=True)
assert 'alert-danger' in response.content.decode()
@@ -677,7 +678,7 @@ def test_order_invoice_reissue(client, env):
i = generate_invoice(env[2])
InvoiceAddress.objects.create(name_parts={'full_name': 'Foo', "_scheme": "full"}, order=env[2])
env[0].settings.set('invoice_generate', 'admin')
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/reissue' % i.pk, {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoices/%d/reissue' % i.pk, {}, follow=True)
assert 'alert-success' in response.content.decode()
i.refresh_from_db()
with scopes_disabled():
@@ -693,14 +694,14 @@ def test_order_invoice_reissue_canceled(client, env):
with scopes_disabled():
i = generate_invoice(env[2])
generate_cancellation(i)
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/reissue' % i.pk, {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoices/%d/reissue' % i.pk, {}, follow=True)
assert 'alert-danger' in response.content.decode()
@pytest.mark.django_db
def test_order_invoice_reissue_unknown(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/invoices/%d/reissue' % 3, {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/invoices/%d/reissue' % 3, {}, follow=True)
assert 'alert-danger' in response.content.decode()
@@ -708,9 +709,9 @@ def test_order_invoice_reissue_unknown(client, env):
def test_order_resend_link(client, env):
mail.outbox = []
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/resend', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/resend', {}, follow=True)
assert 'alert-success' in response.content.decode()
assert 'FOO' in mail.outbox[0].body
assert 'ABC32' in mail.outbox[0].body
@pytest.mark.django_db
@@ -720,9 +721,9 @@ def test_order_reactivate_not_canceled(client, env):
o.status = Order.STATUS_PAID
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/reactivate', follow=True)
response = client.get('/control/event/dummy/dummy/orders/ABC32/reactivate', follow=True)
assert 'alert-danger' in response.content.decode()
response = client.post('/control/event/dummy/dummy/orders/FOO/reactivate', follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/reactivate', follow=True)
assert 'alert-danger' in response.content.decode()
@@ -735,7 +736,7 @@ def test_order_reactivate(client, env):
o.status = Order.STATUS_CANCELED
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/reactivate', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/reactivate', {
}, follow=True)
assert 'alert-success' in response.content.decode()
with scopes_disabled():
@@ -750,9 +751,9 @@ def test_order_extend_not_pending(client, env):
o.status = Order.STATUS_PAID
o.save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/extend', follow=True)
response = client.get('/control/event/dummy/dummy/orders/ABC32/extend', follow=True)
assert 'alert-danger' in response.content.decode()
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', follow=True)
assert 'alert-danger' in response.content.decode()
@@ -765,7 +766,7 @@ def test_order_extend_not_expired(client, env):
generate_invoice(o)
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert 'alert-success' in response.content.decode()
@@ -785,7 +786,7 @@ def test_order_extend_overdue_quota_empty(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert 'alert-success' in response.content.decode()
@@ -808,7 +809,7 @@ def test_order_extend_overdue_quota_blocked_by_waiting_list(client, env):
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert 'alert-success' in response.content.decode()
@@ -833,7 +834,7 @@ def test_order_extend_expired_quota_left(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
with scopes_disabled():
assert o.invoices.count() == 2
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-success' in response.content
@@ -857,7 +858,7 @@ def test_order_extend_expired_quota_empty(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-danger' in response.content
@@ -878,7 +879,7 @@ def test_order_extend_expired_quota_empty_ignore(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate,
'quota_ignore': 'on'
}, follow=True)
@@ -905,7 +906,7 @@ def test_order_extend_expired_seat_free(client, env):
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
assert o.invoices.count() == 2
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-success' in response.content
@@ -933,7 +934,7 @@ def test_order_extend_expired_seat_blocked(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-danger' in response.content
@@ -976,7 +977,7 @@ def test_order_extend_expired_seat_taken(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-danger' in response.content
@@ -1005,7 +1006,7 @@ def test_order_extend_expired_quota_partial(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-danger' in response.content
@@ -1035,7 +1036,7 @@ def test_order_extend_expired_voucher_budget_ok(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-success' in response.content
@@ -1066,7 +1067,7 @@ def test_order_extend_expired_voucher_budget_fail(client, env):
q.items.add(env[3])
newdate = (now() + timedelta(days=20)).strftime("%Y-%m-%d")
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/extend', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/extend', {
'expires': newdate
}, follow=True)
assert b'alert-danger' in response.content
@@ -1090,7 +1091,7 @@ def test_order_mark_paid_overdue_quota_blocked_by_waiting_list(client, env):
env[0].waitinglistentries.create(item=env[3], email='foo@bar.com')
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'p',
'payment_date': now().date().isoformat(),
'amount': str(o.pending_sum),
@@ -1112,7 +1113,7 @@ def test_order_mark_paid_blocked(client, env):
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'amount': str(o.pending_sum),
'payment_date': now().date().isoformat(),
'status': 'p'
@@ -1137,7 +1138,7 @@ def test_order_mark_paid_overpaid_expired(client, env):
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'p',
'payment_date': now().date().isoformat(),
'amount': '0.00',
@@ -1162,7 +1163,7 @@ def test_order_mark_paid_forced(client, env):
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'p',
'payment_date': now().date().isoformat(),
'amount': str(o.pending_sum),
@@ -1206,7 +1207,7 @@ def test_order_mark_paid_expired_seat_taken(client, env):
q = Quota.objects.create(event=env[0], size=100)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'p',
'payment_date': now().date().isoformat(),
'amount': str(o.pending_sum),
@@ -1236,7 +1237,7 @@ def test_order_mark_paid_expired_blocked(client, env):
q = Quota.objects.create(event=env[0], size=100)
q.items.add(env[3])
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'p',
'payment_date': now().date().isoformat(),
'amount': str(o.pending_sum),
@@ -1250,7 +1251,7 @@ def test_order_mark_paid_expired_blocked(client, env):
env[0].settings.seating_allow_blocked_seats_for_channel = ["bar"]
response = client.post('/control/event/dummy/dummy/orders/FOO/transition', {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition', {
'status': 'p',
'payment_date': now().date().isoformat(),
'amount': str(o.pending_sum),
@@ -1265,22 +1266,22 @@ def test_order_mark_paid_expired_blocked(client, env):
@pytest.mark.django_db
def test_order_go_lowercase(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/go?code=DuMmyfoO')
assert response['Location'].endswith('/control/event/dummy/dummy/orders/FOO/')
response = client.get('/control/event/dummy/dummy/orders/go?code=DuMmyabC32')
assert response['Location'].endswith('/control/event/dummy/dummy/orders/ABC32/')
@pytest.mark.django_db
def test_order_go_with_slug(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/go?code=DUMMYFOO')
assert response['Location'].endswith('/control/event/dummy/dummy/orders/FOO/')
response = client.get('/control/event/dummy/dummy/orders/go?code=DUMMYABC32')
assert response['Location'].endswith('/control/event/dummy/dummy/orders/ABC32/')
@pytest.mark.django_db
def test_order_go_found(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/go?code=FOO')
assert response['Location'].endswith('/control/event/dummy/dummy/orders/FOO/')
response = client.get('/control/event/dummy/dummy/orders/go?code=ABC32')
assert response['Location'].endswith('/control/event/dummy/dummy/orders/ABC32/')
@pytest.mark.django_db
@@ -1768,7 +1769,7 @@ def test_check_vatid(client, env):
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('AT'))
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
mock_validate.return_value = 'AT123456'
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-success' in response.content.decode()
ia.refresh_from_db()
assert ia.vat_id_validated
@@ -1781,7 +1782,7 @@ def test_check_vatid_no_entered(client, env):
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, country=Country('AT'))
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
mock_validate.return_value = 'AT123456'
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-danger' in response.content.decode()
ia.refresh_from_db()
assert not ia.vat_id_validated
@@ -1792,7 +1793,7 @@ def test_check_vatid_invalid_country(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
with scopes_disabled():
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567', country=Country('FR'))
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-danger' in response.content.decode()
ia.refresh_from_db()
assert not ia.vat_id_validated
@@ -1805,7 +1806,7 @@ def test_check_vatid_noneu_country(client, env):
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='CHU1234567', country=Country('CH'))
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
mock_validate.return_value = 'AT123456'
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-danger' in response.content.decode()
ia.refresh_from_db()
assert not ia.vat_id_validated
@@ -1818,7 +1819,7 @@ def test_check_vatid_no_country(client, env):
ia = InvoiceAddress.objects.create(order=env[2], is_business=True, vat_id='ATU1234567')
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
mock_validate.return_value = 'AT123456'
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-danger' in response.content.decode()
ia.refresh_from_db()
assert not ia.vat_id_validated
@@ -1829,7 +1830,7 @@ def test_check_vatid_no_invoiceaddress(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')
with mock.patch('pretix.base.services.tax._validate_vat_id_EU') as mock_validate:
mock_validate.return_value = 'AT123456'
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-danger' in response.content.decode()
@@ -1843,7 +1844,7 @@ def test_check_vatid_invalid(client, env):
raise VATIDFinalError('Fail')
mock_validate.side_effect = raiser
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-danger' in response.content.decode()
ia.refresh_from_db()
assert not ia.vat_id_validated
@@ -1859,7 +1860,7 @@ def test_check_vatid_unavailable(client, env):
raise VATIDTemporaryError('Fail')
mock_validate.side_effect = raiser
response = client.post('/control/event/dummy/dummy/orders/FOO/checkvatid', {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/checkvatid', {}, follow=True)
assert 'alert-danger' in response.content.decode()
ia.refresh_from_db()
assert not ia.vat_id_validated
@@ -1870,11 +1871,11 @@ def test_cancel_payment(client, env):
with scopes_disabled():
p = env[2].payments.last()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/cancel'.format(p.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/payments/{}/cancel'.format(p.pk), {}, follow=True)
assert 'alert-success' in response.content.decode()
p.refresh_from_db()
assert p.state == OrderPayment.PAYMENT_STATE_CANCELED
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/cancel'.format(p.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/payments/{}/cancel'.format(p.pk), {}, follow=True)
assert 'alert-danger' in response.content.decode()
@@ -1889,13 +1890,13 @@ def test_cancel_refund(client, env):
execution_date=now(),
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/cancel'.format(r.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/cancel'.format(r.pk), {}, follow=True)
assert 'alert-success' in response.content.decode()
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_CANCELED
r.state = OrderRefund.REFUND_STATE_DONE
r.save()
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/cancel'.format(r.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/cancel'.format(r.pk), {}, follow=True)
assert 'alert-danger' in response.content.decode()
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_DONE
@@ -1912,7 +1913,7 @@ def test_process_refund(client, env):
execution_date=now(),
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/process'.format(r.pk), {}, follow=True)
assert 'alert-success' in response.content.decode()
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_DONE
@@ -1939,7 +1940,7 @@ def test_process_refund_overpaid_externally(client, env):
execution_date=now(),
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/process'.format(r.pk), {}, follow=True)
assert 'alert-success' in response.content.decode()
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_DONE
@@ -1959,7 +1960,7 @@ def test_process_refund_invalid_state(client, env):
execution_date=now(),
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/process'.format(r.pk), {}, follow=True)
assert 'alert-danger' in response.content.decode()
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_CANCELED
@@ -1976,7 +1977,7 @@ def test_process_refund_mark_refunded(client, env):
execution_date=now(),
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/process'.format(r.pk), {'action': 'r'},
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/process'.format(r.pk), {'action': 'r'},
follow=True)
assert 'alert-success' in response.content.decode()
r.refresh_from_db()
@@ -1996,7 +1997,7 @@ def test_done_refund(client, env):
execution_date=now(),
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/done'.format(r.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/done'.format(r.pk), {}, follow=True)
assert 'alert-success' in response.content.decode()
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_DONE
@@ -2013,7 +2014,7 @@ def test_done_refund_invalid_state(client, env):
execution_date=now(),
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/refunds/{}/done'.format(r.pk), {}, follow=True)
response = client.post('/control/event/dummy/dummy/orders/ABC32/refunds/{}/done'.format(r.pk), {}, follow=True)
assert 'alert-danger' in response.content.decode()
r.refresh_from_db()
assert r.state == OrderRefund.REFUND_STATE_EXTERNAL
@@ -2024,7 +2025,7 @@ def test_confirm_payment(client, env):
with scopes_disabled():
p = env[2].payments.last()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {
response = client.post('/control/event/dummy/dummy/orders/ABC32/payments/{}/confirm'.format(p.pk), {
'amount': str(p.amount),
'payment_date': str(now().date().isoformat()),
}, follow=True)
@@ -2042,7 +2043,7 @@ def test_confirm_payment_invalid_state(client, env):
p.state = OrderPayment.PAYMENT_STATE_FAILED
p.save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {
response = client.post('/control/event/dummy/dummy/orders/ABC32/payments/{}/confirm'.format(p.pk), {
'amount': str(p.amount),
'payment_date': str(now().date().isoformat()),
}, follow=True)
@@ -2060,7 +2061,7 @@ def test_confirm_payment_partal_amount(client, env):
p.amount -= Decimal(5.00)
p.save()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/payments/{}/confirm'.format(p.pk), {
response = client.post('/control/event/dummy/dummy/orders/ABC32/payments/{}/confirm'.format(p.pk), {
'amount': str(p.amount),
'payment_date': str(now().date().isoformat()),
}, follow=True)
@@ -2077,15 +2078,15 @@ def test_refund_paid_order_fully_mark_as_refunded(client, env):
p = env[2].payments.last()
p.confirm()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/refund')
response = client.get('/control/event/dummy/dummy/orders/ABC32/refund')
doc = BeautifulSoup(response.content.decode(), "lxml")
assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00"
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '14.00',
'start-mode': 'full',
'start-action': 'mark_refunded'
}, follow=True)
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '14.00',
'start-mode': 'full',
'start-action': 'mark_refunded',
@@ -2111,10 +2112,10 @@ def test_refund_paid_order_fully_mark_as_pending(client, env):
p = env[2].payments.last()
p.confirm()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/refund')
response = client.get('/control/event/dummy/dummy/orders/ABC32/refund')
doc = BeautifulSoup(response.content.decode(), "lxml")
assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00"
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '14.00',
'start-mode': 'full',
'start-action': 'mark_pending',
@@ -2140,15 +2141,15 @@ def test_refund_paid_order_partially_mark_as_pending(client, env):
p = env[2].payments.last()
p.confirm()
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/refund')
response = client.get('/control/event/dummy/dummy/orders/ABC32/refund')
doc = BeautifulSoup(response.content.decode(), "lxml")
assert doc.select("input[name$=partial_amount]")[0]["value"] == "14.00"
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending'
}, follow=True)
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2178,8 +2179,8 @@ def test_refund_propose_lower_payment(client, env):
amount=Decimal('6.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED
)
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/refund')
response = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.get('/control/event/dummy/dummy/orders/ABC32/refund')
response = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending'
@@ -2199,8 +2200,8 @@ def test_refund_propose_equal_payment(client, env):
amount=Decimal('7.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED
)
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/refund')
response = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.get('/control/event/dummy/dummy/orders/ABC32/refund')
response = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending'
@@ -2220,8 +2221,8 @@ def test_refund_propose_higher_payment(client, env):
amount=Decimal('8.00'), provider='stripe', state=OrderPayment.PAYMENT_STATE_CONFIRMED
)
client.login(email='dummy@dummy.dummy', password='dummy')
client.get('/control/event/dummy/dummy/orders/FOO/refund')
response = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.get('/control/event/dummy/dummy/orders/ABC32/refund')
response = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending'
@@ -2237,7 +2238,7 @@ def test_refund_amount_does_not_match_or_invalid(client, env):
p = env[2].payments.last()
p.confirm()
client.login(email='dummy@dummy.dummy', password='dummy')
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
resp = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2249,7 +2250,7 @@ def test_refund_amount_does_not_match_or_invalid(client, env):
}, follow=True)
assert b'alert-danger' in resp.content
assert b'do not match the' in resp.content
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
resp = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '15.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2261,7 +2262,7 @@ def test_refund_amount_does_not_match_or_invalid(client, env):
}, follow=True)
assert b'alert-danger' in resp.content
assert b'The refund amount needs to be positive' in resp.content
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
resp = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2273,7 +2274,7 @@ def test_refund_amount_does_not_match_or_invalid(client, env):
}, follow=True)
assert b'alert-danger' in resp.content
assert b'do not match the' in resp.content
resp = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
resp = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2311,7 +2312,7 @@ def test_refund_paid_order_automatically_failed(client, env, monkeypatch):
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
monkeypatch.setattr("stripe.Refund.create", refund_create)
r = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
r = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2359,7 +2360,7 @@ def test_refund_paid_order_automatically(client, env, monkeypatch):
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
monkeypatch.setattr("stripe.Refund.create", refund_create)
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '7.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2386,7 +2387,7 @@ def test_refund_paid_order_offsetting_to_unknown(client, env):
p.confirm()
client.login(email='dummy@dummy.dummy', password='dummy')
r = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
r = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '5.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2422,7 +2423,7 @@ def test_refund_paid_order_offsetting_to_wrong_currency(client, env):
)
o.positions.create(price=5, item=ticket2)
r = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
r = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '5.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2450,7 +2451,7 @@ def test_refund_paid_order_offsetting(client, env):
total=5, locale='en'
)
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '5.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2492,7 +2493,7 @@ def test_refund_prevent_duplicate_submit(client, env):
)
env[2].refunds.create(provider="manual", amount=Decimal("2.00"), state=OrderRefund.REFUND_STATE_CREATED)
r = client.post('/control/event/dummy/dummy/orders/FOO/refund', {
r = client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '5.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2514,7 +2515,7 @@ def test_refund_paid_order_giftcard(client, env):
p.confirm()
client.login(email='dummy@dummy.dummy', password='dummy')
client.post('/control/event/dummy/dummy/orders/FOO/refund', {
client.post('/control/event/dummy/dummy/orders/ABC32/refund', {
'start-partial_amount': '5.00',
'start-mode': 'partial',
'start-action': 'mark_pending',
@@ -2582,7 +2583,7 @@ def test_delete_cancellation_request(client, env):
refund_as_giftcard=True
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.post('/control/event/dummy/dummy/orders/FOO/cancellationrequests/{}/delete'.format(r.pk), {},
response = client.post('/control/event/dummy/dummy/orders/ABC32/cancellationrequests/{}/delete'.format(r.pk), {},
follow=True)
assert 'alert-success' in response.content.decode()
assert not env[2].cancellation_requests.exists()
@@ -2600,10 +2601,10 @@ def test_approve_cancellation_request(client, env):
refund_as_giftcard=True
)
client.login(email='dummy@dummy.dummy', password='dummy')
response = client.get('/control/event/dummy/dummy/orders/FOO/transition?status=c&req={}'.format(r.pk), {})
response = client.get('/control/event/dummy/dummy/orders/ABC32/transition?status=c&req={}'.format(r.pk), {})
doc = BeautifulSoup(response.content.decode(), "lxml")
assert doc.select('input[name=cancellation_fee]')[0]['value'] == '4.00'
response = client.post('/control/event/dummy/dummy/orders/FOO/transition?req={}'.format(r.pk), {
response = client.post('/control/event/dummy/dummy/orders/ABC32/transition?req={}'.format(r.pk), {
'status': 'c',
'cancellation_fee': '4.00'
}, follow=True)