Compare commits

..
Author SHA1 Message Date
Richard Schreiber 4d18e8391a Fix mail headers being None 2026-02-03 11:16:58 +01:00
169 changed files with 126015 additions and 184718 deletions
-50
View File
@@ -1719,56 +1719,6 @@ List of all order positions
:statuscode 401: Authentication failure :statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/orderpositions/
Returns a list of all order positions within all events of a given organizer (with sufficient access permissions).
The supported query parameters and output format of this endpoint are almost identical to those of the list endpoint
within an event.
The only changes are that responses also contain the ``event`` attribute in each result and that the 'pdf_data'
parameter is not supported.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/orderpositions/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
X-Page-Generated: 2017-12-01T10:00:00Z
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id:": 23442
"event": "sampleconf",
"order": "ABC12",
"positionid": 1,
"canceled": false,
"item": 1345,
...
}
]
}
:param organizer: The ``slug`` field of the organizer to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
Fetching individual positions Fetching individual positions
----------------------------- -----------------------------
-3
View File
@@ -60,9 +60,6 @@ The following values for ``action_types`` are valid with pretix core:
* ``pretix.event.added`` * ``pretix.event.added``
* ``pretix.event.changed`` * ``pretix.event.changed``
* ``pretix.event.deleted`` * ``pretix.event.deleted``
* ``pretix.giftcards.created``
* ``pretix.giftcards.modified``
* ``pretix.giftcards.transaction.*``
* ``pretix.voucher.added`` * ``pretix.voucher.added``
* ``pretix.voucher.changed`` * ``pretix.voucher.changed``
* ``pretix.voucher.deleted`` * ``pretix.voucher.deleted``
+1 -1
View File
@@ -1,6 +1,6 @@
sphinx==9.1.* sphinx==9.1.*
sphinx-rtd-theme~=3.1.0 sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.0.0 sphinxcontrib-httpdomain~=1.8.1
sphinxcontrib-images~=1.0.1 sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1 sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-spelling~=8.0.2
+1 -1
View File
@@ -1,7 +1,7 @@
-e ../ -e ../
sphinx==9.1.* sphinx==9.1.*
sphinx-rtd-theme~=3.1.0 sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.0.0 sphinxcontrib-httpdomain~=1.8.1
sphinxcontrib-images~=1.0.1 sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1 sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-spelling~=8.0.2
+7 -7
View File
@@ -33,7 +33,7 @@ dependencies = [
"celery==5.6.*", "celery==5.6.*",
"chardet==5.2.*", "chardet==5.2.*",
"cryptography>=44.0.0", "cryptography>=44.0.0",
"css-inline==0.20.*", "css-inline==0.19.*",
"defusedcsv>=1.1.0", "defusedcsv>=1.1.0",
"dnspython==2.*", "dnspython==2.*",
"Django[argon2]==4.2.*,>=4.2.26", "Django[argon2]==4.2.*,>=4.2.26",
@@ -65,7 +65,7 @@ dependencies = [
"kombu==5.6.*", "kombu==5.6.*",
"libsass==0.23.*", "libsass==0.23.*",
"lxml", "lxml",
"markdown==3.10.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3. "markdown==3.10.1", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7 # We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*", "mt-940==4.30.*",
"oauthlib==3.3.*", "oauthlib==3.3.*",
@@ -73,11 +73,11 @@ dependencies = [
"packaging", "packaging",
"paypalrestsdk==1.13.*", "paypalrestsdk==1.13.*",
"paypal-checkout-serversdk==1.0.*", "paypal-checkout-serversdk==1.0.*",
"PyJWT==2.11.*", "PyJWT==2.10.*",
"phonenumberslite==9.0.*", "phonenumberslite==9.0.*",
"Pillow==12.1.*", "Pillow==12.1.*",
"pretix-plugin-build", "pretix-plugin-build",
"protobuf==7.34.*", "protobuf==6.33.*",
"psycopg2-binary", "psycopg2-binary",
"pycountry", "pycountry",
"pycparser==3.0", "pycparser==3.0",
@@ -92,7 +92,7 @@ dependencies = [
"redis==7.1.*", "redis==7.1.*",
"reportlab==4.4.*", "reportlab==4.4.*",
"requests==2.32.*", "requests==2.32.*",
"sentry-sdk==2.54.*", "sentry-sdk==2.51.*",
"sepaxml==2.7.*", "sepaxml==2.7.*",
"stripe==7.9.*", "stripe==7.9.*",
"text-unidecode==1.*", "text-unidecode==1.*",
@@ -110,10 +110,10 @@ dev = [
"aiohttp==3.13.*", "aiohttp==3.13.*",
"coverage", "coverage",
"coveralls", "coveralls",
"fakeredis==2.34.*", "fakeredis==2.33.*",
"flake8==7.3.*", "flake8==7.3.*",
"freezegun", "freezegun",
"isort==8.0.*", "isort==7.0.*",
"pep8-naming==0.15.*", "pep8-naming==0.15.*",
"potypo", "potypo",
"pytest-asyncio>=0.24", "pytest-asyncio>=0.24",
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
__version__ = "2026.3.0.dev0" __version__ = "2026.2.0.dev0"
-21
View File
@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
import json
import logging import logging
import os import os
from collections import Counter, defaultdict from collections import Counter, defaultdict
@@ -637,14 +636,6 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
return entry return entry
class OrganizerOrderPositionSerializer(OrderPositionSerializer):
event = SlugRelatedField(slug_field='slug', read_only=True)
class Meta(OrderPositionSerializer.Meta):
fields = OrderPositionSerializer.Meta.fields + ('event',)
read_only_fields = OrderPositionSerializer.Meta.read_only_fields + ('event',)
class RequireAttentionField(serializers.Field): class RequireAttentionField(serializers.Field):
def to_representation(self, instance: OrderPosition): def to_representation(self, instance: OrderPosition):
return instance.require_checkin_attention return instance.require_checkin_attention
@@ -1224,18 +1215,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
raise ValidationError('The given payment provider is not known.') raise ValidationError('The given payment provider is not known.')
return pp return pp
def validate_payment_info(self, info):
if info:
try:
obj = json.loads(info)
except ValueError:
raise ValidationError('payment_info must be valid JSON.')
if not isinstance(obj, dict):
# only objects are allowed
raise ValidationError('payment_info must be a JSON object.')
return info
def validate_expires(self, expires): def validate_expires(self, expires):
if expires < now(): if expires < now():
raise ValidationError('Expiration date must be in the future.') raise ValidationError('Expiration date must be in the future.')
+1 -2
View File
@@ -365,10 +365,9 @@ class TeamInviteSerializer(serializers.ModelSerializer):
def _send_invite(self, instance): def _send_invite(self, instance):
mail( mail(
instance.email, instance.email,
_('Account invitation'), _('pretix account invitation'),
'pretixcontrol/email/invitation.txt', 'pretixcontrol/email/invitation.txt',
{ {
'instance': settings.PRETIX_INSTANCE_NAME,
'user': self, 'user': self,
'organizer': self.context['organizer'].name, 'organizer': self.context['organizer'].name,
'team': instance.team.name, 'team': instance.team.name,
+1 -2
View File
@@ -67,7 +67,6 @@ orga_router.register(r'invoices', order.InvoiceViewSet)
orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportViewSet) orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportViewSet)
orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters') orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters')
orga_router.register(r'transactions', order.OrganizerTransactionViewSet) orga_router.register(r'transactions', order.OrganizerTransactionViewSet)
orga_router.register(r'orderpositions', order.OrganizerOrderPositionViewSet, basename='orderpositions')
team_router = routers.DefaultRouter() team_router = routers.DefaultRouter()
team_router.register(r'members', organizer.TeamMemberViewSet) team_router.register(r'members', organizer.TeamMemberViewSet)
@@ -84,7 +83,7 @@ event_router.register(r'discounts', discount.DiscountViewSet)
event_router.register(r'quotas', item.QuotaViewSet) event_router.register(r'quotas', item.QuotaViewSet)
event_router.register(r'vouchers', voucher.VoucherViewSet) event_router.register(r'vouchers', voucher.VoucherViewSet)
event_router.register(r'orders', order.EventOrderViewSet) event_router.register(r'orders', order.EventOrderViewSet)
event_router.register(r'orderpositions', order.EventOrderPositionViewSet) event_router.register(r'orderpositions', order.OrderPositionViewSet)
event_router.register(r'transactions', order.TransactionViewSet) event_router.register(r'transactions', order.TransactionViewSet)
event_router.register(r'invoices', order.InvoiceViewSet) event_router.register(r'invoices', order.InvoiceViewSet)
event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets') event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets')
+1 -5
View File
@@ -188,15 +188,11 @@ class CheckinListViewSet(viewsets.ModelViewSet):
clist = self.get_object() clist = self.get_object()
if serializer.validated_data.get('nonce'): if serializer.validated_data.get('nonce'):
if kwargs.get('position'): if kwargs.get('position'):
prev = kwargs['position'].all_checkins.filter( prev = kwargs['position'].all_checkins.filter(nonce=serializer.validated_data['nonce']).first()
nonce=serializer.validated_data['nonce'],
successful=False
).first()
else: else:
prev = clist.checkins.filter( prev = clist.checkins.filter(
nonce=serializer.validated_data['nonce'], nonce=serializer.validated_data['nonce'],
raw_barcode=serializer.validated_data['raw_barcode'], raw_barcode=serializer.validated_data['raw_barcode'],
successful=False
).first() ).first()
if prev: if prev:
# Ignore because nonce is already handled # Ignore because nonce is already handled
+12 -49
View File
@@ -57,10 +57,9 @@ from pretix.api.serializers.order import (
BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer, BlockedTicketSecretSerializer, InvoiceSerializer, OrderCreateSerializer,
OrderPaymentCreateSerializer, OrderPaymentSerializer, OrderPaymentCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer, OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, OrganizerOrderPositionSerializer, OrderRefundSerializer, OrderSerializer, OrganizerTransactionSerializer,
OrganizerTransactionSerializer, PriceCalcSerializer, PrintLogSerializer, PriceCalcSerializer, PrintLogSerializer, RevokedTicketSecretSerializer,
RevokedTicketSecretSerializer, SimulatedOrderSerializer, SimulatedOrderSerializer, TransactionSerializer,
TransactionSerializer,
) )
from pretix.api.serializers.orderchange import ( from pretix.api.serializers.orderchange import (
BlockNameSerializer, OrderChangeOperationSerializer, BlockNameSerializer, OrderChangeOperationSerializer,
@@ -1066,7 +1065,8 @@ with scopes_disabled():
} }
class OrderPositionViewSetMixin: class OrderPositionViewSet(viewsets.ModelViewSet):
serializer_class = OrderPositionSerializer
queryset = OrderPosition.all.none() queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter) filter_backends = (DjangoFilterBackend, RichOrderingFilter)
ordering = ('order__datetime', 'positionid') ordering = ('order__datetime', 'positionid')
@@ -1087,7 +1087,8 @@ class OrderPositionViewSetMixin:
def get_serializer_context(self): def get_serializer_context(self):
ctx = super().get_serializer_context() ctx = super().get_serializer_context()
ctx['pdf_data'] = False ctx['event'] = self.request.event
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
ctx['check_quotas'] = self.request.query_params.get('check_quotas', 'true').lower() == 'true' ctx['check_quotas'] = self.request.query_params.get('check_quotas', 'true').lower() == 'true'
return ctx return ctx
@@ -1096,8 +1097,9 @@ class OrderPositionViewSetMixin:
qs = OrderPosition.all qs = OrderPosition.all
else: else:
qs = OrderPosition.objects qs = OrderPosition.objects
qs = qs.filter(order__event__organizer=self.request.organizer)
if self.request.query_params.get('pdf_data', 'false').lower() == 'true' and getattr(self.request, 'event', None): qs = qs.filter(order__event=self.request.event)
if self.request.query_params.get('pdf_data', 'false').lower() == 'true':
prefetch_related_objects([self.request.organizer], 'meta_properties') prefetch_related_objects([self.request.organizer], 'meta_properties')
prefetch_related_objects( prefetch_related_objects(
[self.request.event], [self.request.event],
@@ -1152,9 +1154,9 @@ class OrderPositionViewSetMixin:
qs = qs.prefetch_related( qs = qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.select_related("device")), Prefetch('checkins', queryset=Checkin.objects.select_related("device")),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')), Prefetch('print_logs', queryset=PrintLog.objects.select_related('device')),
'answers', 'answers__options', 'answers__question', 'order__event', 'order__event__organizer' 'answers', 'answers__options', 'answers__question',
).select_related( ).select_related(
'item', 'order', 'seat' 'item', 'order', 'order__event', 'order__event__organizer', 'seat'
) )
return qs return qs
@@ -1166,45 +1168,6 @@ class OrderPositionViewSetMixin:
return prov return prov
raise NotFound('Unknown output provider.') raise NotFound('Unknown output provider.')
class OrganizerOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrganizerOrderPositionSerializer
def get_queryset(self):
qs = super().get_queryset()
perm = self.permission if self.request.method in SAFE_METHODS else self.write_permission
if isinstance(self.request.auth, (TeamAPIToken, Device)):
auth_obj = self.request.auth
elif self.request.user.is_authenticated:
auth_obj = self.request.user
else:
raise PermissionDenied("Unknown authentication scheme")
qs = qs.filter(
order__event__in=auth_obj.get_events_with_permission(perm, request=self.request).filter(
organizer=self.request.organizer
)
)
return qs
class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet):
serializer_class = OrderPositionSerializer
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['event'] = self.request.event
ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true'
return ctx
def get_queryset(self):
qs = super().get_queryset()
qs = qs.filter(order__event=self.request.event)
return qs
@action(detail=True, methods=['POST'], url_name='price_calc') @action(detail=True, methods=['POST'], url_name='price_calc')
def price_calc(self, request, *args, **kwargs): def price_calc(self, request, *args, **kwargs):
""" """
+7 -28
View File
@@ -249,24 +249,12 @@ class GiftCardViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
value = serializer.validated_data.pop('value') value = serializer.validated_data.pop('value')
inst = serializer.save(issuer=self.request.organizer) inst = serializer.save(issuer=self.request.organizer)
inst.log_action(
action='pretix.giftcards.created',
user=self.request.user,
auth=self.request.auth,
)
inst.transactions.create(value=value, acceptor=self.request.organizer) inst.transactions.create(value=value, acceptor=self.request.organizer)
inst.log_action( inst.log_action(
action='pretix.giftcards.transaction.manual', 'pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data=merge_dicts( data=merge_dicts(self.request.data, {'id': inst.pk})
self.request.data,
{
'id': inst.pk,
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
)
) )
@transaction.atomic() @transaction.atomic()
@@ -281,7 +269,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency, inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
testmode=serializer.instance.testmode) testmode=serializer.instance.testmode)
inst.log_action( inst.log_action(
action='pretix.giftcards.modified', 'pretix.giftcards.modified',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data=self.request.data, data=self.request.data,
@@ -294,14 +282,10 @@ class GiftCardViewSet(viewsets.ModelViewSet):
diff = value - old_value diff = value - old_value
inst.transactions.create(value=diff, acceptor=self.request.organizer) inst.transactions.create(value=diff, acceptor=self.request.organizer)
inst.log_action( inst.log_action(
action='pretix.giftcards.transaction.manual', 'pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data={ data={'value': diff}
'value': diff,
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
) )
return inst return inst
@@ -325,15 +309,10 @@ class GiftCardViewSet(viewsets.ModelViewSet):
}, status=status.HTTP_409_CONFLICT) }, status=status.HTTP_409_CONFLICT)
gc.transactions.create(value=value, text=text, info=info, acceptor=self.request.organizer) gc.transactions.create(value=value, text=text, info=info, acceptor=self.request.organizer)
gc.log_action( gc.log_action(
action='pretix.giftcards.transaction.manual', 'pretix.giftcards.transaction.manual',
user=self.request.user, user=self.request.user,
auth=self.request.auth, auth=self.request.auth,
data={ data={'value': value, 'text': text}
'value': value,
'text': text,
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
) )
return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK) return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK)
-44
View File
@@ -174,38 +174,6 @@ class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
} }
class ParametrizedGiftcardWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
giftcard = logentry.content_object
if not giftcard:
return None
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'issuer_slug': logentry.organizer.slug,
'giftcard': giftcard.pk,
'action': logentry.action_type,
}
class ParametrizedGiftcardTransactionWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry):
giftcard = logentry.content_object
if not giftcard:
return None
return {
'notification_id': logentry.pk,
'issuer_id': logentry.organizer_id,
'issuer_slug': logentry.organizer.slug,
'acceptor_id': logentry.parsed_data.get('acceptor_id'),
'acceptor_slug': logentry.parsed_data.get('acceptor_slug'),
'giftcard': giftcard.pk,
'action': logentry.action_type,
}
class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent): class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent):
def build_payload(self, logentry: LogEntry): def build_payload(self, logentry: LogEntry):
@@ -465,18 +433,6 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.customer.anonymized', 'pretix.customer.anonymized',
_('Customer account anonymized'), _('Customer account anonymized'),
), ),
ParametrizedGiftcardWebhookEvent(
'pretix.giftcards.created',
_('Gift card added'),
),
ParametrizedGiftcardWebhookEvent(
'pretix.giftcards.modified',
_('Gift card modified'),
),
ParametrizedGiftcardTransactionWebhookEvent(
'pretix.giftcards.transaction.*',
_('Gift card used in transaction'),
)
) )
+2 -5
View File
@@ -216,10 +216,7 @@ class OutboundSyncProvider:
try: try:
mapped_objects = self.sync_order(sq.order) mapped_objects = self.sync_order(sq.order)
actions_taken = [res and res.sync_info.get("action", "") for res_list in mapped_objects.values() for res in res_list] if not all(all(not res or res.sync_info.get("action", "") == "nothing_to_do" for res in res_list) for res_list in mapped_objects.values()):
should_write_logentry = any(action not in (None, "nothing_to_do") for action in actions_taken)
logger.info('Synced order %s to %s, actions: %r, log: %r', sq.order.code, sq.sync_provider, actions_taken, should_write_logentry)
if should_write_logentry:
sq.order.log_action("pretix.event.order.data_sync.success", { sq.order.log_action("pretix.event.order.data_sync.success", {
"provider": self.identifier, "provider": self.identifier,
"objects": { "objects": {
@@ -240,7 +237,7 @@ class OutboundSyncProvider:
sq.set_sync_error("exceeded", e.messages, e.full_message) sq.set_sync_error("exceeded", e.messages, e.full_message)
else: else:
logger.info( logger.info(
f"Could not sync order {sq.order.code} to {sq.sync_provider} " f"Could not sync order {sq.order.code} to {type(self).__name__} "
f"(transient error, attempt #{sq.failed_attempts}, next {sq.not_before})", f"(transient error, attempt #{sq.failed_attempts}, next {sq.not_before})",
exc_info=True, exc_info=True,
) )
+7 -9
View File
@@ -39,7 +39,7 @@ from pretix.base.templatetags.rich_text import (
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback, DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
markdown_compile_email, truelink_callback, markdown_compile_email, truelink_callback,
) )
from pretix.helpers.format import FormattedString, SafeFormatter, format_map from pretix.helpers.format import SafeFormatter, format_map
from pretix.base.services.placeholders import ( # noqa from pretix.base.services.placeholders import ( # noqa
get_available_placeholders, PlaceholderContext get_available_placeholders, PlaceholderContext
@@ -141,7 +141,6 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
return markdown_compile_email(plaintext, context=context) return markdown_compile_email(plaintext, context=context)
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str: def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
apply_format_map = not isinstance(plain_body, FormattedString)
body_md = self.compile_markdown(plain_body, context) body_md = self.compile_markdown(plain_body, context)
if context: if context:
linker = bleach.Linker( linker = bleach.Linker(
@@ -150,13 +149,12 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback], callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True parse_email=True
) )
if apply_format_map: body_md = format_map(
body_md = format_map( body_md,
body_md, context=context,
context=context, mode=SafeFormatter.MODE_RICH_TO_HTML,
mode=SafeFormatter.MODE_RICH_TO_HTML, linkifier=linker
linkifier=linker )
)
htmlctx = { htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME, 'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL, 'site_url': settings.SITE_URL,
-2
View File
@@ -651,7 +651,6 @@ class OrderListExporter(MultiSheetListExporter):
pgettext('address', 'State'), pgettext('address', 'State'),
_('Voucher'), _('Voucher'),
_('Voucher budget usage'), _('Voucher budget usage'),
_('Voucher tag'),
_('Pseudonymization ID'), _('Pseudonymization ID'),
_('Ticket secret'), _('Ticket secret'),
_('Seat ID'), _('Seat ID'),
@@ -770,7 +769,6 @@ class OrderListExporter(MultiSheetListExporter):
op.state_for_address or '', op.state_for_address or '',
op.voucher.code if op.voucher else '', op.voucher.code if op.voucher else '',
op.voucher_budget_use if op.voucher_budget_use else '', op.voucher_budget_use if op.voucher_budget_use else '',
op.voucher.tag if op.voucher else '',
op.pseudonymization_id, op.pseudonymization_id,
op.secret, op.secret,
] ]
+6 -6
View File
@@ -890,18 +890,18 @@ class BaseQuestionsForm(forms.Form):
if not help_text: if not help_text:
if q.valid_date_min and q.valid_date_max: if q.valid_date_min and q.valid_date_max:
help_text = format_lazy( help_text = format_lazy(
_('Please enter a date between {min} and {max}.'), 'Please enter a date between {min} and {max}.',
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"), min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"), max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
) )
elif q.valid_date_min: elif q.valid_date_min:
help_text = format_lazy( help_text = format_lazy(
_('Please enter a date no earlier than {min}.'), 'Please enter a date no earlier than {min}.',
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"), min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
) )
elif q.valid_date_max: elif q.valid_date_max:
help_text = format_lazy( help_text = format_lazy(
_('Please enter a date no later than {max}.'), 'Please enter a date no later than {max}.',
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"), max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
) )
if initial and initial.answer: if initial and initial.answer:
@@ -939,18 +939,18 @@ class BaseQuestionsForm(forms.Form):
if not help_text: if not help_text:
if q.valid_datetime_min and q.valid_datetime_max: if q.valid_datetime_min and q.valid_datetime_max:
help_text = format_lazy( help_text = format_lazy(
_('Please enter a date and time between {min} and {max}.'), 'Please enter a date and time between {min} and {max}.',
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"), min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"), max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
) )
elif q.valid_datetime_min: elif q.valid_datetime_min:
help_text = format_lazy( help_text = format_lazy(
_('Please enter a date and time no earlier than {min}.'), 'Please enter a date and time no earlier than {min}.',
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"), min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
) )
elif q.valid_datetime_max: elif q.valid_datetime_max:
help_text = format_lazy( help_text = format_lazy(
_('Please enter a date and time no later than {max}.'), 'Please enter a date and time no later than {max}.',
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"), max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
) )
+1 -10
View File
@@ -42,8 +42,6 @@ from django.utils.html import escape
from django.utils.timezone import get_current_timezone, now from django.utils.timezone import get_current_timezone, now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from pretix.helpers.format import PlainHtmlAlternativeString
def replace_arabic_numbers(inp): def replace_arabic_numbers(inp):
if not isinstance(inp, str): if not isinstance(inp, str):
@@ -63,18 +61,11 @@ def replace_arabic_numbers(inp):
return inp.translate(table) return inp.translate(table)
def format_placeholder_help_text(placeholder_name, sample_value):
if isinstance(sample_value, PlainHtmlAlternativeString):
sample_value = sample_value.plain
title = (_("Sample: %s") % sample_value) if sample_value else ""
return ('<button type="button" class="content-placeholder" title="%s">{%s}</button>' % (escape(title), escape(placeholder_name)))
def format_placeholders_help_text(placeholders, event=None): def format_placeholders_help_text(placeholders, event=None):
placeholders = [(k, v.render_sample(event) if event else v) for k, v in placeholders.items()] placeholders = [(k, v.render_sample(event) if event else v) for k, v in placeholders.items()]
placeholders.sort(key=lambda x: x[0]) placeholders.sort(key=lambda x: x[0])
phs = [ phs = [
format_placeholder_help_text(k, v) '<button type="button" class="content-placeholder" title="%s">{%s}</button>' % (escape(_("Sample: %s") % v) if v else "", escape(k))
for k, v in placeholders for k, v in placeholders
] ]
return _('Available placeholders: {list}').format( return _('Available placeholders: {list}').format(
-4
View File
@@ -148,10 +148,6 @@ class NumberedCanvas(Canvas):
self.restoreState() self.restoreState()
class InvoiceNotReadyException(Exception):
pass
class BaseInvoiceRenderer: class BaseInvoiceRenderer:
""" """
This is the base class for all invoice renderers. This is the base class for all invoice renderers.
-17
View File
@@ -21,7 +21,6 @@
# #
from typing import Optional from typing import Optional
from django.utils.translation import gettext_lazy as _
from django_countries.fields import Country from django_countries.fields import Country
from pretix.base.models import Invoice, InvoiceAddress from pretix.base.models import Invoice, InvoiceAddress
@@ -107,22 +106,6 @@ class TransmissionType:
def transmission_info_to_form_data(self, transmission_info: dict) -> dict: def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
return transmission_info return transmission_info
def describe_info(self, transmission_info: dict, country: Country, is_business: bool):
form_data = self.transmission_info_to_form_data(transmission_info)
data = []
visible_field_keys = self.invoice_address_form_fields_visible(country, is_business)
for k, f in self.invoice_address_form_fields.items():
if k not in visible_field_keys:
continue
v = form_data.get(k)
if v is True:
v = _("Yes")
elif v is False:
v = _("No")
if v:
data.append((f.label, v))
return data
def pdf_watermark(self) -> Optional[str]: def pdf_watermark(self) -> Optional[str]:
""" """
Return a watermark that should be rendered across the PDF file. Return a watermark that should be rendered across the PDF file.
+3 -3
View File
@@ -132,7 +132,7 @@ class AllowIgnoreQuotaColumn(BooleanColumnMixin, ImportColumn):
class PriceModeColumn(ImportColumn): class PriceModeColumn(ImportColumn):
identifier = 'price_mode' identifier = 'price_mode'
verbose_name = gettext_lazy('Price effect') verbose_name = gettext_lazy('Price mode')
default_value = None default_value = None
initial = 'static:none' initial = 'static:none'
@@ -147,7 +147,7 @@ class PriceModeColumn(ImportColumn):
elif value in reverse: elif value in reverse:
return reverse[value] return reverse[value]
else: else:
raise ValidationError(_("Could not parse {value} as a price effect, use one of {options}.").format( raise ValidationError(_("Could not parse {value} as a price mode, use one of {options}.").format(
value=value, options=', '.join(d.keys()) value=value, options=', '.join(d.keys())
)) ))
@@ -162,7 +162,7 @@ class ValueColumn(DecimalColumnMixin, ImportColumn):
def clean(self, value, previous_values): def clean(self, value, previous_values):
value = super().clean(value, previous_values) value = super().clean(value, previous_values)
if value and previous_values.get("price_mode") == "none": if value and previous_values.get("price_mode") == "none":
raise ValidationError(_("It is pointless to set a value without a price effect.")) raise ValidationError(_("It is pointless to set a value without a price mode."))
return value return value
def assign(self, value, obj: Voucher, **kwargs): def assign(self, value, obj: Voucher, **kwargs):
+1 -4
View File
@@ -346,8 +346,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
{ {
'user': self, 'user': self,
'messages': msg, 'messages': msg,
'url': build_absolute_uri('control:user.settings'), 'url': build_absolute_uri('control:user.settings')
'instance': settings.PRETIX_INSTANCE_NAME,
}, },
event=None, event=None,
user=self, user=self,
@@ -392,7 +391,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
'user': self, 'user': self,
'reason': msg, 'reason': msg,
'code': code, 'code': code,
'instance': settings.PRETIX_INSTANCE_NAME,
}, },
event=None, event=None,
user=self, user=self,
@@ -432,7 +430,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
mail( mail(
self.email, _('Password recovery'), 'pretixcontrol/email/forgot.txt', self.email, _('Password recovery'), 'pretixcontrol/email/forgot.txt',
{ {
'instance': settings.PRETIX_INSTANCE_NAME,
'user': self, 'user': self,
'url': (build_absolute_uri('control:auth.forgot.recover') 'url': (build_absolute_uri('control:auth.forgot.recover')
+ '?id=%d&token=%s' % (self.id, default_token_generator.make_token(self))) + '?id=%d&token=%s' % (self.id, default_token_generator.make_token(self)))
-2
View File
@@ -130,8 +130,6 @@ class LoggingMixin:
organizer_id = self.event.organizer_id organizer_id = self.event.organizer_id
elif hasattr(self, 'organizer_id'): elif hasattr(self, 'organizer_id'):
organizer_id = self.organizer_id organizer_id = self.organizer_id
elif hasattr(self, 'issuer_id'):
organizer_id = self.issuer_id
if user and not user.is_authenticated: if user and not user.is_authenticated:
user = None user = None
-23
View File
@@ -40,7 +40,6 @@ from i18nfield.fields import I18nCharField
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.banlist import banned from pretix.base.banlist import banned
from pretix.base.i18n import language
from pretix.base.models.base import LoggedModel from pretix.base.models.base import LoggedModel
from pretix.base.models.fields import MultiStringField from pretix.base.models.fields import MultiStringField
from pretix.base.models.giftcards import GiftCardTransaction from pretix.base.models.giftcards import GiftCardTransaction
@@ -165,28 +164,6 @@ class Customer(LoggedModel):
self.attendee_profiles.all().delete() self.attendee_profiles.all().delete()
self.invoice_addresses.all().delete() self.invoice_addresses.all().delete()
def send_security_notice(self, message, email=None):
from pretix.base.services.mail import SendMailException, mail
from pretix.multidomain.urlreverse import build_absolute_uri
try:
with language(self.locale):
mail(
email or self.email,
self.organizer.settings.mail_subject_customer_security_notice,
self.organizer.settings.mail_text_customer_security_notice,
{
**self.get_email_context(),
'message': str(message),
'url': build_absolute_uri(self.organizer, 'presale:organizer.customer.index')
},
customer=self,
organizer=self.organizer,
locale=self.locale
)
except SendMailException:
pass # Already logged
@scopes_disabled() @scopes_disabled()
def assign_identifier(self): def assign_identifier(self):
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ23456789') charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ23456789')
+1 -1
View File
@@ -86,7 +86,7 @@ class OrderSyncQueue(models.Model):
def set_sync_error(self, failure_mode, messages, full_message): def set_sync_error(self, failure_mode, messages, full_message):
logger.exception( logger.exception(
f"Could not sync order {self.order.code} to {self.sync_provider} ({failure_mode})" f"Could not sync order {self.order.code} to {type(self).__name__} ({failure_mode})"
) )
self.order.log_action(f"pretix.event.order.data_sync.failed.{failure_mode}", { self.order.log_action(f"pretix.event.order.data_sync.failed.{failure_mode}", {
"provider": self.sync_provider, "provider": self.sync_provider,
+13 -7
View File
@@ -87,7 +87,7 @@ from pretix.base.timemachine import time_machine_now
from ...helpers import OF_SELF from ...helpers import OF_SELF
from ...helpers.countries import CachedCountries, FastCountryField from ...helpers.countries import CachedCountries, FastCountryField
from ...helpers.format import FormattedString, format_map from ...helpers.format import format_map
from ...helpers.names import build_name from ...helpers.names import build_name
from ...testutils.middleware import debugflags_var from ...testutils.middleware import debugflags_var
from ._transactions import ( from ._transactions import (
@@ -1178,8 +1178,7 @@ class Order(LockModel, LoggedModel):
recipient = position.attendee_email recipient = position.attendee_email
email_content = render_mail(template, context) email_content = render_mail(template, context)
if not isinstance(subject, FormattedString): subject = format_map(subject, context)
subject = format_map(subject, context)
mail( mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.locale, self, headers=headers, sender=sender, self.event, self.locale, self, headers=headers, sender=sender,
@@ -2908,8 +2907,7 @@ class OrderPosition(AbstractPosition):
with language(self.order.locale, self.order.event.settings.region): with language(self.order.locale, self.order.event.settings.region):
recipient = self.attendee_email recipient = self.attendee_email
email_content = render_mail(template, context) email_content = render_mail(template, context)
if not isinstance(subject, FormattedString): subject = format_map(subject, context)
subject = format_map(subject, context)
mail( mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender, self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
@@ -3509,10 +3507,18 @@ class InvoiceAddress(models.Model):
def describe_transmission(self): def describe_transmission(self):
from pretix.base.invoicing.transmission import transmission_types from pretix.base.invoicing.transmission import transmission_types
data = [] data = []
t, __ = transmission_types.get(identifier=self.transmission_type) t, __ = transmission_types.get(identifier=self.transmission_type)
data.append((_("Transmission type"), t.public_name)) data.append((_("Transmission type"), t.public_name))
if self.transmission_info: form_data = t.transmission_info_to_form_data(self.transmission_info or {})
data += t.describe_info(self.transmission_info, self.country, self.is_business) for k, f in t.invoice_address_form_fields.items():
v = form_data.get(k)
if v is True:
v = _("Yes")
elif v is False:
v = _("No")
if v:
data.append((f.label, v))
return data return data
+1 -1
View File
@@ -239,7 +239,7 @@ class Voucher(LoggedModel):
) )
) )
price_mode = models.CharField( price_mode = models.CharField(
verbose_name=_("Price effect"), verbose_name=_("Price mode"),
max_length=100, max_length=100,
choices=PRICE_MODES, choices=PRICE_MODES,
default='none' default='none'
+2 -3
View File
@@ -181,11 +181,10 @@ class WaitingListEntry(LoggedModel):
block_quota=True, block_quota=True,
item_id=self.item_id, item_id=self.item_id,
subevent_id=self.subevent_id, subevent_id=self.subevent_id,
waitinglistentries__isnull=False, waitinglistentries__isnull=False
seat__isnull=True
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0 ).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
free_seats = num_free_seats_for_product - num_valid_vouchers_for_product free_seats = num_free_seats_for_product - num_valid_vouchers_for_product
if free_seats < 1: if not free_seats:
raise WaitingListException(_('No seat with this product is currently available.')) raise WaitingListException(_('No seat with this product is currently available.'))
if '@' not in self.email: if '@' not in self.email:
-17
View File
@@ -1646,14 +1646,6 @@ class GiftCardPayment(BasePaymentProvider):
'transaction_id': trans.pk, 'transaction_id': trans.pk,
} }
payment.confirm(send_mail=not is_early_special_case, generate_invoice=not is_early_special_case) payment.confirm(send_mail=not is_early_special_case, generate_invoice=not is_early_special_case)
gc.log_action(
action='pretix.giftcards.transaction.payment',
data={
'value': trans.value,
'acceptor_id': self.event.organizer.id,
'acceptor_slug': self.event.organizer.slug
}
)
except PaymentException as e: except PaymentException as e:
payment.fail(info={'error': str(e)}) payment.fail(info={'error': str(e)})
raise e raise e
@@ -1678,15 +1670,6 @@ class GiftCardPayment(BasePaymentProvider):
'transaction_id': trans.pk, 'transaction_id': trans.pk,
} }
refund.done() refund.done()
gc.log_action(
action='pretix.giftcards.transaction.refund',
data={
'value': refund.amount,
'acceptor_id': self.event.organizer.id,
'acceptor_slug': self.event.organizer.slug,
'text': refund.comment,
}
)
@receiver(register_payment_providers, dispatch_uid="payment_free") @receiver(register_payment_providers, dispatch_uid="payment_free")
+1 -1
View File
@@ -65,7 +65,7 @@ def get_all_plugins(*, event=None, organizer=None) -> List[type]:
if app.name in settings.PRETIX_PLUGINS_EXCLUDE: if app.name in settings.PRETIX_PLUGINS_EXCLUDE:
continue continue
level = getattr(meta, "level", PLUGIN_LEVEL_EVENT) level = getattr(app, "level", PLUGIN_LEVEL_EVENT)
if level == PLUGIN_LEVEL_EVENT: if level == PLUGIN_LEVEL_EVENT:
if event and hasattr(app, 'is_available'): if event and hasattr(app, 'is_available'):
if not app.is_available(event): if not app.is_available(event):
+1 -2
View File
@@ -51,7 +51,6 @@ from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString from i18nfield.strings import LazyI18nString
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.invoicing.pdf import InvoiceNotReadyException
from pretix.base.invoicing.transmission import ( from pretix.base.invoicing.transmission import (
get_transmission_types, transmission_providers, get_transmission_types, transmission_providers,
) )
@@ -505,7 +504,7 @@ def generate_invoice(order: Order, trigger_pdf=True):
return invoice return invoice
@app.task(base=TransactionAwareTask, throws=(InvoiceNotReadyException,)) @app.task(base=TransactionAwareTask)
def invoice_pdf_task(invoice: int): def invoice_pdf_task(invoice: int):
with scopes_disabled(): with scopes_disabled():
i = Invoice.objects.get(pk=invoice) i = Invoice.objects.get(pk=invoice)
+32 -67
View File
@@ -81,9 +81,7 @@ from pretix.base.signals import (
) )
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from pretix.helpers.format import ( from pretix.helpers.format import SafeFormatter, format_map
FormattedString, PlainHtmlAlternativeString, SafeFormatter, format_map,
)
from pretix.helpers.hierarkey import clean_filename from pretix.helpers.hierarkey import clean_filename
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.presale.ical import get_private_icals from pretix.presale.ical import get_private_icals
@@ -220,9 +218,6 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
if email == INVALID_ADDRESS: if email == INVALID_ADDRESS:
return return
if isinstance(template, FormattedString):
raise TypeError("Cannot pass an already formatted body template")
if no_order_links and not plain_text_only: 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.') raise ValueError('If you set no_order_links, you also need to set plain_text_only.')
@@ -256,28 +251,23 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
if event and attach_tickets and not event.settings.mail_attach_tickets: if event and attach_tickets and not event.settings.mail_attach_tickets:
attach_tickets = False attach_tickets = False
with language(locale), override(timezone): with language(locale):
if isinstance(context, dict) and order: if isinstance(context, dict) and order:
_autoextend_context(context, order) _autoextend_context(context, order)
# Build raw content # Build raw content
content_plain = render_mail(template, context, placeholder_mode=None) body_plain = render_mail(template, context, placeholder_mode=SafeFormatter.MODE_RICH_TO_PLAIN)
if settings_holder: if settings_holder:
signature = str(settings_holder.settings.get('mail_text_signature')) signature = str(settings_holder.settings.get('mail_text_signature'))
else: else:
signature = "" signature = ""
# Build full plain-text body # Build full plain-text body
if not isinstance(content_plain, FormattedString):
body_plain = format_map(content_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
else:
body_plain = content_plain
body_plain = _wrap_plain_body(body_plain, signature, event, order, position, no_order_links) body_plain = _wrap_plain_body(body_plain, signature, event, order, position, no_order_links)
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
# Build subject # Build subject
if not isinstance(subject, FormattedString): subject = str(subject).format_map(TolerantDict(context))
subject = format_map(subject, context)
subject = raw_subject = subject.replace('\n', ' ').replace('\r', '')[:900] subject = raw_subject = subject.replace('\n', ' ').replace('\r', '')[:900]
if settings_holder: if settings_holder:
subject = prefix_subject(settings_holder, subject) subject = prefix_subject(settings_holder, subject)
@@ -296,24 +286,26 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
else: else:
renderer = ClassicMailRenderer(None, organizer) renderer = ClassicMailRenderer(None, organizer)
try: with override(timezone):
if 'context' in inspect.signature(renderer.render).parameters: content_plain = render_mail(template, context, placeholder_mode=None)
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context) try:
elif 'position' in inspect.signature(renderer.render).parameters: if 'context' in inspect.signature(renderer.render).parameters:
# Backwards compatibility body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
warnings.warn('Email renderer called without context argument because context argument is not ' elif 'position' in inspect.signature(renderer.render).parameters:
'supported.', # Backwards compatibility
DeprecationWarning) warnings.warn('Email renderer called without context argument because context argument is not '
body_html = renderer.render(content_plain, signature, raw_subject, order, position) 'supported.',
else: DeprecationWarning)
# Backwards compatibility body_html = renderer.render(content_plain, signature, raw_subject, order, position)
warnings.warn('Email renderer called without position argument because position argument is not ' else:
'supported.', # Backwards compatibility
DeprecationWarning) warnings.warn('Email renderer called without position argument because position argument is not '
body_html = renderer.render(content_plain, signature, raw_subject, order) 'supported.',
except: DeprecationWarning)
logger.exception('Could not render HTML body') body_html = renderer.render(content_plain, signature, raw_subject, order)
body_html = None except:
logger.exception('Could not render HTML body')
body_html = None
m = OutgoingMail.objects.create( m = OutgoingMail.objects.create(
organizer=organizer, organizer=organizer,
@@ -389,7 +381,7 @@ def mail_send_task(self, **kwargs) -> bool:
# mail_send_task(self, *, outgoing_mail) # mail_send_task(self, *, outgoing_mail)
with scopes_disabled(): with scopes_disabled():
mail_send(**kwargs) mail_send(**kwargs)
return False return
else: else:
raise ValueError("Unknown arguments") raise ValueError("Unknown arguments")
@@ -409,18 +401,6 @@ def mail_send_task(self, **kwargs) -> bool:
outgoing_mail.inflight_since = now() outgoing_mail.inflight_since = now()
outgoing_mail.save(update_fields=["status", "inflight_since"]) outgoing_mail.save(update_fields=["status", "inflight_since"])
# Performance optimization, saves database queries later on if we resolve the known relationships
if outgoing_mail.event_id:
assert outgoing_mail.event.organizer_id == outgoing_mail.organizer.pk
outgoing_mail.event.organizer = outgoing_mail.organizer
if outgoing_mail.order_id:
assert outgoing_mail.order.event_id == outgoing_mail.event_id
outgoing_mail.order.event = outgoing_mail.event
outgoing_mail.order.organizer = outgoing_mail.organizer
if outgoing_mail.orderposition_id:
assert outgoing_mail.orderposition.order_id == outgoing_mail.order_id
outgoing_mail.orderposition.order = outgoing_mail.order
headers = dict(outgoing_mail.headers) headers = dict(outgoing_mail.headers)
headers.setdefault('X-PX-Correlation', str(outgoing_mail.guid)) headers.setdefault('X-PX-Correlation', str(outgoing_mail.guid))
email = CustomEmail( email = CustomEmail(
@@ -455,24 +435,15 @@ def mail_send_task(self, **kwargs) -> bool:
content = ct.file.read() content = ct.file.read()
args.append((name, content, ct.type)) args.append((name, content, ct.type))
attach_size += len(content) attach_size += len(content)
except Exception as e: except Exception:
# This sometimes fails e.g. with FileNotFoundError. We haven't been able to figure out # 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. # why (probably some race condition with ticket cache invalidation?), so retry later.
try: try:
logger.exception(f'Could not attach tickets to email {outgoing_mail.guid}, will retry') self.retry(max_retries=5, countdown=60)
retry_after = 60
outgoing_mail.error = "Tickets not ready"
outgoing_mail.error_detail = str(e)
outgoing_mail.sent = now()
outgoing_mail.status = OutgoingMail.STATUS_AWAITING_RETRY
outgoing_mail.retry_after = now() + timedelta(seconds=retry_after)
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after",
"actual_attachments"])
self.retry(max_retries=5, countdown=retry_after)
except MaxRetriesExceededError: except MaxRetriesExceededError:
# Well then, something is really wrong, let's send it without attachment before we # Well then, something is really wrong, let's send it without attachment before we
# don't send at all # don't send at all
logger.exception(f'Too many retries attaching tickets to email {outgoing_mail.guid}, skip attachment') logger.exception(f'Could not attach tickets to email {outgoing_mail.guid}')
pass pass
if attach_size * 1.37 < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1024 * 1024: if attach_size * 1.37 < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1024 * 1024:
@@ -548,7 +519,6 @@ def mail_send_task(self, **kwargs) -> bool:
) )
except InvoiceAddress.DoesNotExist: except InvoiceAddress.DoesNotExist:
pass pass
expected_recipients = {e.lower() for e in expected_recipients if e}
if any(t in expected_recipients for t in outgoing_mail.to): if any(t in expected_recipients for t in outgoing_mail.to):
invoices_to_mark_transmitted.append(inv) invoices_to_mark_transmitted.append(inv)
@@ -819,12 +789,7 @@ def render_mail(template, context, placeholder_mode: Optional[int]=SafeFormatter
body = format_map(body, context, mode=placeholder_mode) body = format_map(body, context, mode=placeholder_mode)
else: else:
tpl = get_template(template) tpl = get_template(template)
context = { body = tpl.render(context)
# Known bug, should behave differently for plain and HTML but we'll fix after security release
k: v.html if isinstance(v, PlainHtmlAlternativeString) else v
for k, v in context.items()
}
body = FormattedString(tpl.render(context))
return body return body
@@ -970,7 +935,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
body_plain += "\r\n\r\n-- \r\n" body_plain += "\r\n\r\n-- \r\n"
if signature: if signature:
signature = format_map(signature, {"event": event.name if event else ''}) signature = signature.format(event=event.name if event else '')
body_plain += signature body_plain += signature
body_plain += "\r\n\r\n-- \r\n" body_plain += "\r\n\r\n-- \r\n"
@@ -982,7 +947,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
body_plain += _( body_plain += _(
"You can view your order details at the following URL:\n{orderurl}." "You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format( ).replace("\n", "\r\n").format(
orderurl=build_absolute_uri( event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order.position', kwargs={ order.event, 'presale:event.order.position', kwargs={
'order': order.code, 'order': order.code,
'secret': position.web_secret, 'secret': position.web_secret,
+2 -55
View File
@@ -247,16 +247,6 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
for gc in position.issued_gift_cards.all(): for gc in position.issued_gift_cards.all():
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=gc.pk) gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=gc.pk)
gc.transactions.create(value=position.price, order=order, acceptor=order.event.organizer) gc.transactions.create(value=position.price, order=order, acceptor=order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=user,
auth=auth,
data={
'value': position.price,
'acceptor_id': order.event.organizer.id,
'acceptor_slug': order.event.organizer.slug
}
)
break break
for m in position.granted_memberships.all(): for m in position.granted_memberships.all():
@@ -558,15 +548,6 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
) )
else: else:
gc.transactions.create(value=-position.price, order=order, acceptor=order.event.organizer) gc.transactions.create(value=-position.price, order=order, acceptor=order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=user,
data={
'value': -position.price,
'acceptor_id': order.event.organizer.id,
'acceptor_slug': order.event.organizer.slug
}
)
for m in position.granted_memberships.all(): for m in position.granted_memberships.all():
m.canceled = True m.canceled = True
@@ -2453,16 +2434,6 @@ class OrderChangeManager:
)) ))
else: else:
gc.transactions.create(value=-position.price, order=self.order, acceptor=self.order.event.organizer) gc.transactions.create(value=-position.price, order=self.order, acceptor=self.order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=self.user,
auth=self.auth,
data={
'value': -position.price,
'acceptor_id': self.order.event.organizer.id,
'acceptor_slug': self.order.event.organizer.slug
}
)
for m in position.granted_memberships.with_usages().all(): for m in position.granted_memberships.with_usages().all():
m.canceled = True m.canceled = True
@@ -2480,16 +2451,6 @@ class OrderChangeManager:
)) ))
else: else:
gc.transactions.create(value=-opa.position.price, order=self.order, acceptor=self.order.event.organizer) gc.transactions.create(value=-opa.position.price, order=self.order, acceptor=self.order.event.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
user=self.user,
auth=self.auth,
data={
'value': -opa.position.price,
'acceptor_id': self.order.event.organizer.id,
'acceptor_slug': self.order.event.organizer.slug
}
)
for m in opa.granted_memberships.with_usages().all(): for m in opa.granted_memberships.with_usages().all():
m.canceled = True m.canceled = True
@@ -3158,10 +3119,7 @@ def _try_auto_refund(order, auto_refund=True, manual_refund=False, allow_partial
customer=order.customer, customer=order.customer,
testmode=order.testmode testmode=order.testmode
) )
giftcard.log_action( giftcard.log_action('pretix.giftcards.created', data={})
action='pretix.giftcards.created',
data={}
)
r = order.refunds.create( r = order.refunds.create(
order=order, order=order,
payment=None, payment=None,
@@ -3448,18 +3406,7 @@ def signal_listener_issue_giftcards(sender: Event, order: Order, **kwargs):
currency=sender.currency, issued_in=p, testmode=order.testmode, currency=sender.currency, issued_in=p, testmode=order.testmode,
expires=sender.organizer.default_gift_card_expiry, expires=sender.organizer.default_gift_card_expiry,
) )
gc.log_action( gc.transactions.create(value=p.price - issued, order=order, acceptor=sender.organizer)
action='pretix.giftcards.created',
)
trans = gc.transactions.create(value=p.price - issued, order=order, acceptor=sender.organizer)
gc.log_action(
action='pretix.giftcards.transaction.manual',
data={
'value': trans.value,
'acceptor_id': order.event.organizer.id,
'acceptor_slug': order.event.organizer.slug
}
)
any_giftcards = True any_giftcards = True
p.secret = gc.secret p.secret = gc.secret
p.save(update_fields=['secret']) p.save(update_fields=['secret'])
-7
View File
@@ -24,7 +24,6 @@ import logging
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from django.db.models import Prefetch, prefetch_related_objects
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.html import escape, mark_safe from django.utils.html import escape, mark_safe
@@ -36,7 +35,6 @@ from pretix.base.forms.widgets import format_placeholders_help_text
from pretix.base.i18n import ( from pretix.base.i18n import (
LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber, LazyCurrencyNumber, LazyDate, LazyExpiresDate, LazyNumber,
) )
from pretix.base.models import EventMetaValue
from pretix.base.reldate import RelativeDateWrapper from pretix.base.reldate import RelativeDateWrapper
from pretix.base.settings import PERSON_NAME_SCHEMES, get_name_parts_localized from pretix.base.settings import PERSON_NAME_SCHEMES, get_name_parts_localized
from pretix.base.signals import ( from pretix.base.signals import (
@@ -754,11 +752,6 @@ def base_placeholders(sender, **kwargs):
name_scheme['sample'][f] name_scheme['sample'][f]
)) ))
prefetch_related_objects(
[sender],
Prefetch('meta_values', queryset=EventMetaValue.objects.select_related("property"), to_attr="meta_values_cached")
)
prefetch_related_objects([sender.organizer], Prefetch('meta_properties'))
for k, v in sender.meta_data.items(): for k, v in sender.meta_data.items():
ph.append(MarkdownTextPlaceholder( ph.append(MarkdownTextPlaceholder(
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k], 'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],
-1
View File
@@ -176,7 +176,6 @@ def shred(self, event: Event, fileid: str, confirm_code: str, user: int=None, lo
_('Data shredding completed'), _('Data shredding completed'),
'pretixbase/email/shred_completed.txt', 'pretixbase/email/shred_completed.txt',
{ {
'instance': settings.PRETIX_INSTANCE_NAME,
'user': user, 'user': user,
'organizer': event.organizer.name, 'organizer': event.organizer.name,
'event': str(event.name), 'event': str(event.name),
-22
View File
@@ -2948,28 +2948,6 @@ If you did not request a new password, please ignore this email.
Best regards, Best regards,
Your {organizer} team""")) # noqa: W291
},
'mail_subject_customer_security_notice': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("Changes to your account at {organizer}")),
},
'mail_text_customer_security_notice': {
'type': LazyI18nString,
'default': LazyI18nString.from_gettext(gettext_noop("""Hello {name},
the following change has been made to your account at {organizer}:
{message}
You can review and change your account settings here:
{url}
If this change was not performed by you, please contact us immediately.
Best regards,
Your {organizer} team""")) # noqa: W291 Your {organizer} team""")) # noqa: W291
}, },
'smtp_use_custom': { 'smtp_use_custom': {
+3
View File
@@ -8,6 +8,9 @@
<h1>{% trans "Not found" %}</h1> <h1>{% trans "Not found" %}</h1>
<p>{% trans "I'm afraid we could not find the the resource you requested." %}</p> <p>{% trans "I'm afraid we could not find the the resource you requested." %}</p>
<p>{{ exception }}</p> <p>{{ exception }}</p>
<p class="links">
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
</p>
{% if request.user.is_staff and not staff_session %} {% if request.user.is_staff and not staff_session %}
<form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post"> <form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post">
<p> <p>
-3
View File
@@ -12,9 +12,6 @@
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}"> <link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}">
{% block custom_header %}{% endblock %} {% block custom_header %}{% endblock %}
{% if css_theme %}
<link rel="stylesheet" type="text/css" href="{{ css_theme }}" />
{% endif %}
</head> </head>
<body> <body>
<div class="container"> <div class="container">
@@ -13,5 +13,5 @@ Start time: {{ start_time }} (new data added after this time might not have been
Best regards, Best regards,
Your {{ instance }} team Your pretix team
{% endblocktrans %} {% endblocktrans %}
@@ -1,34 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-today pretix GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django import template
from django.utils.html import mark_safe
register = template.Library()
@register.filter("anon_email")
def anon_email(value):
"""Replaces @ with [at] and . with [dot] for anonymization."""
if not isinstance(value, str):
return value
value = value.replace("@", "[at]").replace(".", "[dot]")
return mark_safe(''.join(['&#{0};'.format(ord(char)) for char in value]))
+1 -1
View File
@@ -423,7 +423,7 @@ def resolve_timeframe_to_dates_inclusive(ref_dt, frame, timezone) -> Tuple[Optio
raise ValueError(f"Invalid timeframe '{frame}'") raise ValueError(f"Invalid timeframe '{frame}'")
def resolve_timeframe_to_datetime_start_inclusive_end_exclusive(ref_dt, frame, timezone) -> Tuple[Optional[datetime], Optional[datetime]]: def resolve_timeframe_to_datetime_start_inclusive_end_exclusive(ref_dt, frame, timezone) -> Tuple[Optional[date], Optional[date]]:
""" """
Given a serialized timeframe, evaluate it relative to `ref_dt` and return a tuple of datetimes Given a serialized timeframe, evaluate it relative to `ref_dt` and return a tuple of datetimes
where the first element ist the first possible datetime within the timeframe and the second where the first element ist the first possible datetime within the timeframe and the second
-1
View File
@@ -95,7 +95,6 @@ class OrganizerSlugBanlistValidator(BanlistValidator):
'csp_report', 'csp_report',
'widget', 'widget',
'lead', 'lead',
'scheduling',
] ]
+1 -14
View File
@@ -20,7 +20,6 @@
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
import pycountry import pycountry
from django.conf import settings
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext, pgettext, pgettext_lazy from django.utils.translation import gettext, pgettext, pgettext_lazy
@@ -30,7 +29,6 @@ from django_scopes import scope
from pretix.base.addressvalidation import ( from pretix.base.addressvalidation import (
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED, COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED,
) )
from pretix.base.i18n import language
from pretix.base.invoicing.transmission import get_transmission_types from pretix.base.invoicing.transmission import get_transmission_types
from pretix.base.models import Organizer from pretix.base.models import Organizer
from pretix.base.models.tax import VAT_ID_COUNTRIES from pretix.base.models.tax import VAT_ID_COUNTRIES
@@ -91,7 +89,7 @@ def _info(cc):
} }
def _address_form(request): def address_form(request):
cc = request.GET.get("country", "DE") cc = request.GET.get("country", "DE")
info = _info(cc) info = _info(cc)
@@ -159,15 +157,4 @@ def _address_form(request):
# The help text explains that it is optional, so we want to hide that if it is required # The help text explains that it is optional, so we want to hide that if it is required
info["vat_id"]["helptext_visible"] = False info["vat_id"]["helptext_visible"] = False
return info
def address_form(request):
locale = request.GET.get('locale')
if locale in dict(settings.LANGUAGES):
with language(locale):
info = _address_form(request)
else:
info = _address_form(request)
return JsonResponse(info) return JsonResponse(info)
+2 -2
View File
@@ -2820,8 +2820,8 @@ class DeviceFilterForm(FilterForm):
class OutgoingMailFilterForm(FilterForm): class OutgoingMailFilterForm(FilterForm):
orders = { orders = {
'date': 'created', 'date': 'd',
'-date': '-created', '-date': '-d',
} }
query = forms.CharField( query = forms.CharField(
label=_('Search email address or subject'), label=_('Search email address or subject'),
-15
View File
@@ -635,16 +635,6 @@ class MailSettingsForm(SettingsForm):
required=False, required=False,
widget=I18nMarkdownTextarea, widget=I18nMarkdownTextarea,
) )
mail_subject_customer_security_notice = I18nFormField(
label=_("Subject"),
required=False,
widget=I18nTextInput,
)
mail_text_customer_security_notice = I18nFormField(
label=_("Text"),
required=False,
widget=I18nMarkdownTextarea,
)
base_context = { base_context = {
'mail_text_customer_registration': ['customer', 'url'], 'mail_text_customer_registration': ['customer', 'url'],
@@ -653,8 +643,6 @@ class MailSettingsForm(SettingsForm):
'mail_subject_customer_email_change': ['customer', 'url'], 'mail_subject_customer_email_change': ['customer', 'url'],
'mail_text_customer_reset': ['customer', 'url'], 'mail_text_customer_reset': ['customer', 'url'],
'mail_subject_customer_reset': ['customer', 'url'], 'mail_subject_customer_reset': ['customer', 'url'],
'mail_text_customer_security_notice': ['customer', 'url', 'message'],
'mail_subject_customer_security_notice': ['customer', 'url', 'message'],
} }
def _get_sample_context(self, base_parameters): def _get_sample_context(self, base_parameters):
@@ -668,9 +656,6 @@ class MailSettingsForm(SettingsForm):
'presale:organizer.customer.activate' 'presale:organizer.customer.activate'
) + '?token=' + get_random_string(30) ) + '?token=' + get_random_string(30)
if 'message' in base_parameters:
placeholders['message'] = _('Your password has been changed.')
if 'customer' in base_parameters: if 'customer' in base_parameters:
placeholders['name'] = pgettext_lazy('person_name_sample', 'John Doe') placeholders['name'] = pgettext_lazy('person_name_sample', 'John Doe')
name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme] name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme]
+1 -89
View File
@@ -19,44 +19,17 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
from django import forms
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_scopes.forms import SafeModelChoiceField from django_scopes.forms import SafeModelChoiceField
from phonenumber_field.formfields import PhoneNumberField
from pretix.base.forms import I18nModelForm from pretix.base.forms import I18nModelForm
from pretix.base.forms.questions import (
NamePartsFormField, WrappedPhoneNumberPrefixWidget,
)
from pretix.base.models import WaitingListEntry from pretix.base.models import WaitingListEntry
from pretix.control.forms.widgets import Select2 from pretix.control.forms.widgets import Select2
class WaitingListEntryEditForm(I18nModelForm): class WaitingListEntryTransferForm(I18nModelForm):
itemvar = forms.ChoiceField(
error_messages={
'invalid_choice': _("Select a valid choice.")
}
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.instance = kwargs.get('instance', None)
initial = kwargs.get('initial', {})
choices = []
if self.instance and self.instance.pk and 'itemvar' not in initial:
if self.instance.variation is not None:
initial['itemvar'] = f'{self.instance.item.pk}-{self.instance.variation.pk}'
if self.instance.variation.active is False:
choices.append((initial['itemvar'], str(self.instance.variation)))
else:
initial['itemvar'] = self.instance.item.pk
if self.instance.item.active is False:
choices.append((initial['itemvar'], str(self.instance)))
kwargs['initial'] = initial
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.event.has_subevents: if self.event.has_subevents:
@@ -72,73 +45,12 @@ class WaitingListEntryEditForm(I18nModelForm):
} }
) )
self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].widget.choices = self.fields['subevent'].choices
else:
del self.fields['subevent']
if self.event.settings.waiting_list_names_asked:
self.fields['name_parts'] = NamePartsFormField(
max_length=255,
required=self.event.settings.waiting_list_names_required,
scheme=self.event.organizer.settings.name_scheme,
titles=self.event.organizer.settings.name_scheme_titles,
label=_('Name'),
)
else:
del self.fields['name_parts']
if not self.event.settings.waiting_list_phones_asked:
del self.fields['phone']
items = self.event.items.filter(active=True).prefetch_related(
'variations'
)
for item in items:
if len(item.variations.all()) > 0:
for variation in item.variations.all():
if variation.active:
choices.append(
('{}-{}'.format(item.pk, variation.pk), '{} - {}'.format(str(item), str(variation)))
)
else:
choices.append(('{}'.format(item.pk), str(item)))
self.fields['itemvar'].label = _("Product")
self.fields['itemvar'].help_text = _("Only includes active products.")
self.fields['itemvar'].required = True
self.fields['itemvar'].choices = choices
def clean(self):
cleaned_data = super().clean()
if self.instance.voucher is not None:
raise forms.ValidationError(_('A voucher for this waiting list entry was already sent out.'))
itemvar = cleaned_data.get('itemvar')
if itemvar:
self.instance.item = self.event.items.get(pk=itemvar.split('-')[0])
if '-' in itemvar:
self.instance.variation = self.instance.item.variations.get(pk=itemvar.split('-')[1])
if ((self.instance.item and not self.instance.item.active) or
(self.instance.variation and not self.instance.variation.active)):
self.add_error('itemvar', _('The selected product is not active.'))
return cleaned_data
class Meta: class Meta:
model = WaitingListEntry model = WaitingListEntry
fields = [ fields = [
'email',
'name_parts',
'phone',
'subevent', 'subevent',
] ]
field_classes = { field_classes = {
'subevent': SafeModelChoiceField, 'subevent': SafeModelChoiceField,
'email': forms.EmailField,
'phone': PhoneNumberField,
}
widgets = {
'phone': WrappedPhoneNumberPrefixWidget,
} }
-3
View File
@@ -518,7 +518,6 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
'The order requires approval before it can continue to be processed.'), 'The order requires approval before it can continue to be processed.'),
'pretix.event.order.approved': _('The order has been approved.'), 'pretix.event.order.approved': _('The order has been approved.'),
'pretix.event.order.denied': _('The order has been denied (comment: "{comment}").'), 'pretix.event.order.denied': _('The order has been denied (comment: "{comment}").'),
'pretix.event.order.vatid.validated': _('The customer VAT ID has been verified.'),
'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" ' 'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" '
'to "{new_email}".'), 'to "{new_email}".'),
'pretix.event.order.contact.confirmed': _( 'pretix.event.order.contact.confirmed': _(
@@ -802,8 +801,6 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
'pretix.giftcards.created': _('The gift card has been created.'), 'pretix.giftcards.created': _('The gift card has been created.'),
'pretix.giftcards.modified': _('The gift card has been changed.'), 'pretix.giftcards.modified': _('The gift card has been changed.'),
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'), 'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
'pretix.giftcards.transaction.payment': _('A payment has been performed.'),
'pretix.giftcards.transaction.refund': _('A refund has been performed. '),
'pretix.team.token.created': _('The token "{name}" has been created.'), 'pretix.team.token.created': _('The token "{name}" has been created.'),
'pretix.team.token.deleted': _('The token "{name}" has been revoked.'), 'pretix.team.token.deleted': _('The token "{name}" has been revoked.'),
'pretix.event.checkin.reset': _('The check-in and print log state has been reset.') 'pretix.event.checkin.reset': _('The check-in and print log state has been reset.')
@@ -19,14 +19,6 @@
</ul> </ul>
<br> <br>
{% endif %} {% endif %}
{% if possible_cookie_problem %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
It looks like your browser is not accepting our cookie and you need to log in repeatedly. Please
check if your browser is set to block cookies, or delete all existing cookies and retry.
{% endblocktrans %}
</div>
{% endif %}
{% csrf_token %} {% csrf_token %}
{% bootstrap_form form %} {% bootstrap_form form %}
<div class="form-group buttons"> <div class="form-group buttons">
@@ -9,5 +9,5 @@ Please do never give this code to another person. Our support team will never as
If this code was not requested by you, please contact us immediately. If this code was not requested by you, please contact us immediately.
Best regards, Best regards,
Your {{ instance }} team Your pretix team
{% endblocktrans %} {% endblocktrans %}
@@ -5,5 +5,5 @@ you requested a new password. Please go to the following page to reset your pass
{{ url }} {{ url }}
Best regards, Best regards,
Your {{ instance }} team Your pretix team
{% endblocktrans %} {% endblocktrans %}
@@ -1,6 +1,6 @@
{% load i18n %}{% blocktrans with url=url|safe %}Hello, {% load i18n %}{% blocktrans with url=url|safe %}Hello,
you have been invited to a team on {{ instance }}, a platform to perform event you have been invited to a team on pretix, a platform to perform event
ticket sales. ticket sales.
Organizer: {{ organizer }} Organizer: {{ organizer }}
@@ -13,5 +13,5 @@ If you do not want to join, you can safely ignore or delete this email.
Best regards, Best regards,
Your {{ instance }} team Your pretix team
{% endblocktrans %} {% endblocktrans %}
@@ -1,6 +1,6 @@
{% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello, {% load i18n %}{% blocktrans with url=url|safe messages=messages|safe %}Hello,
this is to inform you that the account information of your {{ instance }} account has been this is to inform you that the account information of your pretix account has been
changed. In particular, the following changes have been performed: changed. In particular, the following changes have been performed:
{{ messages }} {{ messages }}
@@ -12,5 +12,5 @@ You can review and change your account settings here:
{{ url }} {{ url }}
Best regards, Best regards,
Your {{ instance }} team Your pretix team
{% endblocktrans %} {% endblocktrans %}
@@ -65,9 +65,6 @@
{% blocktrans asvar title_reset %}Customer account password reset{% endblocktrans %} {% blocktrans asvar title_reset %}Customer account password reset{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="reset" title=title_reset items="mail_subject_customer_reset,mail_text_customer_reset" %} {% include "pretixcontrol/event/mail_settings_fragment.html" with pid="reset" title=title_reset items="mail_subject_customer_reset,mail_text_customer_reset" %}
{% blocktrans asvar title_security_notice %}Customer account security notification{% endblocktrans %}
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="security_notice" title=title_security_notice items="mail_subject_customer_security_notice,mail_text_customer_security_notice" %}
</div> </div>
</fieldset> </fieldset>
</div> </div>
@@ -19,7 +19,7 @@
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>{% trans "From" context "email" %}</dt> <dt>{% trans "From" context "email" %}</dt>
<dd>{{ sender }}</dd> <dd>{{ mail.sender }}</dd>
<dt>{% trans "To" context "email" %}</dt> <dt>{% trans "To" context "email" %}</dt>
<dd>{{ mail.to|join:", " }}</dd> <dd>{{ mail.to|join:", " }}</dd>
{% if mail.cc %} {% if mail.cc %}
@@ -144,23 +144,14 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p> <p>
{% blocktrans trimmed %} {% trans "If you lose access to your devices, you can use one of the following keys to log in. We recommend to store them in a safe place, e.g. printed out or in a password manager. Every token can be used at most once." %}
If you lose access to your devices, you can use one of your emergency tokens to log in.
We recommend to store them in a safe place, e.g. printed out or in a password manager.
Every token can be used at most once.
{% endblocktrans %}
</p> </p>
{% if static_tokens_device %} <p>{% trans "Unused tokens:" %}</p>
<p> <ul>
{% blocktrans trimmed with generation_date_time=static_tokens_device.created_at %} {% for t in static_tokens %}
You generated your emergency tokens on {{ generation_date_time }}. <li><code>{{ t.token }}</code></li>
{% endblocktrans %} {% endfor %}
</p> </ul>
{% else %}
<p>
{% trans "You don't have any emergency tokens yet." %}
</p>
{% endif %}
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default"> <a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
<span class="fa fa-refresh"></span> <span class="fa fa-refresh"></span>
{% trans "Generate new emergency tokens" %} {% trans "Generate new emergency tokens" %}
@@ -1,33 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Edit entry" %}{% endblock %}
{% block content %}
<h1>{% trans "Edit entry" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% if form.subevent %}
{% bootstrap_field form.subevent layout="control" %}
{% endif %}
{% bootstrap_field form.email layout="control" %}
{% if form.name_parts %}
{% bootstrap_field form.name_parts layout="control" %}
{% endif %}
{% if form.phone %}
{% bootstrap_field form.phone layout="control" %}
{% endif %}
{% bootstrap_field form.itemvar layout="control" %}
<div class="form-group submit-group">
<a href="{% url "control:event.orders.waitinglist" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}
@@ -124,7 +124,6 @@
</option> </option>
{% endfor %} {% endfor %}
</select> </select>
<input name="search" type="text" placeholder="{% trans "Search" %}" class="form-control" value="{{ request.GET.search }}">
{% if request.event.has_subevents %} {% if request.event.has_subevents %}
<select name="subevent" class="form-control"> <select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option> <option value="">{% trans "All dates" context "subevent" %}</option>
@@ -268,13 +267,13 @@
data-toggle="tooltip" title="{% trans "Move to the end of the list" %}"> data-toggle="tooltip" title="{% trans "Move to the end of the list" %}">
<span class="fa fa-thumbs-down"></span> <span class="fa fa-thumbs-down"></span>
</button> </button>
{% if request.event.has_subevents %}
<a href="{% url "control:event.orders.waitinglist.edit" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}" <a href="{% url "control:event.orders.waitinglist.transfer" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}"
class="btn btn-default btn-sm" title="{% trans "Edit entry" %}" class="btn btn-default btn-sm" title="{% trans "Transfer to other date" context "subevent" %}"
data-toggle="tooltip"> data-toggle="tooltip">
<i class="fa fa-edit" aria-hidden="true"></i> <i class="fa fa-calendar" aria-hidden="true"></i>
</a> </a>
{% endif %}
<a href="{% url "control:event.orders.waitinglist.delete" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}?next={{ request.get_full_path|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a> <a href="{% url "control:event.orders.waitinglist.delete" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}?next={{ request.get_full_path|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% else %} {% else %}
<button class="btn btn-default btn-sm disabled"> <button class="btn btn-default btn-sm disabled">
@@ -0,0 +1,23 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Transfer entry" %}{% endblock %}
{% block content %}
<h1>{% trans "Transfer entry" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>{% blocktrans trimmed context "subevent" %}
Please select the date to which the following waiting list entry should be
transferred: <strong>{{ entry }}</strong>?
{% endblocktrans %}</p>
{% bootstrap_field form.subevent layout="control" %}
<div class="form-group submit-group">
<a href="{% url "control:event.orders.waitinglist" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary btn-save">
{% trans "Transfer" %}
</button>
</div>
</form>
{% endblock %}
+2 -2
View File
@@ -480,8 +480,8 @@ urlpatterns = [
re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'), re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'),
re_path(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(), re_path(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(),
name='event.orders.waitinglist.delete'), name='event.orders.waitinglist.delete'),
re_path(r'^waitinglist/(?P<entry>\d+)/edit$', waitinglist.EntryEdit.as_view(), re_path(r'^waitinglist/(?P<entry>\d+)/transfer$', waitinglist.EntryTransfer.as_view(),
name='event.orders.waitinglist.edit'), name='event.orders.waitinglist.transfer'),
re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'), re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'),
re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'), re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'),
re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'), re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'),
-2
View File
@@ -149,8 +149,6 @@ def login(request):
return process_login(request, form.user_cache, form.cleaned_data.get('keep_logged_in', False)) return process_login(request, form.user_cache, form.cleaned_data.get('keep_logged_in', False))
else: else:
form = LoginForm(backend=backend, request=request) form = LoginForm(backend=backend, request=request)
# Detect redirection loop (usually means cookie not accepted)
ctx['possible_cookie_problem'] = request.path in request.headers.get("Referer", "")
ctx['form'] = form ctx['form'] = form
ctx['can_register'] = settings.PRETIX_REGISTRATION ctx['can_register'] = settings.PRETIX_REGISTRATION
ctx['can_reset'] = settings.PRETIX_PASSWORD_RESET ctx['can_reset'] = settings.PRETIX_PASSWORD_RESET
+4 -8
View File
@@ -870,15 +870,11 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
) )
except ValueError: except ValueError:
msgs[self.supported_locale[idx]] = format_html( msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
'<div class="alert alert-danger">{}</div>', PlaceholderValidator.error_message)
PlaceholderValidator.error_message
)
except KeyError as e: except KeyError as e:
msgs[self.supported_locale[idx]] = format_html( msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
'<div class="alert alert-danger">{}</div>', _('Invalid placeholder: {%(value)s}') % {'value': e.args[0]})
_('Invalid placeholder: {%(value)s}') % {'value': e.args[0]}
)
return JsonResponse({ return JsonResponse({
'item': preview_item, 'item': preview_item,
-8
View File
@@ -21,8 +21,6 @@
# #
import base64 import base64
import logging import logging
from email.header import decode_header, make_header
from email.utils import parseaddr
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
@@ -126,12 +124,6 @@ class OutgoingMailDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequir
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
if self.object.body_html: if self.object.body_html:
ctx['data_url'] = "data:text/html;charset=utf-8;base64," + base64.b64encode(self.object.body_html.encode()).decode() ctx['data_url'] = "data:text/html;charset=utf-8;base64," + base64.b64encode(self.object.body_html.encode()).decode()
from_name, from_email = parseaddr(self.object.sender)
if from_name:
from_name = make_header(decode_header(from_name))
ctx['sender'] = "{} <{}>".format(from_name, from_email) if from_name else from_email
return ctx return ctx
+4 -16
View File
@@ -1226,11 +1226,7 @@ class OrderRefundView(OrderView):
customer=order.customer, customer=order.customer,
testmode=order.testmode testmode=order.testmode
) )
giftcard.log_action( giftcard.log_action('pretix.giftcards.created', user=self.request.user, data={})
action='pretix.giftcards.created',
user=self.request.user,
data={}
)
refunds.append(OrderRefund( refunds.append(OrderRefund(
order=order, order=order,
payment=None, payment=None,
@@ -1641,17 +1637,9 @@ class OrderCheckVATID(OrderView):
try: try:
normalized_id = validate_vat_id(ia.vat_id, str(ia.country)) normalized_id = validate_vat_id(ia.vat_id, str(ia.country))
with transaction.atomic(): ia.vat_id_validated = True
ia.vat_id_validated = True ia.vat_id = normalized_id
ia.vat_id = normalized_id ia.save()
ia.save()
self.order.log_action(
'pretix.event.order.vatid.validated',
data={
'vat_id': normalized_id,
},
user=self.request.user,
)
except VATIDFinalError as e: except VATIDFinalError as e:
messages.error(self.request, e.message) messages.error(self.request, e.message)
except VATIDTemporaryError: except VATIDTemporaryError:
+20 -48
View File
@@ -1039,10 +1039,9 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
def _send_invite(self, instance): def _send_invite(self, instance):
mail( mail(
instance.email, instance.email,
_('Account invitation'), _('pretix account invitation'),
'pretixcontrol/email/invitation.txt', 'pretixcontrol/email/invitation.txt',
{ {
'instance': settings.PRETIX_INSTANCE_NAME,
'user': self, 'user': self,
'organizer': self.request.organizer.name, 'organizer': self.request.organizer.name,
'team': instance.team.name, 'team': instance.team.name,
@@ -1668,12 +1667,9 @@ class GiftCardAcceptanceInviteView(OrganizerDetailViewMixin, OrganizerPermission
active=False, active=False,
) )
self.request.organizer.log_action( self.request.organizer.log_action(
action='pretix.giftcards.acceptance.acceptor.invited', 'pretix.giftcards.acceptance.acceptor.invited',
data={ data={'acceptor': form.cleaned_data['acceptor'].slug,
'acceptor': form.cleaned_data['acceptor'].slug, 'reusable_media': form.cleaned_data['reusable_media']},
'issuer': self.request.organizer.slug,
'reusable_media': form.cleaned_data['reusable_media']
},
user=self.request.user user=self.request.user
) )
messages.success(self.request, _('The selected organizer has been invited.')) messages.success(self.request, _('The selected organizer has been invited.'))
@@ -1709,11 +1705,8 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
).delete() ).delete()
if done: if done:
self.request.organizer.log_action( self.request.organizer.log_action(
action='pretix.giftcards.acceptance.acceptor.removed', 'pretix.giftcards.acceptance.acceptor.removed',
data={ data={'acceptor': request.POST.get("delete_acceptor")},
'acceptor': request.POST.get("delete_acceptor"),
'issuer': self.request.organizer.slug
},
user=request.user user=request.user
) )
messages.success(self.request, _('The selected connection has been removed.')) messages.success(self.request, _('The selected connection has been removed.'))
@@ -1723,11 +1716,8 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
).delete() ).delete()
if done: if done:
self.request.organizer.log_action( self.request.organizer.log_action(
action='pretix.giftcards.acceptance.issuer.removed', 'pretix.giftcards.acceptance.issuer.removed',
data={ data={'issuer': request.POST.get("delete_acceptor")},
'issuer': request.POST.get("delete_acceptor"),
'acceptor': self.request.organizer.slug
},
user=request.user user=request.user
) )
messages.success(self.request, _('The selected connection has been removed.')) messages.success(self.request, _('The selected connection has been removed.'))
@@ -1737,11 +1727,8 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
).update(active=True) ).update(active=True)
if done: if done:
self.request.organizer.log_action( self.request.organizer.log_action(
action='pretix.giftcards.acceptance.issuer.accepted', 'pretix.giftcards.acceptance.issuer.accepted',
data={ data={'issuer': request.POST.get("accept_issuer")},
'issuer': request.POST.get("accept_issuer"),
'acceptor': self.request.organizer.slug
},
user=request.user user=request.user
) )
messages.success(self.request, _('The selected connection has been accepted.')) messages.success(self.request, _('The selected connection has been accepted.'))
@@ -1847,12 +1834,10 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
acceptor=request.organizer, acceptor=request.organizer,
) )
self.object.log_action( self.object.log_action(
action='pretix.giftcards.transaction.manual', 'pretix.giftcards.transaction.manual',
data={ data={
'value': value, 'value': value,
'text': request.POST.get('text'), 'text': request.POST.get('text')
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}, },
user=self.request.user, user=self.request.user,
) )
@@ -1901,24 +1886,15 @@ class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
messages.success(self.request, _('The gift card has been created and can now be used.')) messages.success(self.request, _('The gift card has been created and can now be used.'))
form.instance.issuer = self.request.organizer form.instance.issuer = self.request.organizer
super().form_valid(form) super().form_valid(form)
form.instance.log_action( form.instance.transactions.create(
action='pretix.giftcards.created', acceptor=self.request.organizer,
user=self.request.user, value=form.cleaned_data['value']
) )
form.instance.log_action('pretix.giftcards.created', user=self.request.user, data={})
if form.cleaned_data['value']: if form.cleaned_data['value']:
form.instance.transactions.create( form.instance.log_action('pretix.giftcards.transaction.manual', user=self.request.user, data={
acceptor=self.request.organizer, 'value': form.cleaned_data['value']
value=form.cleaned_data['value'] })
)
form.instance.log_action(
action='pretix.giftcards.transaction.manual',
user=self.request.user,
data={
'value': form.cleaned_data['value'],
'acceptor_id': self.request.organizer.id,
'acceptor_slug': self.request.organizer.slug
}
)
return redirect(reverse( return redirect(reverse(
'control:organizer.giftcard', 'control:organizer.giftcard',
kwargs={ kwargs={
@@ -1946,11 +1922,7 @@ class GiftCardUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('The gift card has been changed.')) messages.success(self.request, _('The gift card has been changed.'))
super().form_valid(form) super().form_valid(form)
form.instance.log_action( form.instance.log_action('pretix.giftcards.modified', user=self.request.user, data=dict(form.cleaned_data))
action='pretix.giftcards.modified',
user=self.request.user,
data=dict(form.cleaned_data)
)
return redirect(reverse( return redirect(reverse(
'control:organizer.giftcard', 'control:organizer.giftcard',
kwargs={ kwargs={
+10 -16
View File
@@ -49,14 +49,12 @@ from django.db import transaction
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse from django.urls import reverse
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.http import url_has_allowed_host_and_scheme from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, ListView, TemplateView, UpdateView from django.views.generic import FormView, ListView, TemplateView, UpdateView
from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_static.models import StaticDevice
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
@@ -87,9 +85,8 @@ logger = logging.getLogger(__name__)
class RecentAuthenticationRequiredMixin: class RecentAuthenticationRequiredMixin:
max_time = 900 max_time = 3600
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
tdelta = time.time() - request.session.get('pretix_auth_login_time', 0) tdelta = time.time() - request.session.get('pretix_auth_login_time', 0)
if tdelta > self.max_time: if tdelta > self.max_time:
@@ -292,13 +289,16 @@ class User2FAMainView(RecentAuthenticationRequiredMixin, TemplateView):
ctx = super().get_context_data() ctx = super().get_context_data()
try: try:
ctx['static_tokens_device'] = StaticDevice.objects.get(user=self.request.user, name='emergency') ctx['static_tokens'] = StaticDevice.objects.get(user=self.request.user, name='emergency').token_set.all()
except StaticDevice.MultipleObjectsReturned: except StaticDevice.MultipleObjectsReturned:
ctx['static_tokens_device'] = StaticDevice.objects.filter( ctx['static_tokens'] = StaticDevice.objects.filter(
user=self.request.user, name='emergency' user=self.request.user, name='emergency'
).first() ).first().token_set.all()
except StaticDevice.DoesNotExist: except StaticDevice.DoesNotExist:
ctx['static_tokens_device'] = None d = StaticDevice.objects.create(user=self.request.user, name='emergency')
for i in range(10):
d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890'))
ctx['static_tokens'] = d.token_set.all()
ctx['devices'] = [] ctx['devices'] = []
for dt in REAL_DEVICE_TYPES: for dt in REAL_DEVICE_TYPES:
@@ -630,14 +630,8 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template
]) ])
self.request.user.update_session_token() self.request.user.update_session_token()
update_session_auth_hash(self.request, self.request.user) update_session_auth_hash(self.request, self.request.user)
messages.success( messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe '
request, 'place in case you lose access to your devices.'))
_('Your emergency codes have been newly generated. Remember to store them in a safe '
'place in case you lose access to your devices. You will not be able to view them '
'again here.\n\nYour emergency codes:\n{tokens}').format(
tokens='- ' + '\n- '.join(t.token for t in d.token_set.all())
)
)
return redirect(reverse('control:user.settings.2fa')) return redirect(reverse('control:user.settings.2fa'))
+13 -20
View File
@@ -53,7 +53,7 @@ from pretix.base.models import Item, LogEntry, Quota, WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException from pretix.base.models.waitinglist import WaitingListException
from pretix.base.services.waitinglist import assign_automatically from pretix.base.services.waitinglist import assign_automatically
from pretix.base.views.tasks import AsyncAction from pretix.base.views.tasks import AsyncAction
from pretix.control.forms.waitinglist import WaitingListEntryEditForm from pretix.control.forms.waitinglist import WaitingListEntryTransferForm
from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views import PaginationMixin from pretix.control.views import PaginationMixin
@@ -138,17 +138,6 @@ class WaitingListQuerySetMixin:
elif force_filtered and '__ALL' not in self.request_data: elif force_filtered and '__ALL' not in self.request_data:
qs = qs.none() qs = qs.none()
if self.request_data.get("search", "") != "":
s = self.request_data.get("search", "")
search_q = Q(email__icontains=s)
if self.request.event.settings.waiting_list_names_asked:
search_q = search_q | Q(name_cached__icontains=s)
if self.request.event.settings.waiting_list_phones_asked:
search_q = search_q | Q(phone__icontains=s)
qs = qs.filter(search_q)
return qs return qs
@@ -249,7 +238,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['items'] = Item.objects.filter(event=self.request.event) ctx['items'] = Item.objects.filter(event=self.request.event)
ctx['filtered'] = any(param in self.request.GET for param in ("status", "item", "search")) ctx['filtered'] = ("status" in self.request.GET or "item" in self.request.GET)
itemvar_cache = {} itemvar_cache = {}
quota_cache = {} quota_cache = {}
@@ -280,12 +269,11 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
block_quota=True, block_quota=True,
item_id=wle.item_id, item_id=wle.item_id,
subevent=wle.subevent_id, subevent=wle.subevent_id,
waitinglistentries__isnull=False, waitinglistentries__isnull=False
seat__isnull=True
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0 ).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
free_seats = num_free_seats_for_product - num_valid_vouchers_for_product free_seats = num_free_seats_for_product - num_valid_vouchers_for_product
wle.availability = ( wle.availability = (
Quota.AVAILABILITY_GONE if free_seats < 1 else wle.availability[0], Quota.AVAILABILITY_GONE if free_seats == 0 else wle.availability[0],
min(free_seats, wle.availability[1]) if wle.availability[1] is not None else free_seats, min(free_seats, wle.availability[1]) if wle.availability[1] is not None else free_seats,
) )
@@ -402,20 +390,25 @@ class EntryDelete(EventPermissionRequiredMixin, CompatDeleteView):
}) })
class EntryEdit(EventPermissionRequiredMixin, UpdateView): class EntryTransfer(EventPermissionRequiredMixin, UpdateView):
model = WaitingListEntry model = WaitingListEntry
template_name = 'pretixcontrol/waitinglist/edit.html' template_name = 'pretixcontrol/waitinglist/transfer.html'
permission = 'can_change_orders' permission = 'can_change_orders'
form_class = WaitingListEntryEditForm form_class = WaitingListEntryTransferForm
context_object_name = 'entry' context_object_name = 'entry'
def dispatch(self, request, *args, **kwargs):
if not self.request.event.has_subevents:
raise Http404(_("This is not an event series."))
return super().dispatch(request, *args, **kwargs)
def get_object(self, queryset=None) -> WaitingListEntry: def get_object(self, queryset=None) -> WaitingListEntry:
return get_object_or_404(WaitingListEntry, pk=self.kwargs['entry'], event=self.request.event, voucher__isnull=True) return get_object_or_404(WaitingListEntry, pk=self.kwargs['entry'], event=self.request.event, voucher__isnull=True)
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('The waitinglist entry has been transferred.'))
if form.has_changed(): if form.has_changed():
messages.success(self.request, _('The waitinglist entry has been changed.'))
self.object.log_action( self.object.log_action(
'pretix.event.orders.waitinglist.changed', user=self.request.user, data={ 'pretix.event.orders.waitinglist.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data k: form.cleaned_data.get(k) for k in form.changed_data
+2 -25
View File
@@ -22,7 +22,6 @@
import logging import logging
from string import Formatter from string import Formatter
from django.core.exceptions import SuspiciousOperation
from django.utils.html import conditional_escape from django.utils.html import conditional_escape
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -38,17 +37,6 @@ class PlainHtmlAlternativeString:
return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')" return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')"
class FormattedString(str):
"""
A str subclass that has been specifically marked as "already formatted" for email rendering
purposes to avoid duplicate formatting.
"""
__slots__ = ()
def __str__(self):
return self
class SafeFormatter(Formatter): class SafeFormatter(Formatter):
""" """
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
@@ -89,19 +77,8 @@ class SafeFormatter(Formatter):
# Ignore format_spec # Ignore format_spec
return super().format_field(self._prepare_value(value), '') return super().format_field(self._prepare_value(value), '')
def convert_field(self, value, conversion):
# Ignore any conversions
if conversion is None:
return value
else:
return str(value)
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None):
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None) -> FormattedString:
if isinstance(template, FormattedString):
raise SuspiciousOperation("Calling format_map() on an already formatted string is likely unsafe.")
if not isinstance(template, str): if not isinstance(template, str):
template = str(template) template = str(template)
return FormattedString( return SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)
SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-3
View File
@@ -265,7 +265,6 @@ Objekt-IDs
Offline-Scan Offline-Scan
OK OK
Online-Banking Online-Banking
Online-Banking-Nutzer
Onlinebanking Onlinebanking
Onlinebanking-Zugangsdaten Onlinebanking-Zugangsdaten
Open Open
@@ -540,8 +539,6 @@ WeChat-Zahlung
Weiterleitungs-URIs Weiterleitungs-URIs
Weiterleitungs-URL Weiterleitungs-URL
Weiterleitungs-URLs Weiterleitungs-URLs
WERO
WERO-App
WhatsApp WhatsApp
Widget Widget
Widget-Code Widget-Code
File diff suppressed because it is too large Load Diff
@@ -265,7 +265,6 @@ Objekt-IDs
Offline-Scan Offline-Scan
OK OK
Online-Banking Online-Banking
Online-Banking-Nutzer
Onlinebanking Onlinebanking
Onlinebanking-Zugangsdaten Onlinebanking-Zugangsdaten
Open Open
@@ -540,8 +539,6 @@ WeChat-Zahlung
Weiterleitungs-URIs Weiterleitungs-URIs
Weiterleitungs-URL Weiterleitungs-URL
Weiterleitungs-URLs Weiterleitungs-URLs
WERO
WERO-App
WhatsApp WhatsApp
Widget Widget
Widget-Code Widget-Code
+2262 -2556
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-24 11:50+0000\n" "POT-Creation-Date: 2026-01-26 13:20+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-03-02 21:00+0000\n" "PO-Revision-Date: 2025-12-03 23:00+0000\n"
"Last-Translator: Sandra Rial Pérez <sandrarial@gestiontickets.online>\n" "Last-Translator: sandra r <sandrarial@gestiontickets.online>\n"
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
"js/gl/>\n" "js/gl/>\n"
"Language: gl\n" "Language: gl\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.16.1\n" "X-Generator: Weblate 5.14.3\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -162,12 +162,12 @@ msgstr "Pedidos pagados"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (ordered)" msgid "Attendees (ordered)"
msgstr "Asistentes (ordenados)" msgstr ""
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (paid)" msgid "Attendees (paid)"
msgstr "Asistentes (de pago)" msgstr ""
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
msgid "Total revenue" msgid "Total revenue"
@@ -732,8 +732,8 @@ msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available." "complete your order as long as theyre available."
msgstr "" msgstr ""
"Os artigos do teu carro xa non están reservados para ti. Podes completar o " "Os artigos da túa cesta xa non están reservados para ti. Aínda podes "
"teu pedido sempre que estean dispoñibles." "completar o teu pedido mentres estean dispoñibles."
#: pretix/static/pretixpresale/js/ui/cart.js:49 #: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired" msgid "Cart expired"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -6
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-02-10 16:49+0000\n" "PO-Revision-Date: 2025-05-05 09:40+0000\n"
"Last-Translator: Raffaele Doretto <ced@comune.portogruaro.ve.it>\n" "Last-Translator: \"Luca Martinelli [Sannita]\" <sannita@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
"js/it/>\n" "js/it/>\n"
"Language: it\n" "Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.2\n" "X-Generator: Weblate 5.11.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -1134,9 +1134,6 @@ msgid ""
"add yourself to the waiting list. We will then notify if seats are available " "add yourself to the waiting list. We will then notify if seats are available "
"again." "again."
msgstr "" msgstr ""
"Alcune o tutte le categorie di biglietti sono attualmente esaurite. Se lo "
"desideri, puoi aggiungerti alla lista d'attesa. Ti informeremo se i posti "
"saranno nuovamente disponibili."
#: pretix/static/pretixpresale/js/widget/widget.js:76 #: pretix/static/pretixpresale/js/widget/widget.js:76
msgctxt "widget" msgctxt "widget"
File diff suppressed because it is too large Load Diff
+58 -55
View File
@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n" "POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-02-23 10:00+0000\n" "PO-Revision-Date: 2026-01-28 18:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n" "Last-Translator: Ryo Tagami <rtagami@airstrip.jp>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-" "Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
"js/ja/>\n" "js/ja/>\n"
"Language: ja\n" "Language: ja\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.16\n" "X-Generator: Weblate 5.15.2\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -27,7 +27,7 @@ msgstr "支払い済み"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76 #: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
msgid "Comment:" msgid "Comment:"
msgstr "コメント:" msgstr "注釈:"
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34 #: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
msgid "PayPal" msgid "PayPal"
@@ -157,7 +157,7 @@ msgstr "受注件数"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:63 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:63
msgid "Paid orders" msgid "Paid orders"
msgstr "支払い済み件数" msgstr "支払い済みの注文"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
@@ -167,7 +167,7 @@ msgstr "参加者 (注文済み)"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
msgid "Attendees (paid)" msgid "Attendees (paid)"
msgstr "参加者 (支払い済み)" msgstr "参加者 (有料)"
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51 #: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
msgid "Total revenue" msgid "Total revenue"
@@ -215,7 +215,7 @@ msgstr "このチケットは特別な対応が必要です"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
msgid "Switch direction" msgid "Switch direction"
msgstr "向きを切り替え" msgstr "方向転換"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
msgid "Entry" msgid "Entry"
@@ -223,7 +223,7 @@ msgstr "入場"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
msgid "Exit" msgid "Exit"
msgstr "退" msgstr "退"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
msgid "Scan a ticket or search and press return…" msgid "Scan a ticket or search and press return…"
@@ -256,7 +256,7 @@ msgstr "承認保留中"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
msgid "Redeemed" msgid "Redeemed"
msgstr "引き換え済み" msgstr "使用済"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
msgid "Cancel" msgid "Cancel"
@@ -297,7 +297,7 @@ msgstr "不明なチケット"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
msgid "Ticket type not allowed here" msgid "Ticket type not allowed here"
msgstr "この種類のチケットはここでは使用できません" msgstr "この種類のチケットは使用できません"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
msgid "Entry not allowed" msgid "Entry not allowed"
@@ -321,7 +321,7 @@ msgstr "注文がキャンセルされました"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
msgid "Ticket code is ambiguous on list" msgid "Ticket code is ambiguous on list"
msgstr "リスト上でチケットコードが一意に特定できません" msgstr "リストチケットコードは曖昧です"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67 #: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
msgid "Order not approved" msgid "Order not approved"
@@ -358,7 +358,7 @@ msgstr "閉じる"
#: pretix/static/pretixbase/js/addressform.js:101 #: pretix/static/pretixbase/js/addressform.js:101
#: pretix/static/pretixpresale/js/ui/main.js:529 #: pretix/static/pretixpresale/js/ui/main.js:529
msgid "required" msgid "required"
msgstr "必" msgstr "必"
#: pretix/static/pretixbase/js/asynctask.js:13 #: pretix/static/pretixbase/js/asynctask.js:13
msgid "" msgid ""
@@ -370,7 +370,8 @@ msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:17 #: pretix/static/pretixbase/js/asynctask.js:17
msgid "Your request has been queued on the server and will soon be processed." msgid "Your request has been queued on the server and will soon be processed."
msgstr "お客様のリクエストはサーバーで受け付けられました。まもなく処理されます。" msgstr ""
"サーバへ送信されたリクエスト順にお応えしています。今しばらくお待ちください。"
#: pretix/static/pretixbase/js/asynctask.js:21 #: pretix/static/pretixbase/js/asynctask.js:21
msgid "" msgid ""
@@ -393,7 +394,8 @@ msgstr "{code} のエラーが発生しました。"
msgid "" msgid ""
"We currently cannot reach the server, but we keep trying. Last error code: " "We currently cannot reach the server, but we keep trying. Last error code: "
"{code}" "{code}"
msgstr "現在サーバへの接続ができませんが、接続試行中です。最新のエラーコード: {code}" msgstr ""
"現在サーバへの接続ができませんが、接続試行中です。エラーコード: {code}"
#: pretix/static/pretixbase/js/asynctask.js:162 #: pretix/static/pretixbase/js/asynctask.js:162
#: pretix/static/pretixcontrol/js/ui/mail.js:21 #: pretix/static/pretixcontrol/js/ui/mail.js:21
@@ -417,8 +419,9 @@ msgid ""
"than one minute, please check your internet connection and then reload this " "than one minute, please check your internet connection and then reload this "
"page and try again." "page and try again."
msgstr "" msgstr ""
"現在リクエストサーバへ送信中です。1分以上経っても応答がない場合は、" "リクエストサーバへ送信されました。1分以上経っても応答がない場合は、イン"
"インターネット接続を確認し、このページを再読み込みして再試行してください。" "ターネット接続を確認してください。確認完了後、ウェブページを再度読込み、再試"
"行してください。"
#: pretix/static/pretixbase/js/asynctask.js:276 #: pretix/static/pretixbase/js/asynctask.js:276
msgid "If this takes longer than a few minutes, please contact us." msgid "If this takes longer than a few minutes, please contact us."
@@ -426,7 +429,7 @@ msgstr "数分以上かかる場合は、お問い合わせください。"
#: pretix/static/pretixbase/js/asynctask.js:331 #: pretix/static/pretixbase/js/asynctask.js:331
msgid "Close message" msgid "Close message"
msgstr "メッセージを閉じる" msgstr "閉じる"
#: pretix/static/pretixcontrol/js/clipboard.js:23 #: pretix/static/pretixcontrol/js/clipboard.js:23
msgid "Copied!" msgid "Copied!"
@@ -440,7 +443,7 @@ msgstr "Ctrl-Cを押してコピー!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:18 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:18
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:24 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:24
msgid "is one of" msgid "is one of"
msgstr "次のいずれか" msgstr "の一つです"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:30 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:30
msgid "is before" msgid "is before"
@@ -520,7 +523,7 @@ msgstr "以下のすべての条件(AND"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
msgid "At least one of the conditions below (OR)" msgid "At least one of the conditions below (OR)"
msgstr "以下の条件のうち、最低1つ(OR" msgstr "以下の条件のうち、最低1つ(または"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
msgid "Event start" msgid "Event start"
@@ -561,16 +564,16 @@ msgstr "複製"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
msgctxt "entry_status" msgctxt "entry_status"
msgid "present" msgid "present"
msgstr "入場中" msgstr "出席"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
msgctxt "entry_status" msgctxt "entry_status"
msgid "absent" msgid "absent"
msgstr "未入場" msgstr "欠席"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
msgid "Error: Product not found!" msgid "Error: Product not found!"
msgstr "エラー:品が見つかりません!" msgstr "エラー:品が見つかりません!"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296 #: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
msgid "Error: Variation not found!" msgid "Error: Variation not found!"
@@ -582,7 +585,7 @@ msgstr "チェックイン用QRコード"
#: pretix/static/pretixcontrol/js/ui/editor.js:549 #: pretix/static/pretixcontrol/js/ui/editor.js:549
msgid "The PDF background file could not be loaded for the following reason:" msgid "The PDF background file could not be loaded for the following reason:"
msgstr "以下の理由によりPDF背景ファイルの読み込みに失敗しました:" msgstr "以下の理由によりPDFファイルの読み込みに失敗しました:"
#: pretix/static/pretixcontrol/js/ui/editor.js:904 #: pretix/static/pretixcontrol/js/ui/editor.js:904
msgid "Group of objects" msgid "Group of objects"
@@ -590,7 +593,7 @@ msgstr "オブジェクトグループ"
#: pretix/static/pretixcontrol/js/ui/editor.js:909 #: pretix/static/pretixcontrol/js/ui/editor.js:909
msgid "Text object (deprecated)" msgid "Text object (deprecated)"
msgstr "テキストオブジェクト(非推奨)" msgstr "テキストオブジェクト (廃止済)"
#: pretix/static/pretixcontrol/js/ui/editor.js:911 #: pretix/static/pretixcontrol/js/ui/editor.js:911
msgid "Text box" msgid "Text box"
@@ -606,7 +609,7 @@ msgstr "画像エリア"
#: pretix/static/pretixcontrol/js/ui/editor.js:917 #: pretix/static/pretixcontrol/js/ui/editor.js:917
msgid "Powered by pretix" msgid "Powered by pretix"
msgstr "Powered by pretix" msgstr "Pretixのイベントチケット売り場"
#: pretix/static/pretixcontrol/js/ui/editor.js:919 #: pretix/static/pretixcontrol/js/ui/editor.js:919
msgid "Object" msgid "Object"
@@ -644,24 +647,24 @@ msgstr "不明なエラー。"
#: pretix/static/pretixcontrol/js/ui/main.js:309 #: pretix/static/pretixcontrol/js/ui/main.js:309
msgid "Your color has great contrast and will provide excellent accessibility." msgid "Your color has great contrast and will provide excellent accessibility."
msgstr "" msgstr ""
"選択した色は素晴らしいコントラストを持ち、優れたアクセシビリティを提供しま" "あなたの色は素晴らしいコントラストを持ち、優れたアクセシビリティを提供しま"
"。" "。"
#: pretix/static/pretixcontrol/js/ui/main.js:313 #: pretix/static/pretixcontrol/js/ui/main.js:313
msgid "" msgid ""
"Your color has decent contrast and is sufficient for minimum accessibility " "Your color has decent contrast and is sufficient for minimum accessibility "
"requirements." "requirements."
msgstr "" msgstr ""
"選択した色は適切なコントラストを持ち、最小限のアクセシビリティ要件に十分で" "あなたの色は適切なコントラストを持ち、最小限のアクセシビリティ要件に十分で"
"。" "。"
#: pretix/static/pretixcontrol/js/ui/main.js:317 #: pretix/static/pretixcontrol/js/ui/main.js:317
msgid "" msgid ""
"Your color has insufficient contrast to white. Accessibility of your site " "Your color has insufficient contrast to white. Accessibility of your site "
"will be impacted." "will be impacted."
msgstr "" msgstr ""
"選択した色は白に対して十分なコントラストがありません。サイトの" "あなたの色は白に対して十分なコントラストがありません。サイトのアクセシビリ"
"アクセシビリティに影響します。" "ティに影響します。"
#: pretix/static/pretixcontrol/js/ui/main.js:443 #: pretix/static/pretixcontrol/js/ui/main.js:443
#: pretix/static/pretixcontrol/js/ui/main.js:463 #: pretix/static/pretixcontrol/js/ui/main.js:463
@@ -674,7 +677,7 @@ msgstr "全て"
#: pretix/static/pretixcontrol/js/ui/main.js:462 #: pretix/static/pretixcontrol/js/ui/main.js:462
msgid "None" msgid "None"
msgstr "な" msgstr "な"
#: pretix/static/pretixcontrol/js/ui/main.js:466 #: pretix/static/pretixcontrol/js/ui/main.js:466
msgid "Selected only" msgid "Selected only"
@@ -690,7 +693,7 @@ msgstr "無効なページ番号。"
#: pretix/static/pretixcontrol/js/ui/main.js:1000 #: pretix/static/pretixcontrol/js/ui/main.js:1000
msgid "Use a different name internally" msgid "Use a different name internally"
msgstr "内部で別の名前を使用する" msgstr "内部で別の名前を使用してください"
#: pretix/static/pretixcontrol/js/ui/main.js:1040 #: pretix/static/pretixcontrol/js/ui/main.js:1040
msgid "Click to close" msgid "Click to close"
@@ -719,15 +722,15 @@ msgstr "カウント"
#: pretix/static/pretixcontrol/js/ui/subevent.js:112 #: pretix/static/pretixcontrol/js/ui/subevent.js:112
msgid "(one more date)" msgid "(one more date)"
msgid_plural "({num} more dates)" msgid_plural "({num} more dates)"
msgstr[0] "(他に{num}の日程)" msgstr[0] "({num}の日程)"
#: pretix/static/pretixpresale/js/ui/cart.js:47 #: pretix/static/pretixpresale/js/ui/cart.js:47
msgid "" msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available." "complete your order as long as theyre available."
msgstr "" msgstr ""
"カート内のアイテムの予約が解除されました。在庫がある限り、引き続き注文を完了" "カートに入っている商品は現在売り切れです。在庫があれば、このまま注文を完了"
"できます。" "ることができます。"
#: pretix/static/pretixpresale/js/ui/cart.js:49 #: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired" msgid "Cart expired"
@@ -741,27 +744,27 @@ msgstr "カートの有効期限が近づいています。"
#: pretix/static/pretixpresale/js/ui/cart.js:62 #: pretix/static/pretixpresale/js/ui/cart.js:62
msgid "The items in your cart are reserved for you for one minute." msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes." msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "カート内の商品はあと {num} 分間確保されています。" msgstr[0] "カート内の商品の予約は {num} 分以内に完了します。"
#: pretix/static/pretixpresale/js/ui/cart.js:83 #: pretix/static/pretixpresale/js/ui/cart.js:83
msgid "Your cart has expired." msgid "Your cart has expired."
msgstr "カートの有効期限が切れています。" msgstr "カートの保存期限が切れています。"
#: pretix/static/pretixpresale/js/ui/cart.js:86 #: pretix/static/pretixpresale/js/ui/cart.js:86
msgid "" msgid ""
"The items in your cart are no longer reserved for you. You can still " "The items in your cart are no longer reserved for you. You can still "
"complete your order as long as they're available." "complete your order as long as they're available."
msgstr "" msgstr ""
"カート内の商品の確保期限が切れました。在庫があれば、このまま注文を完了するこ" "カート内の商品の予約期限が切れました。在庫があれば、このまま注文を完了するこ"
"とができます。" "とができます。"
#: pretix/static/pretixpresale/js/ui/cart.js:87 #: pretix/static/pretixpresale/js/ui/cart.js:87
msgid "Do you want to renew the reservation period?" msgid "Do you want to renew the reservation period?"
msgstr "確保期間を更新しますか?" msgstr "予約の期間を更新しますか?"
#: pretix/static/pretixpresale/js/ui/cart.js:90 #: pretix/static/pretixpresale/js/ui/cart.js:90
msgid "Renew reservation" msgid "Renew reservation"
msgstr "確保を更新" msgstr "予約を更新"
#: pretix/static/pretixpresale/js/ui/main.js:194 #: pretix/static/pretixpresale/js/ui/main.js:194
msgid "The organizer keeps %(currency)s %(amount)s" msgid "The organizer keeps %(currency)s %(amount)s"
@@ -777,7 +780,7 @@ msgstr "主催者が留保する料金を入力してください。"
#: pretix/static/pretixpresale/js/ui/main.js:577 #: pretix/static/pretixpresale/js/ui/main.js:577
msgid "Your local time:" msgid "Your local time:"
msgstr "お使いの地域の時刻" msgstr "現地時間"
#: pretix/static/pretixpresale/js/walletdetection.js:39 #: pretix/static/pretixpresale/js/walletdetection.js:39
msgid "Google Pay" msgid "Google Pay"
@@ -860,7 +863,7 @@ msgstr "登録"
#: pretix/static/pretixpresale/js/widget/widget.js:30 #: pretix/static/pretixpresale/js/widget/widget.js:30
msgctxt "widget" msgctxt "widget"
msgid "Reserved" msgid "Reserved"
msgstr "予約済み" msgstr "予約完了"
#: pretix/static/pretixpresale/js/widget/widget.js:31 #: pretix/static/pretixpresale/js/widget/widget.js:31
msgctxt "widget" msgctxt "widget"
@@ -876,7 +879,7 @@ msgstr "%(currency)s %(price)sから"
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "Image of %s" msgid "Image of %s"
msgstr "%sの画像" msgstr "%sのイメージ"
#: pretix/static/pretixpresale/js/widget/widget.js:34 #: pretix/static/pretixpresale/js/widget/widget.js:34
msgctxt "widget" msgctxt "widget"
@@ -886,7 +889,7 @@ msgstr "%(rate)s% %(taxname)s込"
#: pretix/static/pretixpresale/js/widget/widget.js:35 #: pretix/static/pretixpresale/js/widget/widget.js:35
msgctxt "widget" msgctxt "widget"
msgid "plus %(rate)s% %(taxname)s" msgid "plus %(rate)s% %(taxname)s"
msgstr "別途%(rate)s% %(taxname)s" msgstr "%(rate)s% %(taxname)s"
#: pretix/static/pretixpresale/js/widget/widget.js:36 #: pretix/static/pretixpresale/js/widget/widget.js:36
msgctxt "widget" msgctxt "widget"
@@ -896,13 +899,13 @@ msgstr "税込"
#: pretix/static/pretixpresale/js/widget/widget.js:37 #: pretix/static/pretixpresale/js/widget/widget.js:37
msgctxt "widget" msgctxt "widget"
msgid "plus taxes" msgid "plus taxes"
msgstr "税" msgstr "税"
#: pretix/static/pretixpresale/js/widget/widget.js:38 #: pretix/static/pretixpresale/js/widget/widget.js:38
#, javascript-format #, javascript-format
msgctxt "widget" msgctxt "widget"
msgid "currently available: %s" msgid "currently available: %s"
msgstr "現在の残数: %s" msgstr "現在%s使用可能"
#: pretix/static/pretixpresale/js/widget/widget.js:39 #: pretix/static/pretixpresale/js/widget/widget.js:39
msgctxt "widget" msgctxt "widget"
@@ -918,7 +921,7 @@ msgstr "提供開始前"
#: pretix/static/pretixpresale/js/widget/widget.js:41 #: pretix/static/pretixpresale/js/widget/widget.js:41
msgctxt "widget" msgctxt "widget"
msgid "Not available anymore" msgid "Not available anymore"
msgstr "提供終了" msgstr "今後の提供不可"
#: pretix/static/pretixpresale/js/widget/widget.js:42 #: pretix/static/pretixpresale/js/widget/widget.js:42
msgctxt "widget" msgctxt "widget"
@@ -934,7 +937,7 @@ msgstr "最小注文数量:%s"
#: pretix/static/pretixpresale/js/widget/widget.js:45 #: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget" msgctxt "widget"
msgid "Close ticket shop" msgid "Close ticket shop"
msgstr "チケットショップを閉じる" msgstr "チケットショップ閉店"
#: pretix/static/pretixpresale/js/widget/widget.js:46 #: pretix/static/pretixpresale/js/widget/widget.js:46
msgctxt "widget" msgctxt "widget"
@@ -947,8 +950,8 @@ msgid ""
"There are currently a lot of users in this ticket shop. Please open the shop " "There are currently a lot of users in this ticket shop. Please open the shop "
"in a new tab to continue." "in a new tab to continue."
msgstr "" msgstr ""
"現在チケットショップが混み合っています。新しいタブでチケットショップを開いて" "現在チケットショップが混み合っています。新しいタブでチケットショップを開き続"
"行してください。" "行してください。"
#: pretix/static/pretixpresale/js/widget/widget.js:49 #: pretix/static/pretixpresale/js/widget/widget.js:49
msgctxt "widget" msgctxt "widget"
@@ -972,7 +975,7 @@ msgid ""
"this ticket shop. Please click \"Continue\" to retry in a new tab." "this ticket shop. Please click \"Continue\" to retry in a new tab."
msgstr "" msgstr ""
"現在チケットショップが混雑しているため、お客様のカートを作ることができません" "現在チケットショップが混雑しているため、お客様のカートを作ることができません"
"でした。\"続ける\"をクリックして、新しいタブで再試行してください。" "でした。新しいタブを開き「次へ」をクリックしてください。"
#: pretix/static/pretixpresale/js/widget/widget.js:54 #: pretix/static/pretixpresale/js/widget/widget.js:54
msgctxt "widget" msgctxt "widget"
@@ -985,8 +988,8 @@ msgid ""
"You currently have an active cart for this event. If you select more " "You currently have an active cart for this event. If you select more "
"products, they will be added to your existing cart." "products, they will be added to your existing cart."
msgstr "" msgstr ""
"このイベントのカートに商品が入っています。商品を追加すると、既存のカートに追" "お客様のカートはイベントの申し込みに有効です。商品を選択し、カートへ追加して"
"加されます。" "ください。"
#: pretix/static/pretixpresale/js/widget/widget.js:57 #: pretix/static/pretixpresale/js/widget/widget.js:57
msgctxt "widget" msgctxt "widget"
@@ -1076,7 +1079,7 @@ msgstr "前週"
#: pretix/static/pretixpresale/js/widget/widget.js:74 #: pretix/static/pretixpresale/js/widget/widget.js:74
msgctxt "widget" msgctxt "widget"
msgid "Open seat selection" msgid "Open seat selection"
msgstr "座席選択を開く" msgstr "座席一覧を開く"
#: pretix/static/pretixpresale/js/widget/widget.js:75 #: pretix/static/pretixpresale/js/widget/widget.js:75
msgctxt "widget" msgctxt "widget"

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