mirror of
https://github.com/pretix/pretix.git
synced 2026-08-05 09:57:49 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d18e8391a |
@@ -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
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -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,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,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
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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.')
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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'),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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'])
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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': {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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]))
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ class OrganizerSlugBanlistValidator(BanlistValidator):
|
|||||||
'csp_report',
|
'csp_report',
|
||||||
'widget',
|
'widget',
|
||||||
'lead',
|
'lead',
|
||||||
'scheduling',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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={
|
||||||
|
|||||||
@@ -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'))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
|
||||||
)
|
|
||||||
|
|||||||
+2262
-2556
File diff suppressed because it is too large
Load Diff
+2270
-2735
File diff suppressed because it is too large
Load Diff
+2262
-2556
File diff suppressed because it is too large
Load Diff
+2273
-2657
File diff suppressed because it is too large
Load Diff
+2260
-2684
File diff suppressed because it is too large
Load Diff
+2262
-2576
File diff suppressed because it is too large
Load Diff
+2293
-2690
File diff suppressed because it is too large
Load Diff
+2270
-2663
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
|
||||||
|
|||||||
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
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
|||||||
+2288
-2708
File diff suppressed because it is too large
Load Diff
+2262
-2556
File diff suppressed because it is too large
Load Diff
+2292
-2687
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2262
-2558
File diff suppressed because it is too large
Load Diff
+2256
-2643
File diff suppressed because it is too large
Load Diff
+2258
-2684
File diff suppressed because it is too large
Load Diff
+2264
-2573
File diff suppressed because it is too large
Load Diff
+2285
-2685
File diff suppressed because it is too large
Load Diff
+2471
-2885
File diff suppressed because it is too large
Load Diff
@@ -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 they’re available."
|
"complete your order as long as they’re 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"
|
||||||
|
|||||||
+2454
-2852
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2254
-2662
File diff suppressed because it is too large
Load Diff
+2263
-2631
File diff suppressed because it is too large
Load Diff
+2259
-2716
File diff suppressed because it is too large
Load Diff
+2261
-2633
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
|||||||
+3318
-3695
File diff suppressed because it is too large
Load Diff
@@ -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 they’re available."
|
"complete your order as long as they’re 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
Reference in New Issue
Block a user