Compare commits

..
203 changed files with 15591 additions and 28081 deletions
-2
View File
@@ -71,8 +71,6 @@ Checking a ticket in
:>json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
:>json object media_policy: Reusable media policy (see documentation on items), only set on status ``"exchange"``.
:>json object media_type: Reusable media type (see documentation on items), only set on status ``"exchange"``.
:>json boolean simulate: Do not actually perform the check-in, only simulate the response. The ``position`` response
object will not reflect the simulated changes.
**Example request**:
+2 -2
View File
@@ -2038,7 +2038,7 @@ Manipulating individual positions
* ``order`` (mandatory, specified as a string mapping to a ``code``)
* ``addon_to`` (optional, specified as an integer mapping to ``positionid`` - the number of the position within the order, see :ref:`_order-position-resource` - of the parent position)
* ``addon_to`` (optional, specified as an integer mapping to the ``positionid`` of the parent position)
* ``item`` (mandatory)
@@ -2348,7 +2348,7 @@ otherwise, such as splitting an order or changing fees.
"subevent": 562,
"seat": "seat-guid-2",
"price": "99.99",
"addon_to": 1,
"addon_to": 12374,
"attendee_name": "Peter",
}
],
+1 -1
View File
@@ -83,7 +83,7 @@ Dashboards
.. automodule:: pretix.control.signals
:no-index:
:members: event_dashboard_widgets, user_dashboard_widgets, event_dashboard_top
:members: event_dashboard_statistics, user_dashboard_widgets, event_dashboard_top
Ticket designs
""""""""""""""
+7 -7
View File
@@ -3176,9 +3176,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"funding": [
{
"type": "github",
@@ -3352,9 +3352,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"funding": [
{
"type": "opencollective",
@@ -3371,7 +3371,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.16",
"nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
+7 -7
View File
@@ -33,12 +33,12 @@ dependencies = [
"bleach==6.4.*",
"celery==5.6.*",
"chardet==5.2.*",
"cryptography>=50.0.0",
"cryptography>=49.0.0",
"css-inline==0.21.*",
"defusedcsv>=3.0.0",
"dnspython==2.*",
"Django[argon2]==5.2.*",
"django-bootstrap3==26.2",
"django-bootstrap3==26.1",
"django-compressor==4.6.0",
"django-countries==9.0.*",
"django-filter==26.1",
@@ -48,11 +48,11 @@ dependencies = [
"django-hijack==3.7.*",
"django-i18nfield==1.11.*",
"django-libsass==0.9",
"django-localflavor==5.1",
"django-localflavor==5.0",
"django-markup",
"django-oauth-toolkit==2.3.*",
"django-otp==1.7.*",
"django-phonenumber-field==8.5.*",
"django-phonenumber-field==8.4.*",
"django-querytagger==0.0.3",
"django-redis==7.0.*",
"django-scopes==2.1.*",
@@ -67,7 +67,7 @@ dependencies = [
"kombu==5.6.*",
"libsass==0.23.*",
"lxml",
"markdown==3.10.3", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
"markdown==3.10.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*",
"oauthlib==3.3.*",
@@ -94,11 +94,11 @@ dependencies = [
"redis==7.4.*",
"reportlab==5.0.*",
"requests==2.34.*",
"sentry-sdk==2.68.*",
"sentry-sdk==2.66.*",
"sepaxml==2.7.*",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2026072401",
"tlds>=2026041800",
"tqdm==4.*",
"ua-parser==1.0.*",
"vobject==0.9.*",
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2026.8.0.dev0"
__version__ = "2026.7.0.dev0"
-1
View File
@@ -104,7 +104,6 @@ ALL_LANGUAGES = [
('gl', _('Galician')),
('el', _('Greek')),
('he', _('Hebrew')),
('hu', _('Hungarian')),
('id', _('Indonesian')),
('it', _('Italian')),
('ja', _('Japanese')),
+1 -25
View File
@@ -20,11 +20,8 @@
# <https://www.gnu.org/licenses/>.
#
import logging
from datetime import timedelta
from django.contrib.auth.models import AnonymousUser
from django.db import DatabaseError
from django.utils.timezone import now
from django_scopes import scopes_disabled
from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication
@@ -33,7 +30,6 @@ from pretix.api.auth.devicesecurity import (
FullAccessSecurityProfile, get_all_security_profiles,
)
from pretix.base.models import Device
from pretix.base.models.devices import DeviceLastSeen
logger = logging.getLogger(__name__)
@@ -46,7 +42,7 @@ class DeviceTokenAuthentication(TokenAuthentication):
model = self.get_model()
try:
with scopes_disabled():
device = model.objects.select_related('organizer', 'last_seen').get(api_token=key)
device = model.objects.select_related('organizer').get(api_token=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
@@ -57,7 +53,6 @@ class DeviceTokenAuthentication(TokenAuthentication):
logging.warning(f'Connection attempt of revoked device {device.pk}.')
raise exceptions.AuthenticationFailed('Device access has been revoked.')
self._update_last_seen(device)
return AnonymousUser(), device
def authenticate(self, request):
@@ -68,22 +63,3 @@ class DeviceTokenAuthentication(TokenAuthentication):
if not profile.is_allowed(request):
raise exceptions.PermissionDenied('Request denied by device security profile.')
return r
def _update_last_seen(self, device: Device):
try:
try:
last_seen_obj = device.last_seen
except DeviceLastSeen.DoesNotExist:
# First request from device, create model, ignore result. Use get_or_create to be safe
# against concurrent create requests
DeviceLastSeen.objects.get_or_create(device=device, last_seen=now())
else:
if now() - last_seen_obj.last_seen < timedelta(seconds=10):
# We don't need to know the last seen info of a device to more precision than this,
# so we can avoid some database writes if the device is bursting a lot of requests.
return
last_seen_obj.last_seen = now()
last_seen_obj.save(update_fields=["last_seen"])
except DatabaseError:
# Do not stop the request from happening
logger.exception("Database error while updating last_seen")
+7 -16
View File
@@ -20,6 +20,7 @@
# <https://www.gnu.org/licenses/>.
#
import logging
from collections import OrderedDict
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
@@ -51,18 +52,10 @@ class BaseSecurityProfile:
"""
raise NotImplementedError()
@property
def priority(self) -> int:
"""
Priority for ordering, higher will come first.
"""
return 100
class FullAccessSecurityProfile(BaseSecurityProfile):
identifier = 'full'
verbose_name = _('Full device access (reading and changing orders and gift cards, reading of products and settings)')
priority = 1000
def is_allowed(self, request):
return True
@@ -115,7 +108,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
('POST', 'api-v1:checkinrpc.annul'),
('POST', 'api-v1:checkinrpc.annull'),
('GET', 'api-v1:checkinrpc.search'),
('GET', 'api-v1:reusablemedium-list'),
('POST', 'api-v1:reusablemedium-lookup'),
@@ -154,7 +147,7 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
('POST', 'api-v1:checkinrpc.annul'),
('POST', 'api-v1:checkinrpc.annull'),
('GET', 'api-v1:checkinrpc.search'),
)
@@ -191,7 +184,7 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
('GET', 'api-v1:event.settings'),
('POST', 'api-v1:upload'),
('POST', 'api-v1:checkinrpc.redeem'),
('POST', 'api-v1:checkinrpc.annul'),
('POST', 'api-v1:checkinrpc.annull'),
('GET', 'api-v1:checkinrpc.search'),
)
@@ -202,15 +195,13 @@ def get_all_security_profiles():
if _ALL_PROFILES:
return _ALL_PROFILES
types = []
types = OrderedDict()
for recv, ret in register_device_security_profile.send(None):
if isinstance(ret, (list, tuple)):
for r in ret:
types.append(r)
types[r.identifier] = r
else:
types.append(ret)
types.sort(key=lambda el: el.priority, reverse=True)
types = {r.identifier: r for r in types}
types[ret.identifier] = ret
_ALL_PROFILES = types
return types
-1
View File
@@ -90,7 +90,6 @@ class CheckinRPCRedeemInputSerializer(serializers.Serializer):
answers = serializers.JSONField(required=False, allow_null=True)
exchange_medium_type = serializers.ChoiceField(required=False, choices=MEDIA_TYPES)
exchange_medium_identifier = serializers.CharField(required=False)
simulate = serializers.BooleanField(default=False, required=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+1 -1
View File
@@ -619,7 +619,7 @@ class QuestionSerializer(I18nAwareModelSerializer):
options_data = validated_data.pop('options') if 'options' in validated_data else []
items = validated_data.pop('items', [])
question = Question.objects.create(**validated_data, container_type=Question.ContainerType.ORDERPOSITION)
question = Question.objects.create(**validated_data)
question.items.set(items)
for opt_data in options_data:
QuestionOption.objects.create(question=question, **opt_data)
-8
View File
@@ -139,7 +139,6 @@ class CheckinListViewSet(viewsets.ModelViewSet):
)
return qs
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -154,7 +153,6 @@ class CheckinListViewSet(viewsets.ModelViewSet):
ctx['event'] = self.request.event
return ctx
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -839,11 +837,6 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
)
if exchange_medium_identifier: # other fields are filled, see CheckinRPCRedeemInputSerializer.validate
if simulate:
raise CheckInError(
gettext('You cannot simulate a medium exchange.'),
'error'
)
with transaction.atomic():
# Do exchange and check-in atomically, i.e. both succeed or both fail
medium = perform_media_exchange(
@@ -1071,7 +1064,6 @@ class CheckinRPCRedeemView(views.APIView):
legacy_url_support=False,
exchange_medium_type=s.validated_data.get('exchange_medium_type'),
exchange_medium_identifier=s.validated_data.get('exchange_medium_identifier'),
simulate=s.validated_data.get('simulate'),
)
-4
View File
@@ -32,7 +32,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
from django.db import transaction
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets
@@ -65,7 +64,6 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
'limit_sales_channels',
)
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -80,7 +78,6 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
ctx['event'] = self.request.event
return ctx
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -90,7 +87,6 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('You cannot delete this discount because it already has '
+1 -13
View File
@@ -45,7 +45,6 @@ from rest_framework.exceptions import (
NotFound, PermissionDenied, ValidationError,
)
from rest_framework.generics import get_object_or_404
from rest_framework.mixins import UpdateModelMixin
from rest_framework.response import Response
from pretix.api.auth.permission import EventCRUDPermission
@@ -257,7 +256,6 @@ class EventViewSet(viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_create(self, serializer):
copy_from = None
if 'clone_from' in self.request.GET:
@@ -321,7 +319,6 @@ class EventViewSet(viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('The event can not be deleted as it already contains orders. Please set \'live\''
@@ -357,7 +354,6 @@ class CloneEventViewSet(viewsets.ModelViewSet):
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_create(self, serializer):
# Weird edge case: Requires settings permission on the event (to read) but also on the organizer (two write)
perm_holder = (self.request.auth if isinstance(self.request.auth, (Device, TeamAPIToken))
@@ -516,7 +512,6 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
resp['X-Page-Generated'] = date
return resp
@transaction.atomic()
def perform_update(self, serializer):
original_data = self.get_serializer(instance=serializer.instance).data
super().perform_update(serializer)
@@ -533,7 +528,6 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -543,7 +537,6 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('The sub-event can not be deleted as it has already been used in orders. Please set'
@@ -572,7 +565,6 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
def get_queryset(self):
return self.request.event.tax_rules.all()
@transaction.atomic()
def perform_update(self, serializer):
super().perform_update(serializer)
serializer.instance.log_action(
@@ -582,7 +574,6 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -592,7 +583,6 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('This tax rule can not be deleted as it is currently in use.')
@@ -721,7 +711,7 @@ class SeatFilter(FilterSet):
fields = ('zone_name', 'row_name', 'row_label', 'seat_number', 'seat_label', 'seat_guid', 'blocked',)
class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
class SeatViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SeatSerializer
queryset = Seat.objects.none()
write_permission = 'event.settings.general:write'
@@ -766,7 +756,6 @@ class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelV
}
return ctx
@transaction.atomic()
def perform_update(self, serializer):
super().perform_update(serializer)
serializer.instance.event.log_action(
@@ -776,7 +765,6 @@ class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelV
data={"seats": [serializer.instance.pk]},
)
@transaction.atomic()
def bulk_change_blocked(self, blocked):
s = SeatBulkBlockInputSerializer(
data=self.request.data,
+5 -19
View File
@@ -23,7 +23,6 @@ from datetime import timedelta
from celery.result import AsyncResult
from django.conf import settings
from django.db import transaction
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
@@ -46,8 +45,7 @@ from pretix.base.models import (
)
from pretix.base.models.organizer import TeamQuerySet
from pretix.base.services.export import (
ExportError, export, init_event_exporters, init_organizer_exporters,
multiexport,
export, init_event_exporters, init_organizer_exporters, multiexport,
)
from pretix.helpers.http import ChunkBasedFileResponse
@@ -151,11 +149,8 @@ class EventExportersViewSet(ExportersMixin, viewsets.ViewSet):
))
exporters = []
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
try:
ex._serializer = JobRunSerializer(exporter=ex)
exporters.append(ex)
except ExportError:
pass
ex._serializer = JobRunSerializer(exporter=ex)
exporters.append(ex)
return exporters
def do_export(self, cf, instance, data):
@@ -185,11 +180,8 @@ class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
))
exporters = []
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
try:
ex._serializer = JobRunSerializer(exporter=ex)
exporters.append(ex)
except ExportError:
pass
ex._serializer = JobRunSerializer(exporter=ex)
exporters.append(ex)
return exporters
def do_export(self, cf, instance, data):
@@ -228,7 +220,6 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet):
qs = self.request.event.scheduled_exports
return qs.select_related("owner")
@transaction.atomic()
def perform_create(self, serializer):
if not self.request.user.is_authenticated:
raise PermissionDenied('Creation of exports requires user-specific API access.')
@@ -259,7 +250,6 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet):
))
return {e.identifier: e for e in exporters}
@transaction.atomic()
def perform_update(self, serializer):
if not self.request.user.is_authenticated or self.request.user != serializer.instance.owner:
# This is to prevent a possible privilege escalation where user A creates a scheduled export and
@@ -285,7 +275,6 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
self.request.event.log_action(
'pretix.event.export.schedule.deleted',
@@ -313,7 +302,6 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
qs = self.request.organizer.scheduled_exports
return qs.select_related("owner")
@transaction.atomic()
def perform_create(self, serializer):
if not self.request.user.is_authenticated:
raise PermissionDenied('Creation of exports requires user-specific API access.')
@@ -344,7 +332,6 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
))
return {e.identifier: e for e in exporters}
@transaction.atomic()
def perform_update(self, serializer):
if not self.request.user.is_authenticated or self.request.user != serializer.instance.owner:
# This is to prevent a possible privilege escalation where user A creates a scheduled export and
@@ -395,7 +382,6 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
self.request.organizer.log_action(
'pretix.organizer.export.schedule.deleted',
+1 -32
View File
@@ -33,7 +33,6 @@
# License for the specific language governing permissions and limitations under the License.
import django_filters
from django.db import transaction
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
@@ -110,7 +109,6 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
'limit_sales_channels', 'variations__limit_sales_channels', 'program_times'
).all()
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -125,7 +123,6 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
ctx['event'] = self.request.event
return ctx
@transaction.atomic()
def perform_update(self, serializer):
original_data = self.get_serializer(instance=serializer.instance).data
@@ -142,7 +139,6 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('This item cannot be deleted because it has already been ordered '
@@ -187,7 +183,6 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
ctx['event'] = self.request.event
return ctx
@transaction.atomic()
def perform_create(self, serializer):
item = self.item
if not item.has_variations:
@@ -202,7 +197,6 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
{'value': serializer.instance.value})
)
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.item.log_action(
@@ -213,7 +207,6 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
{'value': serializer.instance.value})
)
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('This variation cannot be deleted because it has already been ordered '
@@ -256,7 +249,6 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
ctx['item'] = self.item
return ctx
@transaction.atomic()
def perform_create(self, serializer):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
serializer.save(base_item=item)
@@ -267,7 +259,6 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.base_item.log_action(
@@ -277,7 +268,6 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_destroy(self, instance):
super().perform_destroy(instance)
instance.base_item.log_action(
@@ -313,7 +303,6 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
ctx['item'] = self.item
return ctx
@transaction.atomic()
def perform_create(self, serializer):
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
serializer.save(item=item)
@@ -324,7 +313,6 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.item.log_action(
@@ -334,7 +322,6 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_destroy(self, instance):
super().perform_destroy(instance)
instance.item.log_action(
@@ -367,7 +354,6 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
ctx['item'] = self.item
return ctx
@transaction.atomic()
def perform_create(self, serializer):
item = self.item
category = get_object_or_404(ItemCategory, pk=self.request.data['addon_category'])
@@ -379,7 +365,6 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.base_item.log_action(
@@ -389,7 +374,6 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_destroy(self, instance):
super().perform_destroy(instance)
instance.base_item.log_action(
@@ -419,7 +403,6 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
def get_queryset(self):
return self.request.event.categories.all()
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -434,7 +417,6 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
ctx['event'] = self.request.event
return ctx
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -444,7 +426,6 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
for item in instance.items.all():
item.category = None
@@ -475,12 +456,8 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
write_permission = 'event.items:write'
def get_queryset(self):
return self.request.event.questions.filter(
# the container_type parameter is undocumented, this API is going to change in a later release
container_type=self.request.GET.get('container_type', Question.ContainerType.ORDERPOSITION),
).prefetch_related('options').all()
return self.request.event.questions.prefetch_related('options').all()
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -495,7 +472,6 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
ctx['event'] = self.request.event
return ctx
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -505,7 +481,6 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
data=self.request.data
)
@transaction.atomic()
def perform_destroy(self, instance):
instance.log_action(
'pretix.event.question.deleted',
@@ -534,7 +509,6 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
ctx['question'] = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event)
return ctx
@transaction.atomic()
def perform_create(self, serializer):
q = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event)
serializer.save(question=q)
@@ -545,7 +519,6 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.question.log_action(
@@ -555,7 +528,6 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
)
@transaction.atomic()
def perform_destroy(self, instance):
instance.question.log_action(
'pretix.event.question.option.deleted',
@@ -614,7 +586,6 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -637,7 +608,6 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
ctx['request'] = self.request
return ctx
@transaction.atomic()
def perform_update(self, serializer):
original_data = self.get_serializer(instance=serializer.instance).data
@@ -693,7 +663,6 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
)
serializer.instance.rebuild_cache()
@transaction.atomic()
def perform_destroy(self, instance):
instance.log_action(
'pretix.event.quota.deleted',
-3
View File
@@ -394,7 +394,6 @@ class TeamViewSet(viewsets.ModelViewSet):
)
return inst
@transaction.atomic()
def perform_destroy(self, instance):
instance.log_action('pretix.team.deleted', user=self.request.user, auth=self.request.auth)
instance.delete()
@@ -694,7 +693,6 @@ class MembershipTypeViewSet(viewsets.ModelViewSet):
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied("Can only be deleted if unused.")
@@ -835,7 +833,6 @@ class SalesChannelViewSet(viewsets.ModelViewSet):
)
return inst
@transaction.atomic()
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied("Can only be deleted if unused.")
-4
View File
@@ -20,7 +20,6 @@
# <https://www.gnu.org/licenses/>.
#
import django_filters
from django.db import transaction
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets
@@ -63,7 +62,6 @@ class WaitingListViewSet(viewsets.ModelViewSet):
ctx['event'] = self.request.event
return ctx
@transaction.atomic()
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
@@ -72,7 +70,6 @@ class WaitingListViewSet(viewsets.ModelViewSet):
auth=self.request.auth,
)
@transaction.atomic()
def perform_update(self, serializer):
if serializer.instance.voucher:
raise PermissionDenied('This entry can not be changed as it has already been assigned a voucher.')
@@ -83,7 +80,6 @@ class WaitingListViewSet(viewsets.ModelViewSet):
auth=self.request.auth,
)
@transaction.atomic()
def perform_destroy(self, instance):
if instance.voucher:
raise PermissionDenied('This entry can not be deleted as it has already been assigned a voucher.')
-4
View File
@@ -20,7 +20,6 @@
# <https://www.gnu.org/licenses/>.
#
import django_filters
from django.db import transaction
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import viewsets
@@ -49,7 +48,6 @@ class WebHookViewSet(viewsets.ModelViewSet):
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_create(self, serializer):
inst = serializer.save(organizer=self.request.organizer)
self.request.organizer.log_action(
@@ -59,7 +57,6 @@ class WebHookViewSet(viewsets.ModelViewSet):
data=merge_dicts(self.request.data, {'id': inst.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
inst = serializer.save(organizer=self.request.organizer)
self.request.organizer.log_action(
@@ -70,7 +67,6 @@ class WebHookViewSet(viewsets.ModelViewSet):
)
return inst
@transaction.atomic()
def perform_destroy(self, instance):
self.request.organizer.log_action(
'pretix.webhook.changed',
+3 -57
View File
@@ -40,12 +40,11 @@ from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from pretix.base.settings import PERSON_NAME_SCHEMES
from ..exporter import MultiSheetListExporter, OrganizerLevelExportMixin
from ..models import Membership
from ..exporter import ListExporter, OrganizerLevelExportMixin
from ..signals import register_multievent_data_exporters
class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'customerlist'
verbose_name = gettext_lazy('Customer accounts')
category = pgettext_lazy('export_category', 'Customer accounts')
@@ -55,20 +54,13 @@ class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
def get_required_organizer_permission(cls) -> str:
return 'organizer.customers:write'
@property
def sheets(self):
return (
('customers', _('Customers')),
('memberships', _('Memberships')),
)
@property
def additional_form_fields(self):
return OrderedDict(
[]
)
def iterate_customers(self, form_data):
def iterate_list(self, form_data):
qs = self.organizer.customers.prefetch_related('provider')
headers = [
@@ -117,52 +109,6 @@ class CustomerListExporter(OrganizerLevelExportMixin, MultiSheetListExporter):
]
yield row
def iterate_memberships(self, form_data):
qs = Membership.objects.filter(
customer__organizer=self.organizer
).prefetch_related('membership_type').select_related('customer', 'granted_in', 'granted_in__order')
headers = [
_('Customer ID'),
_('External identifier'),
_('Email'),
_('Test mode'),
_('Canceled'),
_('Membership type'),
_('Purchase ticket'),
_('Start date'),
_('Start time'),
_('End date'),
_('End time'),
_('Name'),
]
name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme]
if name_scheme and len(name_scheme['fields']) > 1:
for k, label, w in name_scheme['fields']:
headers.append(_('Name') + ': ' + str(label))
yield headers
tz = get_current_timezone()
for obj in qs:
row = [
obj.customer.identifier,
obj.customer.external_identifier,
obj.customer.email or '',
_('Yes') if obj.testmode else _('No'),
_('Yes') if obj.canceled else _('No'),
str(obj.membership_type.name),
f'{obj.granted_in.order.code}-{obj.granted_in.positionid}' if obj.granted_in else None,
obj.date_start.astimezone(tz).strftime('%Y-%m-%d'),
obj.date_start.astimezone(tz).strftime('%H:%M'),
obj.date_end.astimezone(tz).strftime('%Y-%m-%d'),
obj.date_end.astimezone(tz).strftime('%H:%M'),
obj.attendee_name or '',
]
if name_scheme and len(name_scheme['fields']) > 1:
for k, label, w in name_scheme['fields']:
row.append(obj.attendee_name_parts.get(k, ''))
yield row
def get_filename(self):
return '{}_customers'.format(self.organizer.slug)
+425 -510
View File
@@ -36,7 +36,6 @@ import copy
import json
import logging
import re
from collections import namedtuple
from datetime import timedelta
from decimal import Decimal
from io import BytesIO
@@ -637,271 +636,449 @@ class PortraitImageField(SizeValidationMixin, ExtValidationMixin, forms.FileFiel
super().__init__(*args, **kwargs)
FakeQuestion = namedtuple(
'FakeQuestion', 'id question position required help_text container_type', defaults=('', Question.ContainerType.ORDERPOSITION)
)
def get_fake_attendee_questions(settings):
fq = []
sqo = settings.system_question_order
if settings.attendee_names_asked:
fq.append(FakeQuestion('attendee_name_parts', _('Attendee name'), sqo.get('attendee_name_parts', 0), settings.attendee_names_required))
if settings.attendee_emails_asked:
fq.append(FakeQuestion('attendee_email', _('Attendee email'), sqo.get('attendee_email', 0), settings.attendee_emails_required))
if settings.attendee_company_asked:
fq.append(FakeQuestion('company', _('Company'), sqo.get('company', 0), settings.attendee_company_required))
if settings.attendee_addresses_asked:
fq.append(FakeQuestion('street', _('Street'), sqo.get('street', 0), settings.attendee_addresses_required))
fq.append(FakeQuestion('zipcode', _('ZIP code'), sqo.get('zipcode', 0), settings.attendee_addresses_required))
fq.append(FakeQuestion('city', _('City'), sqo.get('city', 0), settings.attendee_addresses_required))
fq.append(FakeQuestion('state', _('State'), sqo.get('country', 0), settings.attendee_addresses_required))
fq.append(FakeQuestion('country', _('Country'), sqo.get('country', 0), settings.attendee_addresses_required))
return fq
class BaseQuestionsForm(forms.Form):
"""
This is the base form class responsible for asking order- or ticket-related questions.
This form class is responsible for asking order-related questions. This includes
the attendee name for admission tickets, if the corresponding setting is enabled,
as well as additional questions defined by the organizer.
"""
address_validation = False
def build_user_question_field(self, request, event, answerlist, q):
# Do we already have an answer? Provide it as the initial value
answers = [a for a in answerlist if a.question_id == q.id]
if answers:
initial = answers[0]
else:
def __init__(self, *args, **kwargs):
"""
Takes two additional keyword arguments:
:param cartpos: The cart position the form should be for
:param event: The event this belongs to
"""
request = kwargs.pop('request', None)
cartpos = self.cartpos = kwargs.pop('cartpos', None)
orderpos = self.orderpos = kwargs.pop('orderpos', None)
pos = cartpos or orderpos
item = pos.item
questions = pos.item.questions_to_ask
event = kwargs.pop('event')
self.all_optional = kwargs.pop('all_optional', False)
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
super().__init__(*args, **kwargs)
if cartpos and item.validity_mode == Item.VALIDITY_MODE_DYNAMIC and item.validity_dynamic_start_choice:
if item.validity_dynamic_start_choice_day_limit:
max_date = time_machine_now().astimezone(event.timezone) + timedelta(days=item.validity_dynamic_start_choice_day_limit)
else:
max_date = None
min_date = time_machine_now()
initial = None
tz = ZoneInfo(event.settings.timezone)
required = q.required and not self.all_optional
if q.type == Question.TYPE_BOOLEAN:
if required:
# For some reason, django-bootstrap3 does not set the required attribute
# itself.
widget = forms.CheckboxInput(attrs={'required': 'required'})
if (item.require_membership or (pos.variation and pos.variation.require_membership)) and pos.used_membership:
if pos.used_membership.date_start >= time_machine_now():
initial = min_date = pos.used_membership.date_start
max_date = min(max_date, pos.used_membership.date_end) if max_date else pos.used_membership.date_end
if item.validity_dynamic_duration_months or item.validity_dynamic_duration_days:
attrs = {}
if max_date:
attrs['data-max'] = max_date.date().isoformat()
if min_date:
attrs['data-min'] = min_date.date().isoformat()
self.fields['requested_valid_from'] = forms.DateField(
label=_('Start date'),
help_text='' if initial else _('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=bool(initial),
initial=pos.requested_valid_from or initial,
widget=DatePickerWidget(attrs),
validators=([MaxDateValidator(max_date.date())] if max_date else []) + [MinDateValidator(min_date.date())]
)
else:
widget = forms.CheckboxInput()
self.fields['requested_valid_from'] = forms.SplitDateTimeField(
label=_('Start date'),
help_text='' if initial else _('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=bool(initial),
initial=pos.requested_valid_from or initial,
widget=SplitDateTimePickerWidget(
time_format=get_format_without_seconds('TIME_INPUT_FORMATS'),
min_date=min_date,
max_date=max_date
),
validators=([MaxDateTimeValidator(max_date)] if max_date else []) + [MinDateTimeValidator(min_date)]
)
if initial:
initialbool = (initial.answer == "True")
else:
initialbool = False
add_fields = {}
field = forms.BooleanField(
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
initial=initialbool, widget=widget,
if item.ask_attendee_data and event.settings.attendee_names_asked:
add_fields['attendee_name_parts'] = NamePartsFormField(
max_length=255,
required=event.settings.attendee_names_required and not self.all_optional,
scheme=event.settings.name_scheme,
titles=event.settings.name_scheme_titles,
label=_('Attendee name'),
initial=(cartpos.attendee_name_parts if cartpos else orderpos.attendee_name_parts),
)
elif q.type == Question.TYPE_NUMBER:
field = forms.DecimalField(
label=escape(q.question), required=required,
min_value=q.valid_number_min or Decimal('0.00'),
max_value=q.valid_number_max,
help_text=rich_text(q.help_text),
initial=initial.answer if initial else None,
if item.ask_attendee_data and event.settings.attendee_emails_asked:
add_fields['attendee_email'] = forms.EmailField(
required=event.settings.attendee_emails_required and not self.all_optional,
label=_('Attendee email'),
initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email),
widget=forms.EmailInput(
attrs={
'autocomplete': 'email'
}
)
)
elif q.type == Question.TYPE_STRING:
field = forms.CharField(
label=escape(q.question), required=required,
max_length=q.valid_string_length_max,
help_text=rich_text(q.help_text),
initial=initial.answer if initial else None,
if item.ask_attendee_data and event.settings.attendee_company_asked:
add_fields['company'] = forms.CharField(
required=event.settings.attendee_company_required and not self.all_optional,
label=_('Company'),
max_length=255,
initial=(cartpos.company if cartpos else orderpos.company),
)
elif q.type == Question.TYPE_TEXT:
field = forms.CharField(
label=escape(q.question), required=required,
max_length=q.valid_string_length_max,
help_text=rich_text(q.help_text),
widget=forms.Textarea,
initial=initial.answer if initial else None,
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
add_fields['street'] = forms.CharField(
required=self.attendee_addresses_required,
label=_('Address'),
widget=forms.Textarea(attrs={
'rows': 2,
'placeholder': _('Street and Number'),
'autocomplete': 'street-address'
}),
initial=(cartpos.street if cartpos else orderpos.street),
)
elif q.type == Question.TYPE_COUNTRYCODE:
field = CountryField(
countries=CachedCountries,
blank=True, null=True, blank_label=' ',
add_fields['zipcode'] = forms.CharField(
required=False,
max_length=30,
label=_('ZIP code'),
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
widget=forms.TextInput(attrs={
'autocomplete': 'postal-code',
}),
)
add_fields['city'] = forms.CharField(
required=False,
label=_('City'),
max_length=255,
initial=(cartpos.city if cartpos else orderpos.city),
widget=forms.TextInput(attrs={
'autocomplete': 'address-level2',
}),
)
country = (cartpos.country if cartpos else orderpos.country) or guess_country_from_request(request, event)
add_fields['country'] = CountryField(
countries=CachedCountries
).formfield(
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
widget=forms.Select,
empty_label=' ',
initial=initial.answer if initial else (
guess_country_from_request(request, event) if required else None),
required=self.attendee_addresses_required,
label=_('Country'),
initial=country,
widget=forms.Select(attrs={
'autocomplete': 'country',
'data-trigger-address-info': 'on',
}),
)
elif q.type == Question.TYPE_CHOICE:
field = forms.ModelChoiceField(
queryset=q.options,
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
widget=forms.Select,
to_field_name='identifier',
empty_label='',
initial=initial.options.first() if initial else None,
c = [('', '---')]
fprefix = str(self.prefix) + '-' if self.prefix is not None and self.prefix != '-' else ''
cc = None
state = None
if fprefix + 'country' in self.data:
cc = str(self.data[fprefix + 'country'])
elif country:
cc = str(country)
if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS:
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1])
state = (cartpos.state if cartpos else orderpos.state)
elif fprefix + 'state' in self.data:
self.data = self.data.copy()
del self.data[fprefix + 'state']
add_fields['state'] = forms.ChoiceField(
label=pgettext_lazy('address', 'State'),
required=False,
choices=c,
initial=state,
widget=forms.Select(attrs={
'autocomplete': 'address-level1',
}),
)
elif q.type == Question.TYPE_CHOICE_MULTIPLE:
field = forms.ModelMultipleChoiceField(
queryset=q.options,
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
to_field_name='identifier',
widget=QuestionCheckboxSelectMultiple,
initial=initial.options.all() if initial else None,
)
elif q.type == Question.TYPE_FILE:
if q.valid_file_portrait:
field = PortraitImageField(
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
initial=initial.file if initial else None,
widget=PortraitImageWidget(answer=initial, request=request,
attrs={'data-portrait-photo': 'true'}),
add_fields['state'].widget.is_required = True
field_positions = list(
[
(n, event.settings.system_question_order.get(n if n != 'state' else 'country', 0))
for n in add_fields.keys()
]
)
for q in questions:
# Do we already have an answer? Provide it as the initial value
answers = [a for a in pos.answerlist if a.question_id == q.id]
if answers:
initial = answers[0]
else:
initial = None
tz = ZoneInfo(event.settings.timezone)
help_text = rich_text(q.help_text)
label = escape(q.question) # django-bootstrap3 calls mark_safe
required = q.required and not self.all_optional
if q.type == Question.TYPE_BOOLEAN:
if required:
# For some reason, django-bootstrap3 does not set the required attribute
# itself.
widget = forms.CheckboxInput(attrs={'required': 'required'})
else:
widget = forms.CheckboxInput()
if initial:
initialbool = (initial.answer == "True")
else:
initialbool = False
field = forms.BooleanField(
label=label, required=required,
help_text=help_text,
initial=initialbool, widget=widget,
)
else:
field = ExtFileField(
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
initial=initial.file if initial else None,
widget=UploadedFileWidget(answer=initial, request=request),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
elif q.type == Question.TYPE_NUMBER:
field = forms.DecimalField(
label=label, required=required,
min_value=q.valid_number_min or Decimal('0.00'),
max_value=q.valid_number_max,
help_text=help_text,
initial=initial.answer if initial else None,
)
elif q.type == Question.TYPE_DATE:
attrs = {}
if q.valid_date_min:
attrs['data-min'] = q.valid_date_min.isoformat()
if q.valid_date_max:
attrs['data-max'] = q.valid_date_max.isoformat()
help_text = q.help_text
if not help_text:
if q.valid_date_min and q.valid_date_max:
help_text = format_lazy(
_('Please enter a date between {min} and {max}.'),
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
elif q.type == Question.TYPE_STRING:
field = forms.CharField(
label=label, required=required,
max_length=q.valid_string_length_max,
help_text=help_text,
initial=initial.answer if initial else None,
)
elif q.type == Question.TYPE_TEXT:
field = forms.CharField(
label=label, required=required,
max_length=q.valid_string_length_max,
help_text=help_text,
widget=forms.Textarea,
initial=initial.answer if initial else None,
)
elif q.type == Question.TYPE_COUNTRYCODE:
field = CountryField(
countries=CachedCountries,
blank=True, null=True, blank_label=' ',
).formfield(
label=label, required=required,
help_text=help_text,
widget=forms.Select,
empty_label=' ',
initial=initial.answer if initial else (guess_country_from_request(request, event) if required else None),
)
elif q.type == Question.TYPE_CHOICE:
field = forms.ModelChoiceField(
queryset=q.options,
label=label, required=required,
help_text=help_text,
widget=forms.Select,
to_field_name='identifier',
empty_label='',
initial=initial.options.first() if initial else None,
)
elif q.type == Question.TYPE_CHOICE_MULTIPLE:
field = forms.ModelMultipleChoiceField(
queryset=q.options,
label=label, required=required,
help_text=help_text,
to_field_name='identifier',
widget=QuestionCheckboxSelectMultiple,
initial=initial.options.all() if initial else None,
)
elif q.type == Question.TYPE_FILE:
if q.valid_file_portrait:
field = PortraitImageField(
label=label, required=required,
help_text=help_text,
initial=initial.file if initial else None,
widget=PortraitImageWidget(position=pos, event=event, answer=initial, attrs={'data-portrait-photo': 'true'}),
)
elif q.valid_date_min:
help_text = format_lazy(
_('Please enter a date no earlier than {min}.'),
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
else:
field = ExtFileField(
label=label, required=required,
help_text=help_text,
initial=initial.file if initial else None,
widget=UploadedFileWidget(position=pos, event=event, answer=initial),
ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
)
elif q.valid_date_max:
help_text = format_lazy(
_('Please enter a date no later than {max}.'),
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
)
if initial and initial.answer:
try:
_initial = dateutil.parser.parse(initial.answer).date()
except dateutil.parser.ParserError:
elif q.type == Question.TYPE_DATE:
attrs = {}
if q.valid_date_min:
attrs['data-min'] = q.valid_date_min.isoformat()
if q.valid_date_max:
attrs['data-max'] = q.valid_date_max.isoformat()
if not help_text:
if q.valid_date_min and q.valid_date_max:
help_text = format_lazy(
_('Please enter a date between {min} and {max}.'),
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
)
elif q.valid_date_min:
help_text = format_lazy(
_('Please enter a date no earlier than {min}.'),
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
)
elif q.valid_date_max:
help_text = format_lazy(
_('Please enter a date no later than {max}.'),
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
)
if initial and initial.answer:
try:
_initial = dateutil.parser.parse(initial.answer).date()
except dateutil.parser.ParserError:
_initial = None
else:
_initial = None
else:
_initial = None
field = forms.DateField(
label=escape(q.question), required=required,
help_text=rich_text(help_text),
initial=_initial,
widget=DatePickerWidget(attrs),
)
if q.valid_date_min:
field.validators.append(MinDateValidator(q.valid_date_min))
if q.valid_date_max:
field.validators.append(MaxDateValidator(q.valid_date_max))
elif q.type == Question.TYPE_TIME:
if initial and initial.answer:
try:
_initial = dateutil.parser.parse(initial.answer).time()
except dateutil.parser.ParserError:
field = forms.DateField(
label=label, required=required,
help_text=help_text,
initial=_initial,
widget=DatePickerWidget(attrs),
)
if q.valid_date_min:
field.validators.append(MinDateValidator(q.valid_date_min))
if q.valid_date_max:
field.validators.append(MaxDateValidator(q.valid_date_max))
elif q.type == Question.TYPE_TIME:
if initial and initial.answer:
try:
_initial = dateutil.parser.parse(initial.answer).time()
except dateutil.parser.ParserError:
_initial = None
else:
_initial = None
else:
_initial = None
field = forms.TimeField(
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
initial=_initial,
widget=TimePickerWidget(without_seconds=True),
)
elif q.type == Question.TYPE_DATETIME:
help_text = q.help_text
if not help_text:
if q.valid_datetime_min and q.valid_datetime_max:
help_text = format_lazy(
_('Please enter a date and time between {min} and {max}.'),
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
)
elif q.valid_datetime_min:
help_text = format_lazy(
_('Please enter a date and time no earlier than {min}.'),
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
)
elif q.valid_datetime_max:
help_text = format_lazy(
_('Please enter a date and time no later than {max}.'),
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
)
field = forms.TimeField(
label=label, required=required,
help_text=help_text,
initial=_initial,
widget=TimePickerWidget(without_seconds=True),
)
elif q.type == Question.TYPE_DATETIME:
if not help_text:
if q.valid_datetime_min and q.valid_datetime_max:
help_text = format_lazy(
_('Please enter a date and time between {min} and {max}.'),
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
)
elif q.valid_datetime_min:
help_text = format_lazy(
_('Please enter a date and time no earlier than {min}.'),
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
)
elif q.valid_datetime_max:
help_text = format_lazy(
_('Please enter a date and time no later than {max}.'),
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
)
if initial and initial.answer:
try:
_initial = dateutil.parser.parse(initial.answer).astimezone(tz)
except dateutil.parser.ParserError:
if initial and initial.answer:
try:
_initial = dateutil.parser.parse(initial.answer).astimezone(tz)
except dateutil.parser.ParserError:
_initial = None
else:
_initial = None
else:
_initial = None
field = SplitDateTimeField(
label=escape(q.question), required=required,
help_text=rich_text(help_text),
initial=_initial,
widget=SplitDateTimePickerWidget(
time_format=get_format_without_seconds('TIME_INPUT_FORMATS'),
min_date=q.valid_datetime_min,
max_date=q.valid_datetime_max
),
)
if q.valid_datetime_min:
field.validators.append(MinDateTimeValidator(q.valid_datetime_min))
if q.valid_datetime_max:
field.validators.append(MaxDateTimeValidator(q.valid_datetime_max))
elif q.type == Question.TYPE_PHONENUMBER:
if initial:
try:
initial = PhoneNumber().from_string(initial.answer)
except NumberParseException:
initial = None
field = SplitDateTimeField(
label=label, required=required,
help_text=help_text,
initial=_initial,
widget=SplitDateTimePickerWidget(
time_format=get_format_without_seconds('TIME_INPUT_FORMATS'),
min_date=q.valid_datetime_min,
max_date=q.valid_datetime_max
),
)
if q.valid_datetime_min:
field.validators.append(MinDateTimeValidator(q.valid_datetime_min))
if q.valid_datetime_max:
field.validators.append(MaxDateTimeValidator(q.valid_datetime_max))
elif q.type == Question.TYPE_PHONENUMBER:
if initial:
try:
initial = PhoneNumber().from_string(initial.answer)
except NumberParseException:
initial = None
if not initial:
phone_prefix = guess_phone_prefix_from_request(request, event)
if phone_prefix:
initial = "+{}.".format(phone_prefix)
if not initial:
phone_prefix = guess_phone_prefix_from_request(request, event)
if phone_prefix:
initial = "+{}.".format(phone_prefix)
field = PhoneNumberField(
label=escape(q.question), required=required,
help_text=rich_text(q.help_text),
# We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
# a country code but no number as an initial value. It's a bit hacky, but should be stable for
# the future.
initial=initial,
widget=WrappedPhoneNumberPrefixWidget()
)
field.question = q
if answers:
# Cache the answer object for later use
field.answer = answers[0]
field = PhoneNumberField(
label=label, required=required,
help_text=help_text,
# We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
# a country code but no number as an initial value. It's a bit hacky, but should be stable for
# the future.
initial=initial,
widget=WrappedPhoneNumberPrefixWidget()
)
field.question = q
if answers:
# Cache the answer object for later use
field.answer = answers[0]
if q.dependency_question_id:
field.widget.attrs['data-question-dependency'] = q.dependency_question_id
field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values))
if q.type != 'M':
field.widget.attrs['required'] = q.required and not self.all_optional
field._required = q.required and not self.all_optional
field.required = False
return field
if q.dependency_question_id:
field.widget.attrs['data-question-dependency'] = q.dependency_question_id
field.widget.attrs['data-question-dependency-values'] = escapejson_attr(json.dumps(q.dependency_values))
if q.type != 'M':
field.widget.attrs['required'] = q.required and not self.all_optional
field._required = q.required and not self.all_optional
field.required = False
add_fields['question_%s' % q.id] = field
field_positions.append(('question_%s' % q.id, q.position))
field_positions.sort(key=lambda e: e[1])
for fname, p in field_positions:
self.fields[fname] = add_fields[fname]
responses = question_form_fields.send(sender=event, position=pos)
data = pos.meta_info_data
for r, response in sorted(responses, key=lambda r: str(r[0])):
for key, value in response.items():
# We need to be this explicit, since OrderedDict.update does not retain ordering
self.fields[key] = value
value.initial = data.get('question_form_data', {}).get(key)
for k, v in self.fields.items():
if isinstance(v.widget, forms.MultiWidget):
for w in v.widget.widgets:
autocomplete = w.attrs.get('autocomplete', '')
if autocomplete.strip() == "off":
w.attrs['autocomplete'] = 'off'
else:
w.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + autocomplete
if v.widget.attrs.get('autocomplete') or k == 'attendee_name_parts':
autocomplete = v.widget.attrs.get('autocomplete', '')
if autocomplete.strip() == "off":
v.widget.attrs['autocomplete'] = 'off'
else:
v.widget.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + autocomplete
def clean(self):
from pretix.base.addressvalidation import \
validate_address # local import to prevent impact on startup time
d = super().clean()
if self.address_validation:
self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
if d.get('street') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if not d.get('state'):
self.add_error('state', _('This field is required.'))
def check_user_questions(self, d):
question_cache = {f.question.pk: f.question for f in self.fields.values() if getattr(f, 'question', None)}
def question_is_visible(parentid, qvals):
@@ -944,269 +1121,7 @@ class BaseQuestionsForm(forms.Form):
for q in question_cache.values():
if q.required and q.type == Question.TYPE_BOOLEAN:
if 'question_%d' % q.pk in d and d['question_%d' % q.pk] is False:
d['question_%d' % q.pk] = None
class OrderLevelQuestionsForm(BaseQuestionsForm):
def __init__(self, container, *args, **kwargs):
"""
Takes two additional keyword arguments:
:param checkoutsession: The checkout session the form should be for
:param order: The order the form should be for
:param event: The event this belongs to
"""
request = kwargs.pop('request', None)
event = kwargs.pop('event')
self.all_optional = kwargs.pop('all_optional', False)
super().__init__(*args, **kwargs)
questions = Question.objects.filter(
event=event, container_type=Question.ContainerType.ORDER,
ask_during_checkin=False, hidden=False,
).order_by('position')
answerlist = container.answers.prefetch_related('options')
for q in questions:
self.fields['question_%s' % q.id] = self.build_user_question_field(request, event, answerlist, q)
def clean(self):
d = super().clean()
self.check_user_questions(d)
return d
class TicketLevelQuestionsForm(BaseQuestionsForm):
"""
This form class is responsible for asking ticket-related questions. This includes
the attendee name for admission tickets, if the corresponding setting is enabled,
as well as additional questions defined by the organizer.
"""
def __init__(self, *args, **kwargs):
"""
Takes two additional keyword arguments:
:param cartpos: The cart position the form should be for
:param event: The event this belongs to
"""
request = kwargs.pop('request', None)
cartpos = self.cartpos = kwargs.pop('cartpos', None)
orderpos = self.orderpos = kwargs.pop('orderpos', None)
pos = cartpos or orderpos
item = pos.item
event = kwargs.pop('event')
self.all_optional = kwargs.pop('all_optional', False)
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
super().__init__(*args, **kwargs)
if cartpos and item.validity_mode == Item.VALIDITY_MODE_DYNAMIC and item.validity_dynamic_start_choice:
self.fields['requested_valid_from'] = self.build_requested_valid_from_field(event, pos, item)
questions = []
if item.ask_attendee_data:
questions += get_fake_attendee_questions(event.settings)
questions += pos.item.questions_to_ask
questions.sort(key=lambda q: q.position)
for q in questions:
if isinstance(q, FakeQuestion):
self.fields[q.id] = self.build_system_question_field(request, event, pos, q)
else:
self.fields['question_%s' % q.id] = self.build_user_question_field(request, event, pos.answerlist, q)
responses = question_form_fields.send(sender=event, position=pos)
data = pos.meta_info_data
for r, response in sorted(responses, key=lambda r: str(r[0])):
for key, value in response.items():
# We need to be this explicit, since OrderedDict.update does not retain ordering
self.fields[key] = value
value.initial = data.get('question_form_data', {}).get(key)
for k, v in self.fields.items():
if isinstance(v.widget, forms.MultiWidget):
for w in v.widget.widgets:
autocomplete = w.attrs.get('autocomplete', '')
if autocomplete.strip() == "off":
w.attrs['autocomplete'] = 'off'
else:
w.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + autocomplete
if v.widget.attrs.get('autocomplete') or k == 'attendee_name_parts':
autocomplete = v.widget.attrs.get('autocomplete', '')
if autocomplete.strip() == "off":
v.widget.attrs['autocomplete'] = 'off'
else:
v.widget.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + autocomplete
def build_requested_valid_from_field(self, event, pos, item):
if item.validity_dynamic_start_choice_day_limit:
max_date = time_machine_now().astimezone(event.timezone) + timedelta(days=item.validity_dynamic_start_choice_day_limit)
else:
max_date = None
min_date = time_machine_now()
initial = None
if (item.require_membership or (pos.variation and pos.variation.require_membership)) and pos.used_membership:
if pos.used_membership.date_start >= time_machine_now():
initial = min_date = pos.used_membership.date_start
max_date = min(max_date, pos.used_membership.date_end) if max_date else pos.used_membership.date_end
if item.validity_dynamic_duration_months or item.validity_dynamic_duration_days:
attrs = {}
if max_date:
attrs['data-max'] = max_date.date().isoformat()
if min_date:
attrs['data-min'] = min_date.date().isoformat()
return forms.DateField(
label=_('Start date'),
help_text='' if initial else _('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=bool(initial),
initial=pos.requested_valid_from or initial,
widget=DatePickerWidget(attrs),
validators=([MaxDateValidator(max_date.date())] if max_date else []) + [MinDateValidator(min_date.date())]
)
else:
return forms.SplitDateTimeField(
label=_('Start date'),
help_text='' if initial else _('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=bool(initial),
initial=pos.requested_valid_from or initial,
widget=SplitDateTimePickerWidget(
time_format=get_format_without_seconds('TIME_INPUT_FORMATS'),
min_date=min_date,
max_date=max_date
),
validators=([MaxDateTimeValidator(max_date)] if max_date else []) + [MinDateTimeValidator(min_date)]
)
def build_system_question_field(self, request, event, pos, qc):
field_name = qc.id
if field_name == 'attendee_name_parts':
return NamePartsFormField(
max_length=255,
required=qc.required and not self.all_optional,
scheme=event.settings.name_scheme,
titles=event.settings.name_scheme_titles,
label=escape(qc.question),
help_text=rich_text(qc.help_text),
initial=pos.attendee_name_parts,
)
if field_name == 'attendee_email':
return forms.EmailField(
required=qc.required and not self.all_optional,
label=escape(qc.question),
help_text=rich_text(qc.help_text),
initial=pos.attendee_email,
widget=forms.EmailInput(
attrs={
'autocomplete': 'email'
}
)
)
if field_name == 'company':
return forms.CharField(
required=qc.required and not self.all_optional,
label=escape(qc.question),
help_text=rich_text(qc.help_text),
max_length=255,
initial=pos.company,
)
if field_name == 'street':
return forms.CharField(
required=qc.required and not self.all_optional,
label=escape(qc.question),
help_text=rich_text(qc.help_text),
widget=forms.Textarea(attrs={
'rows': 2,
'placeholder': _('Street and Number'),
'autocomplete': 'street-address'
}),
initial=pos.street,
)
if field_name == 'zipcode':
return forms.CharField(
required=False,
max_length=30,
label=escape(qc.question),
help_text=rich_text(qc.help_text),
initial=pos.zipcode,
widget=forms.TextInput(attrs={
'autocomplete': 'postal-code',
}),
)
if field_name == 'city':
return forms.CharField(
required=False,
label=escape(qc.question),
help_text=rich_text(qc.help_text),
max_length=255,
initial=pos.city,
widget=forms.TextInput(attrs={
'autocomplete': 'address-level2',
}),
)
if field_name == 'country':
country = pos.country or guess_country_from_request(request, event)
return CountryField(
countries=CachedCountries
).formfield(
required=qc.required and not self.all_optional,
label=escape(qc.question),
help_text=rich_text(qc.help_text),
initial=country,
widget=forms.Select(attrs={
'autocomplete': 'country',
'data-trigger-address-info': 'on',
}),
)
if field_name == 'state':
country = pos.country or guess_country_from_request(request, event)
c = [('', '---')]
fprefix = str(self.prefix) + '-' if self.prefix is not None and self.prefix != '-' else ''
cc = None
state = None
if fprefix + 'country' in self.data:
cc = str(self.data[fprefix + 'country'])
elif country:
cc = str(country)
if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS:
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1])
state = (pos.state)
elif fprefix + 'state' in self.data:
self.data = self.data.copy()
del self.data[fprefix + 'state']
field = forms.ChoiceField(
label=escape(qc.question),
help_text=rich_text(qc.help_text),
required=False,
choices=c,
initial=state,
widget=forms.Select(attrs={
'autocomplete': 'address-level1',
}),
)
field.widget.is_required = True
return field
def clean(self):
from pretix.base.addressvalidation import \
validate_address # local import to prevent impact on startup time
d = super().clean()
if self.address_validation:
self.cleaned_data = d = validate_address(d, all_optional=not self.attendee_addresses_required)
if d.get('street') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
if not d.get('state'):
self.add_error('state', _('This field is required.'))
self.check_user_questions(d)
del d['question_%d' % q.pk]
return d
@@ -1286,15 +1201,15 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if not self.ask_vat_id:
del self.fields['vat_id']
elif self.validate_vat_id:
self.fields['vat_id'].help_text = _(
'Optional, but depending on the country you reside in we might need to charge you '
'additional taxes if you do not enter it.'
)
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but depending on the country you reside in we might need to charge you '
'additional taxes if you do not enter it.')),
])
else:
self.fields['vat_id'].help_text = _(
'Optional, but it might be required for you to claim tax benefits on your invoice '
'depending on your and the sellers country of residence.'
)
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but it might be required for you to claim tax benefits on your invoice '
'depending on your and the sellers country of residence.')),
])
transmission_type_choices = [
(t.identifier, t.public_name) for t in get_transmission_types()
@@ -1380,8 +1295,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
del self.fields['beneficiary']
if event.settings.invoice_address_custom_field:
self.fields['custom_field'].label = escape(event.settings.invoice_address_custom_field)
self.fields['custom_field'].help_text = rich_text(event.settings.invoice_address_custom_field_helptext)
self.fields['custom_field'].label = event.settings.invoice_address_custom_field
self.fields['custom_field'].help_text = event.settings.invoice_address_custom_field_helptext
else:
del self.fields['custom_field']
+18 -24
View File
@@ -47,7 +47,6 @@ from pretix.helpers.i18n import (
get_format_without_seconds, get_javascript_format,
get_javascript_format_without_seconds,
)
from pretix.helpers.safedownload import get_token
def replace_arabic_numbers(inp):
@@ -158,26 +157,36 @@ class TimePickerWidget(forms.TimeInput):
class UploadedFileWidget(forms.ClearableFileInput):
def __init__(self, *args, **kwargs):
self.position = kwargs.pop('position')
self.event = kwargs.pop('event')
self.answer = kwargs.pop('answer')
self.request = kwargs.pop('request')
super().__init__(*args, **kwargs)
class FakeFile:
def __init__(self, file, answer, request):
def __init__(self, file, position, event, answer):
self.file = file
self.position = position
self.event = event
self.answer = answer
self.request = request
def __str__(self):
return os.path.basename(self.file.name).split('.', 1)[-1]
@property
def url(self):
token = get_token(self.request, self.answer)
if self.request.resolver_match.namespace == 'control':
return self.answer.backend_file_url + '?token=' + token
from pretix.base.models import OrderPosition
from pretix.multidomain.urlreverse import eventreverse
if isinstance(self.position, OrderPosition):
return eventreverse(self.event, 'presale:event.order.download.answer', kwargs={
'order': self.position.order.code,
'secret': self.position.order.secret,
'answer': self.answer.pk,
})
else:
return self.answer.frontend_file_url + '?token=' + token
return eventreverse(self.event, 'presale:event.cart.download.answer', kwargs={
'answer': self.answer.pk,
})
def get_context(self, name, value, attrs):
# Browsers can't recognize that the server already has a file uploaded
@@ -190,7 +199,7 @@ class UploadedFileWidget(forms.ClearableFileInput):
def format_value(self, value):
if self.is_initial(value):
return self.FakeFile(value, self.answer, self.request)
return self.FakeFile(value, self.position, self.event, self.answer)
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
@@ -305,18 +314,3 @@ class BusinessBooleanRadio(forms.RadioSelect):
'False': False,
False: False,
}.get(value)
class OptionAttrsSelect(forms.Select):
def __init__(self, *args, option_attrs=None, **kwargs):
super().__init__(*args, **kwargs)
self.option_attrs = option_attrs or {}
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(
name, value, label, selected, index, subindex=subindex, attrs=attrs
)
extra = self.option_attrs.get(str(value))
if extra:
option["attrs"].update(extra)
return option
+2 -10
View File
@@ -497,16 +497,8 @@ class RejectInvalidInputMiddleware(MiddlewareMixin):
if "\x00" in request.META['QUERY_STRING'] or "%00" in request.META['QUERY_STRING']:
raise BadRequest("Invalid characters in input.")
if request.method in ('POST', 'PUT', 'PATCH') and request.content_type == "application/x-www-form-urlencoded":
try:
post_data = request.POST.lists()
except BadRequest:
# Reading request.POST wasn't possible, probably an invalid charset. Django will crash once we actually
# use request.POST, but if we don't, let's not crash it (required for some weird payment provider
# webhooks, e.g. computop).
pass
else:
if any("\x00" in value for key, value_list in post_data for value in value_list):
raise BadRequest("Invalid characters in input.")
if any("\x00" in value for key, value_list in request.POST.lists() for value in value_list):
raise BadRequest("Invalid characters in input.")
class CustomCommonMiddleware(CommonMiddleware):
@@ -1,43 +0,0 @@
# Generated by Django 5.2.16 on 2026-08-05 08:00
import django.db.models.deletion
from django.db import migrations, models
import pretix.helpers.database
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0306_alter_eventmetaproperty_unique_together"),
]
operations = [
migrations.CreateModel(
name="DeviceLastSeen",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("last_seen", models.DateTimeField(auto_now=True)),
(
"device",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to="pretixbase.device",
),
),
],
),
migrations.AddIndex(
model_name="devicelastseen",
index=pretix.helpers.database.BrinIndexIgnoredOnSQLite(
models.F("last_seen"),
autosummarize=True,
name="pretixbase_device_last_seen",
),
),
]
@@ -1,63 +0,0 @@
# Generated by Django 4.2.17 on 2025-01-01 20:25
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0307_devicelastseen"),
]
operations = [
migrations.CreateModel(
name="CheckoutSession",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("cart_id", models.CharField(max_length=255, unique=True)),
("created", models.DateTimeField(auto_now_add=True)),
("testmode", models.BooleanField(default=False)),
("session_data", models.JSONField(default=dict)),
(
"customer",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="checkout_sessions",
to="pretixbase.customer",
),
),
(
"event",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="checkout_sessions",
to="pretixbase.event",
),
),
(
"sales_channel",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="pretixbase.saleschannel",
),
),
],
),
migrations.AddField(
model_name="invoiceaddress",
name="checkout_session",
field=models.OneToOneField(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="invoice_address",
to="pretixbase.checkoutsession",
),
),
]
@@ -1,33 +0,0 @@
# Generated by Django 5.2.15 on 2026-08-07 20:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0308_checkoutsession_invoiceaddress_checkout_session'),
]
operations = [
migrations.AddField(
model_name='question',
name='container_type',
field=models.CharField(default='P', max_length=5),
),
migrations.AddField(
model_name='questionanswer',
name='checkoutsession',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='pretixbase.checkoutsession'),
),
migrations.AddField(
model_name='questionanswer',
name='order',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='pretixbase.order'),
),
migrations.AlterUniqueTogether(
name='questionanswer',
unique_together={('cartposition', 'question'), ('checkoutsession', 'question'), ('order', 'question'), ('orderposition', 'question')},
),
]
-12
View File
@@ -33,7 +33,6 @@ from django.db.models.aggregates import Sum
from django.db.models.expressions import OuterRef, Subquery
from django.db.models.functions.comparison import Coalesce
from django.utils.crypto import get_random_string, salted_hmac
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes import ScopedManager, scopes_disabled
@@ -410,17 +409,6 @@ class AttendeeProfile(models.Model):
return '\n'.join([str(p).strip() for p in parts if p and str(p).strip()])
@cached_property
def answers_key_to_index(self):
return {a.get('field_name'): i for i, a in enumerate(self.answers)}
def store_answer(self, answer_dict):
k = answer_dict['field_name']
if k in self.answers_key_to_index:
self.answers[self.answers_key_to_index[k]] = answer_dict
else:
self.answers.append(answer_dict)
def generate_client_id():
return get_random_string(40)
-27
View File
@@ -20,13 +20,11 @@
# <https://www.gnu.org/licenses/>.
#
import string
from datetime import timedelta
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Max
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
@@ -34,7 +32,6 @@ from pretix.base.models import LoggedModel
from pretix.base.permissions import (
AnyPermissionOf, assert_valid_event_permission,
)
from pretix.helpers import BrinIndexIgnoredOnSQLite
@scopes_disabled()
@@ -290,27 +287,3 @@ class Device(LoggedModel):
return self.get_events_with_any_permission()
else:
return self.organizer.events.none()
class DeviceLastSeen(models.Model):
# This is a separate model since we expect it to get A LOT of writes and PostgreSQL always
# writes full rows and then needs to update all indexes on the row, so this is going to save a
# lot of write traffic on the databse
device = models.OneToOneField("Device", on_delete=models.CASCADE, related_name="last_seen")
last_seen = models.DateTimeField(auto_now=True)
class Meta:
indexes = [
BrinIndexIgnoredOnSQLite(
# BRIN indexes are highly efficient on lots of updates, especially of chronological data
# and especially if we later want to query them by range, as we likely want to.
"last_seen",
name="pretixbase_device_last_seen",
autosummarize=True
)
]
@property
def is_recent(self):
# pretixSCAN/pretixPOS sync every 5 minutes, so 7 minutes can be considered "offline"
return now() - self.last_seen < timedelta(minutes=7)
+1 -1
View File
@@ -736,7 +736,7 @@ class Event(EventMixin, LoggedModel):
self.settings.mail_send_order_paid_attendee = True
self.settings.mail_send_order_approved_attendee = True
self.settings.mail_send_order_approved_free_attendee = True
self.settings.mail_send_download_reminder_attendee = True
self.settings.mail_text_download_reminder_attendee = True
@property
def social_image(self):
-9
View File
@@ -1606,9 +1606,6 @@ class Question(LoggedModel):
:param dependency_values: The values that `dependency_question` needs to be set to for this question to be applicable.
:type dependency_values: list[str]
"""
class ContainerType(models.TextChoices):
ORDER = "O", _("Order")
ORDERPOSITION = "P", _("Order position")
TYPE_NUMBER = "N"
TYPE_STRING = "S"
TYPE_TEXT = "T"
@@ -1644,12 +1641,6 @@ class Question(LoggedModel):
related_name="questions",
on_delete=models.CASCADE
)
container_type = models.CharField(
max_length=5,
choices=ContainerType.choices,
verbose_name=_("Asked on"),
default=ContainerType.ORDERPOSITION,
)
question = I18nTextField(
verbose_name=_("Question")
)
+13 -83
View File
@@ -1304,9 +1304,10 @@ class Order(LockModel, LoggedModel):
def answerfile_name(instance, filename: str) -> str:
secret = get_random_string(length=32, allowed_chars=string.ascii_letters + string.digits)
event = (instance.cartposition if instance.cartposition else instance.orderposition.order).event
return 'cachedfiles/answers/{org}/{ev}/{secret}.{filename}'.format(
org=instance.event.organizer.slug,
ev=instance.event.slug,
org=event.organizer.slug,
ev=event.slug,
secret=secret,
filename=escape_uri_path(filename),
)
@@ -1335,14 +1336,6 @@ class QuestionAnswer(models.Model):
'CartPosition', null=True, blank=True,
related_name='answers', on_delete=models.CASCADE
)
order = models.ForeignKey(
'Order', null=True, blank=True,
related_name='answers', on_delete=models.CASCADE
)
checkoutsession = models.ForeignKey(
'CheckoutSession', null=True, blank=True,
related_name='answers', on_delete=models.CASCADE
)
question = models.ForeignKey(
Question, related_name='answers', on_delete=models.CASCADE
)
@@ -1358,21 +1351,16 @@ class QuestionAnswer(models.Model):
objects = ScopedManager(organizer='question__event__organizer')
class Meta:
unique_together = [
['orderposition', 'question'],
['cartposition', 'question'],
['order', 'question'],
['checkoutsession', 'question'],
]
unique_together = [['orderposition', 'question'], ['cartposition', 'question']]
@property
def backend_file_url(self):
if self.file:
if self.associated_order:
if self.orderposition:
return reverse('control:event.order.download.answer', kwargs={
'code': self.associated_order.code,
'event': self.associated_order.event.slug,
'organizer': self.associated_order.event.organizer.slug,
'code': self.orderposition.order.code,
'event': self.orderposition.order.event.slug,
'organizer': self.orderposition.order.event.organizer.slug,
'answer': self.pk,
})
return ""
@@ -1382,14 +1370,14 @@ class QuestionAnswer(models.Model):
from pretix.multidomain.urlreverse import eventreverse
if self.file:
if self.associated_order:
url = eventreverse(self.associated_order.event, 'presale:event.order.download.answer', kwargs={
'order': self.associated_order.code,
'secret': self.associated_order.secret,
if self.orderposition:
url = eventreverse(self.orderposition.order.event, 'presale:event.order.download.answer', kwargs={
'order': self.orderposition.order.code,
'secret': self.orderposition.order.secret,
'answer': self.pk,
})
else:
url = eventreverse(self.event, 'presale:event.cart.download.answer', kwargs={
url = eventreverse(self.cartposition.event, 'presale:event.cart.download.answer', kwargs={
'answer': self.pk,
})
@@ -1404,24 +1392,6 @@ class QuestionAnswer(models.Model):
def file_name(self):
return self.file.name.split('.', 1)[-1]
@property
def associated_order(self):
if self.orderposition:
return self.orderposition.order
elif self.order:
return self.order
@property
def event(self):
if self.orderposition:
return self.orderposition.order.event
elif self.cartposition:
return self.cartposition.event
elif self.order:
return self.order.event
elif self.checkoutsession:
return self.checkoutsession.event
def __str__(self):
return self.to_string(use_cached=True)
@@ -3207,39 +3177,6 @@ class Transaction(models.Model):
return self.tax_value_includes_rounding_correction * self.count
class CheckoutSession(models.Model):
"""
A checkout session optionally bundles cart positions with additional information. This is historically
not required in pretix and currently only used in the Storefront API.
"""
event = models.ForeignKey(
Event,
verbose_name=_("Event"),
related_name="checkout_sessions",
on_delete=models.CASCADE,
)
cart_id = models.CharField(
max_length=255, unique=True,
verbose_name=_("Cart ID (e.g. session key)"),
)
created = models.DateTimeField(
verbose_name=_("Date"),
auto_now_add=True,
)
customer = models.ForeignKey(
Customer,
related_name='checkout_sessions',
null=True, blank=True,
on_delete=models.SET_NULL,
)
sales_channel = models.ForeignKey(
"SalesChannel",
on_delete=models.CASCADE,
)
testmode = models.BooleanField(default=False)
session_data = models.JSONField(default=dict)
class CartPosition(AbstractPosition):
"""
A cart position is similar to an order line, except that it is not
@@ -3444,13 +3381,6 @@ class CartPosition(AbstractPosition):
class InvoiceAddress(models.Model):
last_modified = models.DateTimeField(auto_now=True)
checkout_session = models.OneToOneField(
CheckoutSession,
null=True,
blank=True,
related_name='invoice_address',
on_delete=models.CASCADE
)
order = models.OneToOneField(Order, null=True, blank=True, related_name='invoice_address', on_delete=models.CASCADE)
customer = models.ForeignKey(
Customer,
+4 -21
View File
@@ -330,24 +330,9 @@ class BasePaymentProvider:
payment method. This returns ``False`` by default which is no guarantee that
aborting a pending payment can never happen, it just hides the frontend button
to avoid users accidentally committing double payments.
If the decision doesn't depend on the specific payment, then only implementing
``abort_pending_allowed`` is enough, ``payment_abort_pending_allowed(payment: OrderPayment)``
is expected to take this into account.
As a consumer only evaluate ``payment_abort_pending_allowed(payment: OrderPayment)``
to check if aborting this pending payment is possible.
"""
return False
def _payment_abort_pending_allowed(self, payment: OrderPayment) -> bool:
"""
Experimental: This might change during upcomming releases.
Whether or not a user can abort a payment in pending state to switch to another
payment method. This returns ``self.abort_pending_allowed`` by default which is
no guarantee that aborting a pending payment can never happen, it just hides the
frontend button to avoid users accidentally committing double payments.
"""
return self.abort_pending_allowed
@property
def requires_invoice_immediately(self):
"""
@@ -1034,12 +1019,10 @@ class BasePaymentProvider:
On success, you should set ``payment.state = OrderPayment.PAYMENT_STATE_CANCELED`` (or call the super method).
On failure, you should raise a PaymentException.
"""
if payment.state == OrderPayment.PAYMENT_STATE_PENDING:
if not self._payment_abort_pending_allowed(payment):
raise PaymentException(_(
"This payment is already being processed and cannot be canceled any more."
))
if payment.state == OrderPayment.PAYMENT_STATE_PENDING and not self.abort_pending_allowed:
raise PaymentException(_(
"This payment is already being processed and can not be canceled any more."
))
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
payment.save(update_fields=['state'])
+155 -373
View File
@@ -19,245 +19,30 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import copy
import datetime
import os
import warnings
from collections import namedtuple
from dataclasses import dataclass
from typing import (
TYPE_CHECKING, Iterable, List, Literal, Optional, Tuple, Union, cast,
)
from typing import Union
from zoneinfo import ZoneInfo
from dateutil import parser
from django import forms
from django.core.exceptions import ValidationError
from django.db import models
from django.forms.widgets import Widget
from django.utils.formats import get_format
from django.utils.functional import Promise, lazy
from django.utils.functional import lazy
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from pretix.base.forms.widgets import OptionAttrsSelect
BASE_CHOICES = (
('date_from', _('Event start')),
('date_to', _('Event end')),
('date_admission', _('Event admission')),
('presale_start', _('Presale start')),
('presale_end', _('Presale end')),
)
if TYPE_CHECKING:
from .models import Event, Order, SubEvent
@dataclass(frozen=True)
class BaseChoice:
base: Literal["event", "order"]
attribute: str
text: Promise
supports_before: bool
supports_after: bool
@property
def key(self) -> str:
key = f"{self.base}__{self.attribute}"
return key
@staticmethod
def find(objects: Iterable["BaseChoice"], key: str) -> "BaseChoice":
if "__" in key:
choice = next((obj for obj in objects if obj.key == key), None)
else:
# fallback for RelativeDateFields stored, before support for bases other than event was added
choice = next((obj for obj in objects if obj.attribute == key and obj.base == "event"), None)
if choice is None:
raise TypeError(f"key {key} must be a valid key in BASE_CHOICES")
return choice
BASE_CHOICES: List[BaseChoice] = [
BaseChoice('event', 'date_from', _('Event start'), True, True),
BaseChoice('event', 'date_to', _('Event end'), True, True),
BaseChoice('event', 'date_admission', _('Event admission'), True, True),
BaseChoice('event', 'presale_start', _('Presale start'), True, True),
BaseChoice('event', 'presale_end', _('Presale end'), True, True),
BaseChoice('order', 'datetime', _('Order creation'), False, True),
BaseChoice('order', 'expires', _('Order expiry'), True, True),
]
EVENT_BASE_CHOICES = [
x for x in BASE_CHOICES if x.base == 'event'
]
ORDER_BASE_CHOICES = [
x for x in BASE_CHOICES if x.base == 'order'
]
@dataclass(frozen=True)
class RelativeDate:
"""
This contains information on a date that is defined in relation to a fixed base point.
This means that the underlying data is a fixed date as the base point and a number of days or a time interval
to calculate the date.
The list of valid base date choices is defined in BASE_CHOICES.
If the base_date_name is not set, the date_from attribute of Event is used.
"""
days: int = 0
minutes: Optional[int] = None
time: Optional[datetime.time] = None
is_after: bool = False
base_date_name: str = 'event__date_from__'
def __post_init__(self) -> None:
if self.is_after and not self._choice.supports_after:
raise ValueError(
"The selected base date and attribute combination does not support relative dates placed after the base date"
)
if not self.is_after and not self._choice.supports_before:
raise ValueError(
"The selected base date and attribute combination does not support relative dates placed before the base date")
@property
def _choice(self):
return BaseChoice.find(BASE_CHOICES, self.base_date_name)
@property
def key(self):
return self._choice.key
def __eq__(self, o: object) -> bool:
if not isinstance(o, RelativeDate):
return False
return self.to_string() == o.to_string()
def _resolve_date(self, base: "Event | Order | SubEvent") -> Tuple[datetime.datetime, ZoneInfo]:
"""
Resolves the datetime and timezone information of this RelativeDate object in relation to the provided reference.
:param base: the reference which should be used to resolve the relative date
:return:
"""
from .models import Event, Order, SubEvent
choice = self._choice
if choice.base == "order" and isinstance(base, Order):
event = base.event
base_date = getattr(base, choice.attribute)
elif choice.base == "event" and isinstance(base, SubEvent):
event = base.event
base_date = (getattr(base, choice.attribute) or
getattr(base.event, choice.attribute) or
base.date_from)
elif choice.base == "event" and isinstance(base, Event):
event = base
base_date = getattr(base, choice.attribute) or event.date_from
else:
raise TypeError("The base defined by data does not match the passed in base")
tz = ZoneInfo(event.settings.timezone)
return base_date, tz
def date(self, base: "Event | Order | SubEvent") -> datetime.date:
"""
Resolves the effective date of this RelativeDate object in relation to the provided reference.
:param base: the reference which should be used to resolve the date
:return: datetime.date
"""
if self.minutes is not None:
raise ValueError('A minute-based relative datetime can not be used as a date')
base_date, tz = self._resolve_date(base)
if self.is_after:
new_date = base_date.astimezone(tz) + datetime.timedelta(days=self.days)
else:
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.days)
return new_date.date()
def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
"""
Resolves the effective datetime of this RelativeDate object in relation to the provided reference.
:param base: the reference which should be used to resolve the datetime
:return: datetime.datetime
"""
base_date, tz = self._resolve_date(base)
if self.minutes is not None:
if self.is_after:
return base_date.astimezone(tz) + datetime.timedelta(minutes=self.minutes)
else:
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.minutes)
else:
if self.is_after:
new_date = (base_date.astimezone(tz) + datetime.timedelta(days=self.days)).astimezone(tz)
else:
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.days)).astimezone(tz)
if self.time:
new_date = new_date.replace(
hour=self.time.hour,
minute=self.time.minute,
second=self.time.second
)
new_date = new_date.astimezone(tz)
return new_date
def to_string(self) -> str:
if self.minutes is not None:
return 'RELDATE/minutes/{}/{}/{}'.format( #
self.minutes,
self._choice.key,
'after' if self.is_after else '',
)
return 'RELDATE/{}/{}/{}/{}'.format( #
self.days,
self.time.strftime('%H:%M:%S') if self.time else '-',
self._choice.key,
'after' if self.is_after else '',
)
@classmethod
def from_string(cls, input: str):
if not input.startswith('RELDATE/'):
raise TypeError("Invalid input for RelativeDate.from_string()")
parts = input.split('/')
if parts[1] == 'minutes':
data = RelativeDate(
days=0,
minutes=int(parts[2]),
base_date_name=parts[3],
time=None,
is_after=len(parts) > 4 and parts[4] == "after",
)
else:
if parts[2] == '-':
time = None
else:
timeparts = parts[2].split(':')
time = datetime.time(hour=int(timeparts[0]), minute=int(timeparts[1]), second=int(timeparts[2]))
try:
data = RelativeDate(
days=int(parts[1] or 0),
base_date_name=parts[3],
time=time,
minutes=None,
is_after=len(parts) > 4 and parts[4] == "after",
)
except ValueError:
data = RelativeDate(
days=0,
base_date_name=parts[3],
time=time,
minutes=None,
is_after=len(parts) > 4 and parts[4] == "after",
)
return data
RelativeDate = namedtuple('RelativeDate', ['days', 'minutes', 'time', 'is_after', 'base_date_name'], defaults=(0, None, None, False, 'date_from'))
class RelativeDateWrapper:
@@ -265,53 +50,130 @@ class RelativeDateWrapper:
This contains information on a date that might be relative to an event. This means
that the underlying data is either a fixed date or a number of days and a wall clock
time to calculate the date based on a base point.
The base point can be the date_from, date_to, date_admission, presale_start or presale_end
attribute of an event or subevent. If the respective attribute is not set, ``date_from``
will be used.
"""
def __init__(self, data: Union[datetime.datetime, RelativeDate]):
self.data = data
def date(self, base: "Event | Order | SubEvent") -> datetime.date:
"""
If the RelativeDateWrapper wraps a RelativeDate object:
Resolves the effective date of this object in relation to the provided reference.
If the RelativeDateWrapper wraps an absolute date or datetime:
Returns the wrapped absolute date.
def date(self, event) -> datetime.date:
from .models import SubEvent
:param base: the reference which should be used to resolve the date in case it is a relative date.
:return: datetime.date
"""
if isinstance(self.data, datetime.datetime):
return self.data.date()
elif isinstance(self.data, datetime.date):
return self.data
else:
return self.data.date(base)
if self.data.minutes is not None:
raise ValueError('A minute-based relative datetime can not be used as a date')
def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
"""
If the RelativeDateWrapper wraps a RelativeDate object:
Resolves the effective datetime of this object in relation to the provided reference.
If the RelativeDateWrapper wraps an absolute date or datetime:
Returns the wrapped absolute datetime.
tz = ZoneInfo(event.settings.timezone)
if isinstance(event, SubEvent):
base_date = (
getattr(event, self.data.base_date_name)
or getattr(event.event, self.data.base_date_name)
or event.date_from
)
else:
base_date = getattr(event, self.data.base_date_name) or event.date_from
if self.data.is_after:
new_date = base_date.astimezone(tz) + datetime.timedelta(days=self.data.days)
else:
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.data.days)
return new_date.date()
def datetime(self, event) -> datetime.datetime:
from .models import SubEvent
:param base: the reference which should be used to resolve the datetime in case it is a relative datetime.
:return: datetime.datetime
"""
if isinstance(self.data, (datetime.datetime, datetime.date)):
return self.data
else:
return self.data.datetime(base)
tz = ZoneInfo(event.settings.timezone)
if isinstance(event, SubEvent):
base_date = (
getattr(event, self.data.base_date_name)
or getattr(event.event, self.data.base_date_name)
or event.date_from
)
else:
base_date = getattr(event, self.data.base_date_name) or event.date_from
if self.data.minutes is not None:
if self.data.is_after:
return base_date.astimezone(tz) + datetime.timedelta(minutes=self.data.minutes)
else:
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.data.minutes)
else:
if self.data.is_after:
new_date = (base_date.astimezone(tz) + datetime.timedelta(days=self.data.days)).astimezone(tz)
else:
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.data.days)).astimezone(tz)
if self.data.time:
new_date = new_date.replace(
hour=self.data.time.hour,
minute=self.data.time.minute,
second=self.data.time.second
)
new_date = new_date.astimezone(tz)
return new_date
def to_string(self) -> str:
if isinstance(self.data, (datetime.datetime, datetime.date)):
return self.data.isoformat()
else:
return self.data.to_string()
if self.data.minutes is not None:
return 'RELDATE/minutes/{}/{}/{}'.format( #
self.data.minutes,
self.data.base_date_name,
'after' if self.data.is_after else '',
)
return 'RELDATE/{}/{}/{}/{}'.format( #
self.data.days,
self.data.time.strftime('%H:%M:%S') if self.data.time else '-',
self.data.base_date_name,
'after' if self.data.is_after else '',
)
@classmethod
def from_string(cls, input: str):
if input.startswith('RELDATE/'):
data = RelativeDate.from_string(input)
parts = input.split('/')
if parts[1] == 'minutes':
data = RelativeDate(
days=0,
minutes=int(parts[2]),
base_date_name=parts[3],
time=None,
is_after=len(parts) > 4 and parts[4] == "after",
)
else:
if parts[2] == '-':
time = None
else:
timeparts = parts[2].split(':')
time = datetime.time(hour=int(timeparts[0]), minute=int(timeparts[1]), second=int(timeparts[2]))
try:
data = RelativeDate(
days=int(parts[1] or 0),
base_date_name=parts[3],
time=time,
minutes=None,
is_after=len(parts) > 4 and parts[4] == "after",
)
except ValueError:
data = RelativeDate(
days=0,
base_date_name=parts[3],
time=time,
minutes=None,
is_after=len(parts) > 4 and parts[4] == "after",
)
if data.base_date_name not in [k[0] for k in BASE_CHOICES]:
raise ValueError('{} is not a valid base date'.format(data.base_date_name))
else:
data = parser.parse(input)
return RelativeDateWrapper(data)
@@ -325,6 +187,7 @@ BEFORE_AFTER_CHOICE = (
('after', _('after')),
)
reldatetimeparts = namedtuple('reldatetimeparts', (
"status", # 0
"absolute", # 1
@@ -339,14 +202,6 @@ reldatetimeparts = namedtuple('reldatetimeparts', (
reldatetimeparts.indizes = reldatetimeparts(*range(9))
def _get_choices(base_choices: List[BaseChoice]) -> List[Tuple[str, Promise]]:
return [(c.key, c.text) for c in base_choices]
def _get_choice_validation_obj(choices: List[BaseChoice]):
return {c.key: {"data-supports-before": c.supports_before, "data-supports-after": c.supports_after} for c in choices}
class RelativeDateTimeWidget(forms.MultiWidget):
template_name = 'pretixbase/forms/widgets/reldatetime.html'
parts = reldatetimeparts
@@ -354,7 +209,6 @@ class RelativeDateTimeWidget(forms.MultiWidget):
def __init__(self, *args, **kwargs):
self.status_choices = kwargs.pop('status_choices')
base_choices = kwargs.pop('base_choices')
choices = _get_choices(base_choices)
def placeholder_datetime_format():
df = get_format('DATETIME_INPUT_FORMATS')[0]
@@ -366,29 +220,21 @@ class RelativeDateTimeWidget(forms.MultiWidget):
tf = get_format('TIME_INPUT_FORMATS')[0]
return datetime.time(8, 30, 0).strftime(tf)
widgets = cast(dict[str, Widget | type[Widget]], cast(object, reldatetimeparts(
widgets = reldatetimeparts(
status=forms.RadioSelect(choices=self.status_choices),
absolute=forms.DateTimeInput(
attrs={'placeholder': lazy(placeholder_datetime_format, str), 'class': 'datetimepicker'}
),
rel_days_number=forms.NumberInput(),
rel_mins_relationto=OptionAttrsSelect(
attrs={'data-relative-choice': True},
choices=choices,
option_attrs=_get_choice_validation_obj(base_choices)
),
rel_mins_relationto=forms.Select(choices=base_choices),
rel_days_timeofday=forms.TimeInput(
attrs={'placeholder': lazy(placeholder_time_format, str), 'class': 'timepickerfield'}
),
rel_mins_number=forms.NumberInput(),
rel_days_relationto=OptionAttrsSelect(
attrs={'data-relative-choice': True},
choices=choices,
option_attrs=_get_choice_validation_obj(base_choices)
),
rel_mins_relation=forms.Select(attrs={'data-relation-choice': True}, choices=BEFORE_AFTER_CHOICE),
rel_days_relation=forms.Select(attrs={'data-relation-choice': True}, choices=BEFORE_AFTER_CHOICE),
)))
rel_days_relationto=forms.Select(choices=base_choices),
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
)
super().__init__(widgets=widgets, *args, **kwargs)
def decompress(self, value):
@@ -401,10 +247,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="unset",
absolute=None,
rel_days_number=1,
rel_mins_relationto="event__date_from",
rel_mins_relationto="date_from",
rel_days_timeofday=None,
rel_mins_number=0,
rel_days_relationto="event__date_from",
rel_days_relationto="date_from",
rel_mins_relation="before",
rel_days_relation="before"
)
@@ -413,10 +259,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="absolute",
absolute=value.data,
rel_days_number=1,
rel_mins_relationto="event__date_from",
rel_mins_relationto="date_from",
rel_days_timeofday=None,
rel_mins_number=0,
rel_days_relationto="event__date_from",
rel_days_relationto="date_from",
rel_mins_relation="before",
rel_days_relation="before"
)
@@ -425,10 +271,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="relative_minutes",
absolute=None,
rel_days_number=None,
rel_mins_relationto=value.data.key,
rel_mins_relationto=value.data.base_date_name,
rel_days_timeofday=None,
rel_mins_number=value.data.minutes,
rel_days_relationto=value.data.key,
rel_days_relationto=value.data.base_date_name,
rel_mins_relation="after" if value.data.is_after else "before",
rel_days_relation="after" if value.data.is_after else "before"
)
@@ -436,10 +282,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
status="relative",
absolute=None,
rel_days_number=value.data.days,
rel_mins_relationto=value.data.key,
rel_mins_relationto=value.data.base_date_name,
rel_days_timeofday=value.data.time,
rel_mins_number=0,
rel_days_relationto=value.data.key,
rel_days_relationto=value.data.base_date_name,
rel_mins_relation="after" if value.data.is_after else "before",
rel_days_relation="after" if value.data.is_after else "before"
)
@@ -463,41 +309,17 @@ class RelativeDateTimeField(forms.MultiValueField):
('relative', _('Relative date:')),
('relative_minutes', _('Relative time:')),
]
self.relative_to_order = kwargs.pop('relative_to_order', False)
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
if self.relative_to_order:
possible_choices.extend(ORDER_BASE_CHOICES)
if kwargs.get('limit_choices'):
limit = kwargs.pop('limit_choices')
if any(["__" not in l for l in limit]):
_warn_skips = (str(os.path.dirname(__file__)),)
warnings.warn(
message="Please prefix limit_choices with the base the attributes refer to, for example event__date_from",
category=DeprecationWarning,
skip_file_prefixes=_warn_skips
)
possible_choices = [
c for c in possible_choices if
# new base case as we want limit_choices to be expressed as base__attribute
(c.key in limit) or
# fallback for old event based entries
# if the base is an event, then using only attribute is fine
(c.base == "event" and c.attribute in limit)
]
choices = [(k, v) for k, v in BASE_CHOICES if k in limit]
else:
choices = BASE_CHOICES
if not kwargs.get('required', True):
status_choices.insert(0, ('unset', _('Not set')))
choices = _get_choices(possible_choices)
fields = reldatetimeparts(
status=forms.ChoiceField(
choices=status_choices,
required=True,
required=True
),
absolute=forms.DateTimeField(
required=False
@@ -507,7 +329,7 @@ class RelativeDateTimeField(forms.MultiValueField):
),
rel_mins_relationto=forms.ChoiceField(
choices=choices,
required=False,
required=False
),
rel_days_timeofday=forms.TimeField(
required=False,
@@ -517,7 +339,7 @@ class RelativeDateTimeField(forms.MultiValueField):
),
rel_days_relationto=forms.ChoiceField(
choices=choices,
required=False,
required=False
),
rel_mins_relation=forms.ChoiceField(
choices=BEFORE_AFTER_CHOICE,
@@ -528,9 +350,8 @@ class RelativeDateTimeField(forms.MultiValueField):
required=False
),
)
if 'widget' not in kwargs:
kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=possible_choices)
kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=choices)
kwargs.pop('max_length', 0)
kwargs.pop('empty_value', 0)
super().__init__(
@@ -538,24 +359,21 @@ class RelativeDateTimeField(forms.MultiValueField):
)
def set_event(self, event):
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
if self.relative_to_order:
possible_choices.extend(ORDER_BASE_CHOICES)
possible_choices = possible_choices
choices = _get_choices(possible_choices)
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = choices
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = choices
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
]
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
]
def compress(self, data_list):
if not data_list:
return None
data = reldatetimeparts(*data_list)
if data.status == 'unset':
return None
elif data.status == 'absolute':
if data.status == 'absolute':
return RelativeDateWrapper(data.absolute)
elif data.status == 'unset':
return None
elif data.status == 'relative_minutes':
return RelativeDateWrapper(RelativeDate(
days=0,
@@ -586,18 +404,6 @@ class RelativeDateTimeField(forms.MultiValueField):
raise ValidationError(self.error_messages['incomplete'])
elif data.status == 'relative_minutes' and (data.rel_mins_number is None or not data.rel_mins_relationto):
raise ValidationError(self.error_messages['incomplete'])
elif data.status == 'relative':
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
if data.rel_days_relation == "before" and not choice.supports_before:
raise ValidationError(_('A relative date cannot be expressed as "before" for "{}"'.format(choice.text)))
elif data.status == 'relative' and data.rel_days_relation == "after" and not choice.supports_after:
raise ValidationError(_('A relative date cannot be expressed as "after" for "{}"'.format(choice.text)))
elif data.status == 'relative_minutes':
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
if data.rel_days_relation == "before" and not choice.supports_before:
raise ValidationError(_('A relative time cannot be expressed as "before" for "{}"'.format(choice.text)))
elif data.rel_days_relation == "after" and not choice.supports_after:
raise ValidationError(_('A relative time cannot be expressed as "after" for "{}"'.format(choice.text)))
return super().clean(value)
@@ -618,20 +424,15 @@ class RelativeDateWidget(RelativeDateTimeWidget):
def __init__(self, *args, **kwargs):
self.status_choices = kwargs.pop('status_choices')
self.base_choices = kwargs.pop('base_choices')
widgets = cast(dict[str, Widget | type[Widget]], cast(object, reldateparts(
widgets = reldateparts(
status=forms.RadioSelect(choices=self.status_choices),
absolute=forms.DateInput(
attrs={'class': 'datepickerfield'}
),
rel_days_number=forms.NumberInput(),
rel_days_relationto=OptionAttrsSelect(
choices=self.base_choices,
option_attrs=_get_choice_validation_obj(self.base_choices),
attrs={'data-relative-choice': True},
),
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE, attrs={'data-relation-choice': True},),
)))
rel_days_relationto=forms.Select(choices=kwargs.pop('base_choices')),
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
)
forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
def decompress(self, value):
@@ -659,7 +460,7 @@ class RelativeDateWidget(RelativeDateTimeWidget):
status="relative",
absolute=None,
rel_days_number=value.data.days,
rel_days_relationto=value.data.key,
rel_days_relationto=value.data.base_date_name,
rel_days_relation="after" if value.data.is_after else "before"
)
@@ -673,15 +474,6 @@ class RelativeDateField(RelativeDateTimeField):
]
if not kwargs.get('required', True):
status_choices.insert(0, ('unset', _('Not set')))
self.relative_to_order = kwargs.pop('relative_to_order', False)
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
if self.relative_to_order:
possible_choices.extend(ORDER_BASE_CHOICES)
choices = _get_choices(possible_choices)
fields = reldateparts(
status=forms.ChoiceField(
choices=status_choices,
@@ -694,37 +486,33 @@ class RelativeDateField(RelativeDateTimeField):
required=False
),
rel_days_relationto=forms.ChoiceField(
choices=choices,
required=False,
choices=BASE_CHOICES,
required=False
),
rel_days_relation=forms.ChoiceField(
choices=BEFORE_AFTER_CHOICE,
required=False
),
)
if 'widget' not in kwargs:
kwargs['widget'] = RelativeDateWidget(status_choices=status_choices, base_choices=possible_choices)
kwargs['widget'] = RelativeDateWidget(status_choices=status_choices, base_choices=BASE_CHOICES)
forms.MultiValueField.__init__(
self, fields=fields, require_all_fields=False, *args, **kwargs
)
def set_event(self, event):
choices = [
(c.key, c.text) for c in EVENT_BASE_CHOICES if getattr(event, c.attribute, None)
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = [
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
]
if self.relative_to_order:
choices += [(c.key, c.text) for c in ORDER_BASE_CHOICES]
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = choices
def compress(self, data_list):
if not data_list:
return None
data = reldateparts(*data_list)
if data.status == 'unset':
return None
elif data.status == 'absolute':
if data.status == 'absolute':
return RelativeDateWrapper(data.absolute)
elif data.status == 'unset':
return None
else:
return RelativeDateWrapper(RelativeDate(
days=data.rel_days_number,
@@ -737,14 +525,8 @@ class RelativeDateField(RelativeDateTimeField):
data = reldateparts(*value)
if data.status == 'absolute' and not data.absolute:
raise ValidationError(self.error_messages['incomplete'])
if data.status == 'relative':
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
if data.rel_days_number is None or not data.rel_days_relationto:
raise ValidationError(self.error_messages['incomplete'])
elif data.rel_days_relation == "before" and not choice.supports_before:
raise ValidationError(_("A relative date cannot be expressed as 'before' for '{}'".format(choice.text)))
elif data.rel_days_relation == "after" and not choice.supports_after:
raise ValidationError(_("A relative date cannot be expressed as 'after' for '{}'".format(choice.text)))
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
raise ValidationError(self.error_messages['incomplete'])
return forms.MultiValueField.clean(self, value)
+1 -17
View File
@@ -53,7 +53,6 @@ from django.utils.translation import (
)
from django_scopes import scopes_disabled
from pretix.base.decimal import round_decimal
from pretix.base.i18n import language
from pretix.base.media import MEDIA_TYPES
from pretix.base.models import (
@@ -61,7 +60,7 @@ from pretix.base.models import (
Seat, SeatCategoryMapping, Voucher,
)
from pretix.base.models.event import SubEvent
from pretix.base.models.orders import CheckoutSession, OrderFee
from pretix.base.models.orders import OrderFee
from pretix.base.models.tax import TaxRule
from pretix.base.reldate import RelativeDateWrapper
from pretix.base.services.checkin import _save_answers
@@ -472,16 +471,6 @@ class CartManager:
if term_last < time_machine_now(self.real_now_dt):
raise CartError(error_messages['payment_ended'])
def _ensure_checkout_session(self):
CheckoutSession.objects.get_or_create(
event=self.event,
cart_id=self.cart_id,
defaults={
"sales_channel": self._sales_channel,
"testmode": self.event.testmode,
},
)
def _extend_expiry_of_valid_existing_positions(self):
# real_now_dt is initialized at CartManager instantiation, so it's slightly in the past. Add a small
# delta to reduce risk of extending already expired CartPositions.
@@ -927,8 +916,6 @@ class CartManager:
if custom_price > 99_999_999_999:
raise CartError(error_messages['price_too_high'])
custom_price = round_decimal(custom_price, currency=self.event.currency)
op = self.AddOperation(
count=i['count'],
item=item,
@@ -1051,8 +1038,6 @@ class CartManager:
if custom_price > 99_999_999_999:
raise CartError(error_messages['price_too_high'])
custom_price = round_decimal(custom_price, currency=self.event.currency)
# Fix positions with wrong price (TODO: happens out-of-cartmanager-transaction and therefore a little hacky)
for ca in current_addons[cp][a['item'], a['variation']]:
if ca.listed_price != listed_price:
@@ -1569,7 +1554,6 @@ class CartManager:
def commit(self):
self._check_presale_dates()
self._ensure_checkout_session()
self._check_max_cart_size()
err = self._delete_out_of_timeframe()
+3 -5
View File
@@ -40,7 +40,7 @@ import dateutil
import dateutil.parser
from dateutil.tz import datetime_exists
from django.core.files import File
from django.db import IntegrityError
from django.db import IntegrityError, transaction
from django.db.models import (
BooleanField, Case, Count, ExpressionWrapper, F, IntegerField, Max, Min,
OuterRef, Q, Subquery, TextField, Value, When,
@@ -59,7 +59,6 @@ from pretix.base.models import (
)
from pretix.base.signals import checkin_created, periodic_task
from pretix.helpers import OF_SELF
from pretix.helpers.database import conditional_atomic
from pretix.helpers.jsonlogic import Logic
from pretix.helpers.jsonlogic_boolalg import convert_to_dnf
from pretix.helpers.jsonlogic_query import (
@@ -892,7 +891,6 @@ def _save_answers(op, answers, given_answers):
qa.answer = answer
qa.save(update_fields=['answer'])
qa.options.clear()
return qa
written = False
for q, a in given_answers.items():
@@ -1045,10 +1043,10 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
if not simulate:
_save_answers(op, answers, given_answers)
with conditional_atomic(not simulate):
with transaction.atomic():
# Lock order positions, if it is an entry. We don't need it for exits, as a race condition wouldn't be problematic
opqs = OrderPosition.all.select_related("order", "item")
if type != Checkin.TYPE_EXIT and not simulate:
if type != Checkin.TYPE_EXIT:
opqs = opqs.select_for_update(of=OF_SELF)
op = opqs.get(pk=op.pk)
-5
View File
@@ -33,7 +33,6 @@ from pretix.base.models.customers import CustomerSSOGrant
from ..models import CachedFile, CartPosition, InvoiceAddress
from ..models.auth import UserKnownLoginSource
from ..models.orders import CheckoutSession
from ..signals import periodic_task
@@ -44,10 +43,6 @@ def clean_cart_positions(sender, **kwargs):
cp.delete()
for cp in CartPosition.objects.filter(expires__lt=now() - timedelta(days=14), addon_to__isnull=True):
cp.delete()
for cs in CheckoutSession.objects.filter(created__lt=now() - timedelta(days=14)).exclude(
Exists(CartPosition.objects.filter(cart_id=OuterRef("cart_id")))
):
cs.delete()
for ia in InvoiceAddress.objects.filter(order__isnull=True, customer__isnull=True, last_modified__lt=now() - timedelta(days=14)):
ia.delete()
+77 -108
View File
@@ -48,12 +48,12 @@ from celery.exceptions import MaxRetriesExceededError
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.db import transaction
from django.db.models import (
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, QuerySet, Subquery,
Sum, Value,
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, QuerySet, Sum,
Value,
)
from django.db.models.functions import Cast, Greatest
from django.db.models.functions import Coalesce, Greatest
from django.db.transaction import get_connection
from django.dispatch import receiver
from django.utils.functional import cached_property
@@ -71,10 +71,10 @@ from pretix.base.models import (
Membership, Order, OrderPayment, OrderPosition, Quota, Seat,
SeatCategoryMapping, User, Voucher,
)
from pretix.base.models.event import Event_SettingsStore, SubEvent
from pretix.base.models.event import SubEvent
from pretix.base.models.orders import (
BlockedTicketSecret, CheckoutSession, InvoiceAddress, OrderFee,
OrderRefund, generate_secret,
BlockedTicketSecret, InvoiceAddress, OrderFee, OrderRefund,
generate_secret,
)
from pretix.base.models.organizer import SalesChannel, TeamAPIToken
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
@@ -1030,8 +1030,7 @@ def _apply_rounding_and_fees(positions: List[CartPosition], payment_requests: Li
def _create_order(event: Event, *, email: str, positions: List[CartPosition], now_dt: datetime,
payment_requests: List[dict], sales_channel: SalesChannel, locale: str=None,
address: InvoiceAddress=None, meta_info: dict=None, shown_total=None,
customer=None, valid_if_pending=False, api_meta: dict=None, tax_rounding_mode=None,
cart_id: str=None):
customer=None, valid_if_pending=False, api_meta: dict=None, tax_rounding_mode=None):
payments = []
try:
@@ -1114,14 +1113,6 @@ def _create_order(event: Event, *, email: str, positions: List[CartPosition], no
if meta_info:
for msg in meta_info.get('confirm_messages', []):
order.log_action('pretix.event.order.consent', data={'msg': msg})
if cart_id:
try:
session = CheckoutSession.objects.get(event=event, cart_id=cart_id)
except CheckoutSession.DoesNotExist:
pass
else:
session.answers.update(order=order, checkoutsession=None)
session.delete()
order_placed.send(event, order=order, bulk=False)
return order, payments
@@ -1169,7 +1160,7 @@ def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosi
def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str],
email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web',
shown_total=None, customer=None, api_meta: dict=None, tax_rounding_mode=None, cart_id: str=None):
shown_total=None, customer=None, api_meta: dict=None, tax_rounding_mode=None):
for p in payment_requests:
p['pprov'] = event.get_payment_providers(cached=True)[p['provider']]
if not p['pprov']:
@@ -1276,7 +1267,6 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
valid_if_pending=valid_if_pending,
api_meta=api_meta,
tax_rounding_mode=tax_rounding_mode,
cart_id=cart_id,
)
try:
@@ -1504,104 +1494,83 @@ def send_expiry_warnings(sender, **kwargs):
@scopes_disabled()
def send_download_reminders(sender, **kwargs):
today = now().replace(hour=0, minute=0, second=0, microsecond=0)
events = Event.objects.filter(
Q(has_subevents=False, date_from__gte=now()) |
(Q(has_subevents=True) & Q(Exists(
SubEvent.objects.filter(event_id=OuterRef('id'), date_from__gte=now())
)))
).annotate(
reminder_days=Subquery(
Event_SettingsStore.objects.filter(
object=OuterRef('id'),
key='mail_days_download_reminder'
).exclude(
value="None"
).annotate(
val=Cast(F("value"), output_field=models.IntegerField()),
).values("val")
qs = Order.objects.annotate(
first_date=Coalesce(
Min('all_positions__subevent__date_from'),
F('event__date_from')
)
).filter(
reminder_days__isnull=False,
).order_by()
download_reminder_sent=False,
datetime__lte=now() - timedelta(hours=2),
first_date__gte=today,
).only(
'pk', 'event_id', 'sales_channel', 'datetime',
).order_by('event_id')
event_id = None
days = None
event = None
for event in events.iterator(chunk_size=10_000):
qs = event.orders.filter(
download_reminder_sent=False,
datetime__lte=now() - timedelta(hours=2),
)
for o in qs:
if o.event_id != event_id:
days = o.event.settings.get('mail_days_download_reminder', as_type=int)
event = o.event
event_id = o.event_id
if event.has_subevents:
qs = qs.annotate(
first_date=Min('all_positions__subevent__date_from')
).filter(
Q(first_date__gte=today)
)
else:
event_reminder_date = (event.date_from - timedelta(days=event.reminder_days)).replace(hour=0, minute=0, second=0, microsecond=0)
if now() < event_reminder_date:
if days is None:
continue
if o.sales_channel.identifier not in event.settings.mail_sales_channel_download_reminder:
continue
reminder_date = (o.first_date - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
if now() < reminder_date or o.datetime > reminder_date:
continue
with transaction.atomic():
o = Order.objects.select_for_update(of=OF_SELF).get(pk=o.pk)
if o.download_reminder_sent:
# Race condition
continue
positions = list(o.positions_with_tickets)
if not positions:
continue
qs = qs.only(
'pk', 'event_id', 'sales_channel', 'datetime',
).order_by()
for o in qs:
if o.sales_channel.identifier not in event.settings.mail_sales_channel_download_reminder:
if not o.ticket_download_available:
continue
if event.has_subevents:
reminder_date = ((o.first_date or event.date_from) - timedelta(days=event.reminder_days)).replace(hour=0, minute=0, second=0, microsecond=0)
else:
reminder_date = event_reminder_date
if now() < reminder_date or o.datetime > reminder_date:
continue
with transaction.atomic():
o = Order.objects.select_for_update(of=OF_SELF).get(pk=o.pk)
if o.download_reminder_sent:
# Race condition
continue
positions = list(o.positions_with_tickets)
if not positions:
if o.status != Order.STATUS_PAID:
if o.status != Order.STATUS_PENDING or o.require_approval or (not o.valid_if_pending and not o.event.settings.ticket_download_pending):
continue
if not o.ticket_download_available:
continue
with language(o.locale, o.event.settings.region):
o.download_reminder_sent = True
o.save(update_fields=['download_reminder_sent'])
email_template = event.settings.mail_text_download_reminder
email_subject = event.settings.mail_subject_download_reminder
email_context = get_email_context(event=event, order=o)
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True
)
if o.status != Order.STATUS_PAID:
if o.status != Order.STATUS_PENDING or o.require_approval or (not o.valid_if_pending and not o.event.settings.ticket_download_pending):
continue
with language(o.locale, o.event.settings.region):
o.download_reminder_sent = True
o.save(update_fields=['download_reminder_sent'])
email_template = event.settings.mail_text_download_reminder
email_subject = event.settings.mail_subject_download_reminder
email_context = get_email_context(event=event, order=o)
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True
)
if event.settings.mail_send_download_reminder_attendee:
for p in positions:
if p.subevent_id:
reminder_date = (p.subevent.date_from - timedelta(days=event.reminder_days)).replace(
hour=0, minute=0, second=0, microsecond=0
)
if now() < reminder_date:
continue
if p.addon_to_id is None and p.attendee_email and p.attendee_email != o.email:
email_template = event.settings.mail_text_download_reminder_attendee
email_subject = event.settings.mail_subject_download_reminder_attendee
email_context = get_email_context(event=event, order=o, position=p)
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True, position=p
)
if event.settings.mail_send_download_reminder_attendee:
for p in positions:
if p.subevent_id:
reminder_date = (p.subevent.date_from - timedelta(days=days)).replace(
hour=0, minute=0, second=0, microsecond=0
)
if now() < reminder_date:
continue
if p.addon_to_id is None and p.attendee_email and p.attendee_email != o.email:
email_template = event.settings.mail_text_download_reminder_attendee
email_subject = event.settings.mail_subject_download_reminder_attendee
email_context = get_email_context(event=event, order=o, position=p)
o.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.download_reminder_sent',
attach_tickets=True, position=p
)
def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
@@ -3200,12 +3169,12 @@ class OrderChangeManager:
def perform_order(self, event: Event, payments: List[dict], positions: List[str],
email: str=None, locale: str=None, address: int=None, meta_info: dict=None,
sales_channel: str='web', shown_total=None, customer=None, override_now_dt: datetime=None,
api_meta: dict=None, cart_id: str=None):
api_meta: dict=None):
with language(locale), time_machine_now_assigned(override_now_dt):
try:
try:
return _perform_order(event, payments, positions, email, locale, address, meta_info,
sales_channel, shown_total, customer, api_meta, cart_id=cart_id)
sales_channel, shown_total, customer, api_meta)
except LockTimeoutException:
self.retry()
except (MaxRetriesExceededError, LockTimeoutException):
+3 -11
View File
@@ -801,10 +801,11 @@ def get_available_placeholders(event, base_parameters, rich=False):
return params
def prepare_sample_context_for_preview(placeholder_to_sample):
def get_sample_context(event, context_parameters, rich=True):
context_dict = {}
lbl = _('This value will be replaced based on dynamic parameters.')
for k, sample in placeholder_to_sample.items():
for k, v in get_available_placeholders(event, context_parameters, rich=rich).items():
sample = v.render_sample(event)
if isinstance(sample, PlainHtmlAlternativeString):
context_dict[k] = PlainHtmlAlternativeString(
'<{el} class="placeholder" title="{title}">{plain}</{el}>'.format(
@@ -829,12 +830,3 @@ def prepare_sample_context_for_preview(placeholder_to_sample):
escape(sample)
))
return context_dict
def get_sample_context(event, context_parameters, rich=True):
return prepare_sample_context_for_preview(
{
k: v.render_sample(event)
for k, v in get_available_placeholders(event, context_parameters, rich=rich).items()
}
)
+1 -1
View File
@@ -193,7 +193,7 @@ class QuotaAvailability:
lock_name = '_'.join([str(p) for p in sorted([q.pk for q in quotas])])
if rc.exists(f'quotas:availabilitycachewrite:{lock_name}{self._cache_key_suffix}'):
return
rc.set(f'quotas:availabilitycachewrite:{lock_name}{self._cache_key_suffix}', '1', ex=10)
rc.setex(f'quotas:availabilitycachewrite:{lock_name}{self._cache_key_suffix}', '1', 10)
update = defaultdict(list)
for q in quotas:
+1 -1
View File
@@ -2930,7 +2930,7 @@ Your {event} team""")) # noqa: W291
},
'mail_days_download_reminder': {
'type': int,
'default': None # when this default is changed, send_download_reminders needs to change
'default': None
},
'mail_send_download_reminder_attendee': {
'type': bool,
+1 -1
View File
@@ -44,7 +44,7 @@ def timeline_for_event(event, subevent=None):
ev = subevent or event
if subevent:
ev_edit_url = reverse(
'control:event.subevent', kwargs={
'control:event.subevent.edit', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
'subevent': subevent.pk
+72 -112
View File
@@ -32,21 +32,18 @@ from django.utils.functional import cached_property
from django.utils.timezone import make_aware
from pretix.base.forms.questions import (
BaseInvoiceAddressForm, BaseInvoiceNameForm, OrderLevelQuestionsForm,
TicketLevelQuestionsForm,
BaseInvoiceAddressForm, BaseInvoiceNameForm, BaseQuestionsForm,
)
from pretix.base.models import (
CartPosition, InvoiceAddress, OrderPosition, Question, QuestionAnswer,
QuestionOption,
)
from pretix.base.models.customers import AttendeeProfile
from pretix.base.models.orders import CheckoutSession, Order
from pretix.presale.signals import contact_form_fields_overrides
class BaseQuestionsViewMixin:
order_form_class = OrderLevelQuestionsForm
orderposition_form_class = TicketLevelQuestionsForm
form_class = BaseQuestionsForm
all_optional = False
@cached_property
@@ -59,28 +56,6 @@ class BaseQuestionsViewMixin:
def question_form_kwargs(self, cr):
return {}
@property
def order_question_container(self):
raise NotImplementedError()
@cached_property
def order_questions_form(self):
container = self.order_question_container
if container is None:
return None
kwargs = {} # self.question_form_kwargs(cr)
form = self.order_form_class(
event=self.request.event,
prefix='order',
request=self.request,
container=container,
all_optional=self.all_optional,
data=(self.request.POST if self.request.method == 'POST' else None),
files=(self.request.FILES if self.request.method == 'POST' else None),
**kwargs
)
return form
@cached_property
def forms(self):
"""
@@ -94,17 +69,15 @@ class BaseQuestionsViewMixin:
orderpos = cr if isinstance(cr, OrderPosition) else None
kwargs = self.question_form_kwargs(cr)
form = self.orderposition_form_class(
event=self.request.event,
prefix=cr.id,
request=self.request,
cartpos=cartpos,
orderpos=orderpos,
all_optional=self.all_optional,
data=(self.request.POST if self.request.method == 'POST' else None),
files=(self.request.FILES if self.request.method == 'POST' else None),
**kwargs
)
form = self.form_class(event=self.request.event,
prefix=cr.id,
request=self.request,
cartpos=cartpos,
orderpos=orderpos,
all_optional=self.all_optional,
data=(self.request.POST if self.request.method == 'POST' else None),
files=(self.request.FILES if self.request.method == 'POST' else None),
**kwargs)
form.pos = cartpos or orderpos
form.show_copy_answers_to_addon_button = form.pos.addon_to and (
set(form.pos.addon_to.item.questions.all()) & set(form.pos.item.questions.all()) or
@@ -157,38 +130,8 @@ class BaseQuestionsViewMixin:
def save(self):
failed = False
if self.order_questions_form:
if not self.order_questions_form.is_valid():
failed = True
else:
checkoutsession = self.order_question_container if isinstance(self.order_question_container, CheckoutSession) else None
order = self.order_question_container if isinstance(self.order_question_container, Order) else None
for k, v in self.order_questions_form.cleaned_data.items():
if k.startswith('question_'):
field = self.order_questions_form.fields[k]
if hasattr(field, 'answer'):
# We already have a cached answer object, so we don't
# have to create a new one
if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
or (isinstance(v, QuerySet) and not v.exists()):
if field.answer.file:
field.answer.file.delete()
field.answer.delete()
else:
self._save_to_answer(field, field.answer, v)
field.answer.save()
elif v != '' and v is not None:
self._upsert_answer(
field, v,
checkoutsession=checkoutsession,
order=order,
question=field.question,
)
for form in self.forms:
meta_info = form.pos.meta_info_data
cartposition = form.pos if isinstance(form.pos, CartPosition) else None
orderposition = form.pos if isinstance(form.pos, OrderPosition) else None
# Every form represents a CartPosition or OrderPosition with questions attached
if not form.is_valid():
failed = True
@@ -197,8 +140,10 @@ class BaseQuestionsViewMixin:
prof = AttendeeProfile.objects.filter(
customer=self.cart_customer, pk=form.cleaned_data.get('saved_id')
).first() or AttendeeProfile(customer=getattr(self, 'cart_customer', None))
answers_key_to_index = {a.get('field_name'): i for i, a in enumerate(prof.answers)}
else:
prof = AttendeeProfile(customer=getattr(self, 'cart_customer', None))
answers_key_to_index = {}
# This form was correctly filled, so we store the data as
# answers to the questions / in the CartPosition object
@@ -236,19 +181,64 @@ class BaseQuestionsViewMixin:
else:
self._save_to_answer(field, field.answer, v)
field.answer.save()
answer_dict = self._build_answer_dict(field, field.answer, k)
prof.store_answer(answer_dict)
if isinstance(field, forms.ModelMultipleChoiceField) or isinstance(field, forms.ModelChoiceField):
answer_value = {o.identifier: str(o) for o in field.answer.options.all()}
elif isinstance(field, forms.BooleanField):
answer_value = bool(field.answer.answer)
else:
answer_value = str(field.answer.answer)
answer_dict = {
'field_name': k,
'field_label': str(field.label),
'value': answer_value,
'question_type': field.question.type,
'question_identifier': field.question.identifier,
}
if k in answers_key_to_index:
prof.answers[answers_key_to_index[k]] = answer_dict
else:
prof.answers.append(answer_dict)
elif v != '' and v is not None:
answer = self._upsert_answer(
field, v,
cartposition=cartposition,
orderposition=orderposition,
answer = QuestionAnswer(
cartposition=(form.pos if isinstance(form.pos, CartPosition) else None),
orderposition=(form.pos if isinstance(form.pos, OrderPosition) else None),
question=field.question,
)
try:
self._save_to_answer(field, answer, v)
answer.save()
except IntegrityError:
# Since we prefill ``field.answer`` at form creation time, there's a possible race condition
# here if the users submits their save request a second time while the first one is still running,
# thus leading to duplicate QuestionAnswer objects. Since Django doesn't support UPSERT, the "proper"
# fix would be a transaction with select_for_update(), or at least fetching using get_or_create here
# again. However, both of these approaches have a significant performance overhead for *all* requests,
# while the issue happens very very rarely. So we opt for just catching the error and retrying properly.
answer = QuestionAnswer.objects.get(
cartposition=(form.pos if isinstance(form.pos, CartPosition) else None),
orderposition=(form.pos if isinstance(form.pos, OrderPosition) else None),
question=field.question,
)
self._save_to_answer(field, answer, v)
answer.save()
answer_dict = self._build_answer_dict(field, answer, k)
prof.store_answer(answer_dict)
if isinstance(field, forms.ModelMultipleChoiceField) or isinstance(field, forms.ModelChoiceField):
answer_value = {o.identifier: str(o) for o in answer.options.all()}
elif isinstance(field, forms.BooleanField):
answer_value = bool(answer.answer)
else:
answer_value = str(answer.answer)
answer_dict = {
'field_name': k,
'field_label': str(field.label),
'value': answer_value,
'question_type': field.question.type,
'question_identifier': field.question.identifier,
}
if k in answers_key_to_index:
prof.answers[answers_key_to_index[k]] = answer_dict
else:
prof.answers.append(answer_dict)
else:
field = form.fields[k]
@@ -267,7 +257,10 @@ class BaseQuestionsViewMixin:
'question_type': None,
'question_identifier': None,
}
prof.store_answer(answer_dict)
if k in answers_key_to_index:
prof.answers[answers_key_to_index[k]] = answer_dict
else:
prof.answers.append(answer_dict)
form.pos.meta_info = json.dumps(meta_info)
form.pos.save()
@@ -278,23 +271,6 @@ class BaseQuestionsViewMixin:
return not failed
def _upsert_answer(self, field, v, **answer_kwargs):
answer = QuestionAnswer(**answer_kwargs)
try:
self._save_to_answer(field, answer, v)
answer.save()
except IntegrityError:
# Since we prefill ``field.answer`` at form creation time, there's a possible race condition
# here if the users submits their save request a second time while the first one is still running,
# thus leading to duplicate QuestionAnswer objects. Since Django doesn't support UPSERT, the "proper"
# fix would be a transaction with select_for_update(), or at least fetching using get_or_create here
# again. However, both of these approaches have a significant performance overhead for *all* requests,
# while the issue happens very very rarely. So we opt for just catching the error and retrying properly.
answer = QuestionAnswer.objects.get(**answer_kwargs)
self._save_to_answer(field, answer, v)
answer.save()
return answer
def _save_to_answer(self, field, answer, value):
if isinstance(field, forms.ModelMultipleChoiceField):
answstr = ", ".join([str(o) for o in value])
@@ -318,21 +294,6 @@ class BaseQuestionsViewMixin:
else:
answer.answer = value
def _build_answer_dict(self, field, answer, k):
if isinstance(field, forms.ModelMultipleChoiceField) or isinstance(field, forms.ModelChoiceField):
answer_value = {o.identifier: str(o) for o in answer.options.all()}
elif isinstance(field, forms.BooleanField):
answer_value = bool(answer.answer)
else:
answer_value = str(answer.answer)
return {
'field_name': k,
'field_label': str(field.label),
'value': answer_value,
'question_type': field.question.type,
'question_identifier': field.question.identifier,
}
class OrderQuestionsViewMixin(BaseQuestionsViewMixin):
invoice_form_class = BaseInvoiceAddressForm
@@ -348,7 +309,7 @@ class OrderQuestionsViewMixin(BaseQuestionsViewMixin):
def positions(self):
qqs = self.request.event.questions.all()
if self.only_user_visible:
qqs = qqs.filter(ask_during_checkin=False, hidden=False, container_type=Question.ContainerType.ORDERPOSITION)
qqs = qqs.filter(ask_during_checkin=False, hidden=False)
return list(self.order.positions.select_related(
'item', 'variation'
).prefetch_related(
@@ -436,7 +397,6 @@ class OrderQuestionsViewMixin(BaseQuestionsViewMixin):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['order'] = self.order
ctx['order_questions_form'] = self.order_questions_form
ctx['formgroups'] = self.formdict.items()
ctx['invoice_form'] = self.invoice_form
ctx['invoice_address_asked'] = self.address_asked
+3 -11
View File
@@ -153,19 +153,11 @@ class QuestionForm(I18nModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.container_type == Question.ContainerType.ORDERPOSITION:
self.fields['items'].queryset = self.instance.event.items.all()
self.fields['items'].required = True
else:
del self.fields['items']
del self.fields['ask_during_checkin']
del self.fields['show_during_checkin']
del self.fields['print_on_invoice']
self.fields['dependency_question'].widget.attrs['data-container-type'] = self.instance.container_type
self.fields['items'].queryset = self.instance.event.items.all()
self.fields['items'].required = True
self.fields['dependency_question'].queryset = self.instance.event.questions.filter(
type__in=(Question.TYPE_BOOLEAN, Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE),
ask_during_checkin=False,
container_type=self.instance.container_type,
ask_during_checkin=False
)
if self.instance.pk:
self.fields['dependency_question'].queryset = self.fields['dependency_question'].queryset.exclude(
+3 -1
View File
@@ -562,7 +562,9 @@ class OrderPositionChangeForm(forms.Form):
if instance.addon_to_id:
del self.fields['operation_split']
if not instance.seat and not instance._seat_allowed:
if not instance.seat and not (
instance.item.seat_category_mappings.filter(subevent=instance.subevent).exists()
):
del self.fields['seat']
choices = [
+6 -6
View File
@@ -94,14 +94,14 @@ class SubEventBulkForm(SubEventForm):
label=_('Start of presale'),
help_text=_('Optional. No products will be sold before this date.'),
required=False,
limit_choices=('event__date_from', 'event__date_to'),
limit_choices=('date_from', 'date_to'),
)
rel_presale_end = RelativeDateTimeField(
label=_('End of presale'),
help_text=_('Optional. No products will be sold after this date. If you do not set this value, the presale '
'will end after the end date of your event.'),
required=False,
limit_choices=('event__date_from', 'event__date_to'),
limit_choices=('date_from', 'date_to'),
)
skip_if_overlap = forms.BooleanField(
label=pgettext_lazy('subevent', 'Skip dates that overlap with any existing date'),
@@ -333,12 +333,12 @@ class BulkSubEventItemForm(SubEventItemForm):
rel_available_from = RelativeDateTimeField(
label=_('Available from'),
required=False,
limit_choices=('event__date_from', 'event__date_to'),
limit_choices=('date_from', 'date_to'),
)
rel_available_until = RelativeDateTimeField(
label=_('Available until'),
required=False,
limit_choices=('event__date_from', 'event__date_to'),
limit_choices=('date_from', 'date_to'),
)
def __init__(self, *args, **kwargs):
@@ -355,12 +355,12 @@ class BulkSubEventItemVariationForm(SubEventItemVariationForm):
rel_available_from = RelativeDateTimeField(
label=_('Available from'),
required=False,
limit_choices=('event__date_from', 'event__date_to'),
limit_choices=('date_from', 'date_to'),
)
rel_available_until = RelativeDateTimeField(
label=_('Available_until'),
required=False,
limit_choices=('event__date_from', 'event__date_to'),
limit_choices=('date_from', 'date_to'),
)
def __init__(self, *args, **kwargs):
+13 -21
View File
@@ -33,7 +33,6 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import functools
from collections import defaultdict
from datetime import datetime
from decimal import Decimal
@@ -70,15 +69,6 @@ OVERVIEW_BANLIST = [
]
@functools.lru_cache(maxsize=32)
def object_id_to_string(model_class, **kwargs):
# The cache is thread-local and persists requests, but it's small enough that we can accept that
try:
return str(model_class.objects.get(**kwargs))
except model_class.DoesNotExist:
return "?"
class OrderChangeLogEntryType(OrderLogEntryType):
prefix = _('The order has been changed:')
@@ -103,10 +93,10 @@ class OrderItemChanged(OrderChangeLogEntryType):
def display_prefixed(self, event: Event, logentry: LogEntry, data):
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + str(object_id_to_string(ItemVariation, item__event_id=event.pk, pk=data['old_variation']))
old_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['old_variation']))
new_item = str(event.items.get(pk=data['new_item']))
if data['new_variation']:
new_item += ' - ' + str(object_id_to_string(ItemVariation, item__event_id=event.pk, pk=data['new_variation']))
new_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['new_variation']))
return _('Position #{posid}: {old_item} ({old_price}) changed to {new_item} ({new_price}).').format(
posid=data.get('positionid', '?'),
old_item=old_item, new_item=new_item,
@@ -163,14 +153,14 @@ class OrderTaxRuleChanged(OrderChangeLogEntryType):
if 'positionid' in data:
return _('Tax rule of position #{posid} changed from {old_rule} to {new_rule}.').format(
posid=data.get('positionid', '?'),
old_rule=object_id_to_string(TaxRule, pk=data['old_taxrule']) if data['old_taxrule'] else '',
new_rule=object_id_to_string(TaxRule, pk=data['new_taxrule']),
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '',
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
)
elif 'fee' in data:
return _('Tax rule of fee #{fee} changed from {old_rule} to {new_rule}.').format(
fee=data.get('fee', '?'),
old_rule=object_id_to_string(TaxRule, pk=data['old_taxrule']) if data['old_taxrule'] else '',
new_rule=object_id_to_string(TaxRule, pk=data['new_taxrule']),
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '',
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
)
@@ -214,7 +204,7 @@ class OrderCanceled(OrderChangeLogEntryType):
def display_prefixed(self, event: Event, logentry: LogEntry, data):
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + object_id_to_string(ItemVariation, pk=data['old_variation'])
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
return _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
posid=data.get('positionid', '?'),
old_item=old_item,
@@ -229,7 +219,7 @@ class OrderPositionAdded(OrderChangeLogEntryType):
def display_prefixed(self, event: Event, logentry: LogEntry, data):
item = str(event.items.get(pk=data['item']))
if data['variation']:
item += ' - ' + object_id_to_string(ItemVariation, item__event_id=event.pk, pk=data['variation'])
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
if data['addon_to']:
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
return _('Position #{posid} created: {item} ({price}) as an add-on to position #{addon_to}.').format(
@@ -293,7 +283,7 @@ class OrderChangedSplit(OrderChangeLogEntryType):
def display_prefixed(self, event: Event, logentry: LogEntry, data):
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + object_id_to_string(ItemVariation, pk=data['old_variation'])
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
url = reverse('control:event.order', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
@@ -349,7 +339,6 @@ class OrderChangedSplitFrom(OrderLogEntryType):
'pretix.event.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
})
class CheckinErrorLogEntryType(OrderLogEntryType):
def display(self, logentry: LogEntry, data):
return self.display_plain(self.plain, logentry, data)
@@ -364,7 +353,10 @@ class CheckinErrorLogEntryType(OrderLogEntryType):
event = logentry.event
if 'list' in data and event:
data['list'] = object_id_to_string(CheckinList, event_id=event.id, pk=data['list'])
try:
data['list'] = event.checkin_lists.get(pk=data.get('list')).name
except CheckinList.DoesNotExist:
data['list'] = _("(unknown)")
else:
data['list'] = _("(unknown)")
+3 -11
View File
@@ -133,22 +133,14 @@ This signal is sent out to include custom HTML in the top part of the the event
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
An additional keyword argument ``subevent`` *can* contain a sub-event.
"""
event_dashboard_widgets = EventPluginSignal()
event_dashboard_statistics = EventPluginSignal()
"""
This signal is sent out to include widgets in the event dashboard. Receivers
should return a list of dictionaries, where each dictionary can have the keys:
* content (SafeString, containing HTML)
* display_size (str, one of "full" (whole row), "big" (half a row) or "small"
(quarter of a row). May be ignored on small displays, default is "small")
* priority (int, used for ordering, higher comes first, default is 1)
* url (str, optional, if the full widget should be a link)
This signal is sent out to include statistical content on the event dashboard.
Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped.
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
An additional keyword argument ``subevent`` *can* contain a sub-event.
"""
user_dashboard_widgets = GlobalSignal()
@@ -23,6 +23,7 @@
{% endif %}
{% compress js %}
<script type="text/javascript" src="{% static "jquery/js/jquery-3.6.4.min.js" %}"></script>
<script type="text/javascript" src="{% static "htmx/htmx-2.0.10.min.js" %}"></script>
<script type="text/javascript" src="{% static "js/jquery.formset.js" %}"></script>
<script type="text/javascript" src="{% static "typeahead/typeahead.bundle.js" %}"></script>
<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.js" %}"></script>
@@ -58,7 +59,6 @@
<script type="text/javascript" src="{% static "leaflet/leaflet.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/geo.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/reldate.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
<script type="text/javascript" src="{% static "sortable/Sortable.js" %}"></script>
<script type="text/javascript" src="{% static "colorpicker/bootstrap-colorpicker.js" %}"></script>
@@ -0,0 +1,23 @@
{% load i18n %}
{% load icon %}
{% load humanize %}
{% for cl in lists %}
<a class="quotabox quotabox-full availability"
href="{% url "control:event.orders.checkinlists.show" organizer=request.event.organizer.slug event=request.event.slug list=cl.id %}">
<strong>{{ cl.name }}</strong>
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-{{ cl.percent }}">
</div>
</div>
<div class="numbers">
{% icon "sign-in" %}
{{ cl.checkin_count|default_if_none:0|intcomma }} /
{{ cl.position_count|default_if_none:0|intcomma }}
<br>
{% icon "user" %}
{% blocktrans trimmed with n=cl.inside_count|intcomma %}
{{ n }} present
{% endblocktrans %}
</div>
</a>
{% endfor %}
@@ -0,0 +1,17 @@
{% load i18n %}
{% load icon %}
<div id="comment-form">
<strong>{% trans "Comment" %}:</strong>
{% if "event.settings.general:write" in request.eventpermset %}
<button type="button" class="btn btn-default btn-xs"
hx-get="{% url "control:event.index.comment" organizer=request.organizer.slug event=request.event.slug %}"
hx-target="#comment-form">
{% icon "edit" %}
</button>
{% endif %}
{% if request.event.comment %}
<p>
{{ request.event.comment|linebreaksbr }}
</p>
{% endif %}
</div>
@@ -0,0 +1,16 @@
{% load i18n %}
{% load bootstrap3 %}
<form class="form" method="post"
hx-post="{% url "control:event.index.comment" event=request.event.slug organizer=request.event.organizer.slug %}">
{% csrf_token %}
<div>
<p>
{% bootstrap_field form.comment layout="inline" show_help=True show_label=False horizontal_field_class="col-md-12" %}
</p>
<p class="text-right flip">
<button class="btn btn-default">
{% trans "Update comment" %}
</button>
</p>
</div>
</form>
@@ -0,0 +1,4 @@
{% load i18n %}
{% for q in quotas %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q full=1 %}
{% endfor %}
@@ -0,0 +1,11 @@
{% load i18n %}
{% load humanize %}
{% if count %}
<a href="{% url "control:event.orders.waitinglist" event=request.event.slug organizer=request.organizer.slug %}">
{% blocktrans trimmed with number=count|intcomma count count=count %}
{{ number }} person waiting
{% plural %}
{{ number }} persons waiting
{% endblocktrans %}
</a>
{% endif %}
@@ -1,4 +1,5 @@
{% load i18n %}
{% load eventsignal %}
{% if has_overpaid_orders %}
<div class="alert alert-warning">
{% blocktrans trimmed %}
@@ -56,3 +57,4 @@
class="btn btn-primary">{% trans "Show sync problems" %}</a>
</div>
{% endif %}
{% eventsignal request.event "pretix.control.signals.event_dashboard_top" request=request %}
@@ -0,0 +1,19 @@
{% load i18n %}
<div class="panel panel-primary">
<div class="panel-heading">
<h2 class="panel-title">{% trans "Welcome to pretix!" %}</h2>
</div>
<div class="panel-body">
<div class="attentionline">{% trans "Get started with our setup tool" %}</div>
<p>
{% blocktrans trimmed %}
To start selling tickets, you need to create products or quotas. The fastest way to create
this is to use our setup tool.
{% endblocktrans %}
</p>
<a href="{% url "control:event.quick" organizer=request.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg">
{% trans "Set up event" %}
</a>
</div>
</div>
@@ -1,12 +0,0 @@
<div class="welcome-wizard">
<h3>{{ title }}</h3>
{% if subtitle %}
<div class="attentionline">{{ subtitle }}</div>
{% endif %}
{% if text %}
<p>{{ text }}</p>
{% endif %}
{% if button_text %}
<p><a href="{{ button_url }}" class="btn btn-primary btn-lg">{{ button_text }}</a></p>
{% endif %}
</div>
@@ -1,10 +1,10 @@
{% load i18n %}
<div class="panel panel-default items">
<div class="panel-heading">
<details class="panel panel-default items" open>
<summary class="panel-heading">
<h3 class="panel-title">
{% trans "Your timeline" %}
{% trans "Timeline" %}
</h3>
</div>
</summary>
<div class="panel-body timeline">
{% regroup timeline by date as tl_list %}
{% for day in tl_list %}
@@ -33,4 +33,4 @@
</div>
{% endfor %}
</div>
</div>
</details>
@@ -3,10 +3,10 @@
{% load eventurl %}
{% load bootstrap3 %}
{% load static %}
{% load eventsignal %}
{% load icon %}
{% block title %}{{ request.event.name }}{% endblock %}
{% block content %}
<h1>
<h1 class="event-dashboard-header">
{{ request.event.name }}
<small>
{% if request.event.has_subevents %}
@@ -14,11 +14,43 @@
{% else %}
{{ request.event.get_date_range_display }}
{% endif %}
<span id="warnings_loading" class="fa fa-cog fa-spin"></span>
<span id="warnings_indicator" class="htmx-indicator">{% icon "cog fa-spin" %}</span>
</small>
<div class="pull-right flip">
<a href="{% url "control:event.live" organizer=request.organizer.slug event=request.event.slug %}"
data-toggle="tooltip"
title="{% trans "Click to change shop status" %}">
{% icon "pencil" %}</a>
<a href="{% url "control:event.live" organizer=request.organizer.slug event=request.event.slug %}"
data-toggle="tooltip"
title="{% trans "Click to change shop status" %}">
{% if request.event.live and request.event.testmode %}
<span class="label label-warning">
<span class="fa fa-warning"></span>
{% trans "public test mode" %}
</span>
{% elif request.event.live %}
<span class="label label-success">
<span class="fa fa-check"></span>
{% trans "live" %}
</span>
{% elif request.event.testmode %}
<span class="label label-warning">
<span class="fa fa-power-off"></span>
{% trans "private test mode" %}
</span>
{% else %}
<span class="label label-danger">
<span class="fa fa-power-off"></span>
{% trans "offline" %}
</span>
{% endif %}
</a>
</div>
<div class="clearfix"></div>
</h1>
<div class="helper-space-below">
{% trans "Shop URL:" %}
<strong>{% trans "Shop URL:" %}</strong>
<span id="shop_url" class="text-muted">{% abseventurl request.event "presale:event.index" %}</span>
<button type="button" class="btn btn-default btn-xs btn-clipboard js-only" data-clipboard-target="#shop_url">
<i class="fa fa-clipboard" aria-hidden="true"></i>
@@ -31,75 +63,76 @@
{% include "pretixcontrol/event/fragment_qr_dropdown.html" with url=0 %}
</div>
<div class="clearfix"></div>
</div>
<div id="warnings_target"></div>
{% eventsignal request.event "pretix.control.signals.event_dashboard_top" request=request %}
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
{% include "pretixcontrol/event/fragment_subevent_choice_simple.html" %}
</form>
{% endif %}
{% if not request.event.has_subevents or subevent %}
{% include "pretixcontrol/event/fragment_timeline.html" %}
{% endif %}
<div class="dashboard">
{% for w in widgets %}
<div class="widget-container widget-{{ w.display_size|default:"small" }} {% if w.lazy %}widget-lazy-loading{% endif %}" data-lazy-id="{{ w.lazy }}">
{% if w.url %}{# backwards compatibility #}
<a href="{{ w.url }}" class="widget">
{% if w.lazy %}
<span class="fa fa-cog fa-4x"></span>
{% else %}
{{ w.content }}
{% endif %}
</a>
{% elif w.link %}
<a href="{{ w.link }}" class="widget">
{% if w.lazy %}
<span class="fa fa-cog fa-4x´"></span>
{% else %}
{{ w.content }}
{% endif %}
</a>
{% else %}
<div class="widget">
{% if w.lazy %}
<span class="fa fa-cog fa-4x"></span>
{% else %}
{{ w.content }}
{% endif %}
</div>
{% endif %}
</div>
{% endfor %}
{% include "pretixcontrol/event/dashboard_partial_comment.html" with url=0 %}
</div>
<div hx-get="{% url "control:event.index.warnings" organizer=request.organizer.slug event=request.event.slug %}"
{# loading indicator is somewhere else because the happy case is "no warnings" and shouldn't make the page jump #}
hx-indicator="#warnings_indicator"
hx-trigger="load"></div>
{% if stats %}
{{ stats }}
{% endif %}
<p>&nbsp;</p>
<div class="panel panel-default items">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Internal comment" %}
</h3>
</div>
<div class="panel-body">
<form class="form" method="post"
action="{% url "control:event.comment" event=request.event.slug organizer=request.event.organizer.slug %}">
{% csrf_token %}
<div class="row">
{% bootstrap_field comment_form.comment layout="horizontal" show_help=True show_label=False horizontal_field_class="col-md-12" %}
{% if not request.event.items.exists %}
{% include "pretixcontrol/event/dashboard_partial_welcome.html" %}
{% elif not request.event.has_subevents %}
{% include "pretixcontrol/event/fragment_timeline.html" %}
<div class="row">
<div class="col-md-6 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
<div hx-get="{% url "control:event.index.waiting" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
<span class="loading-mock loading-mock-text-short"></span>
</div>
</div>
<h2 class="panel-title">{% trans "Quotas" %}</h2>
</div>
<div class="panel-body">
<div hx-get="{% url "control:event.index.quotas" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12" %}
{% include "pretixcontrol/fragment_quota_box_mock.html" %}
{% endfor %}
</div>
</div>
</div>
{% if not comment_form.readonly %}
<p class="text-right flip">
<br>
<button class="btn btn-default">
{% trans "Update comment" %}
</button>
</p>
{% endif %}
</form>
</div>
<div class="col-md-6 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">{% trans "Check-in lists" %}</h2>
</div>
<div class="panel-body">
<div hx-get="{% url "control:event.index.checkin" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12" %}
{% include "pretixcontrol/fragment_quota_box_mock.html" %}
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% elif has_checkin_widgets %}
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">{% trans "Check-in lists" %}</h2>
</div>
<div class="panel-body">
<div hx-get="{% url "control:event.index.checkin" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12" %}
{% include "pretixcontrol/fragment_quota_box_mock.html" %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if "event.orders:read" in request.eventpermset or "event.orders:write" in request.eventpermset or "event.settings.general:write" in request.eventpermset or "event.items:write" in request.eventpermset %}
<div class="panel panel-default">
<div class="panel-heading">
@@ -107,10 +140,27 @@
{% trans "Event logs" %}
</h3>
</div>
<ul class="list-group" id="logs_target">
<div class="logs-lazy-loading">
<span class="fa fa-cog fa-4x"></span>
</div>
<ul class="list-group"
hx-get="{% url "control:event.index.logs" organizer=request.organizer.slug event=request.event.slug %}"
hx-trigger="load">
{% for i in "12345" %}
<li class="list-group-item logentry">
<div class="row">
<div class="col-lg-2 col-sm-6 col-xs-12">
<span class="loading-mock loading-mock-text-medium"></span>
</div>
<div class="col-lg-2 col-sm-6 col-xs-12">
<span class="loading-mock loading-mock-text-short"></span>
</div>
<div class="col-lg-2 col-sm-12 col-xs-12">
<span class="loading-mock loading-mock-text-medium"></span>
</div>
<div class="col-lg-6 col-sm-12 col-xs-12">
<span class="loading-mock loading-mock-text-long"></span>
</div>
</div>
</li>
{% endfor %}
</ul>
<div class="panel-footer">
<a href="{% url "control:event.log" event=request.event.slug organizer=request.event.organizer.slug %}">
@@ -86,7 +86,7 @@
<a href="?{% url_replace request 'ordering' 'date_to' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Paid tickets per quota" %}
{% trans "Quota utilization" %}
</th>
<th>
{% trans "Status" %}
@@ -151,7 +151,7 @@
</td>
<td>
{% for q in e.first_quotas|slice:":3" %}
{% include "pretixcontrol/fragment_quota_box_paid.html" with quota=q %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
{% endfor %}
{% if e.first_quotas|length > 3 %}
<a href="{% url "control:event.items.quotas" organizer=e.organizer.slug event=e.slug %}"
@@ -1,18 +1,26 @@
{% load i18n %}
<div class="quotabox availability" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}<br>{% blocktrans with date=q.cached_availability_time|date:"SHORT_DATETIME_FORMAT" %}Numbers as of {{ date }}{% endblocktrans %}">
{% load humanize %}
<a class="quotabox {% if full %}quotabox-full{% endif %}" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}{% if q.cached_avail.1 is not None %}<br>{% blocktrans with num=q.cached_avail.1 %}Currently available: {{ num }}{% endblocktrans %}{% endif %}"
href="{% url "control:event.items.quotas.show" event=q.event.slug organizer=q.event.organizer.slug quota=q.pk %}">
{% if full %}
<strong>{{ q.name }}</strong>
{% endif %}
{% if q.size|default_if_none:"NONE" == "NONE" %}
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-100">
</div>
</div>
{% else %}
<div class="progress">
<div class="progress-bar progress-bar-{% if q.cached_avail.0 <= 10 or q.cached_avail.0 >= 100 %}danger{% else %}warning{% endif %} progress-bar-{{ q.inv_percent }}">
<div class="progress-bar progress-bar-{% if q.cached_avail.0 < 10 %}danger{% elif q.cached_avail.0 < 100 %}warning{% else %}success{% endif %} progress-bar-{{ q.percent_paid }}">
</div>
<div class="progress-bar progress-bar-{% if q.cached_avail.0 < 10 %}danger{% elif q.cached_avail.0 < 100 %}warning{% else %}success{% endif %} progress-bar-unconfirmed progress-bar-{{ q.percent_other }}">
</div>
</div>
{% endif %}
<div class="numbers">
{{ q.cached_avail.1|default_if_none:"∞" }} / {{ q.size|default_if_none:"∞" }}
{{ q.used|default_if_none:"∞" }} / {{ q.size|default_if_none:"∞" }}<br>
{% blocktrans trimmed with n=q.cached_availability_paid_orders %}
{{ n }} paid
{% endblocktrans %}
</div>
</div>
</a>
@@ -0,0 +1,10 @@
<a class="quotabox quotabox-full">
<strong>
<span class="loading-mock loading-mock-text-short"></span>
</strong>
<div class="progress loading-mock"></div>
<div class="numbers">
<span class="loading-mock loading-mock-text-short"></span>
<br><span class="loading-mock loading-mock-text-short"></span>
</div>
</a>
@@ -1,17 +0,0 @@
{% load i18n %}
<a class="quotabox" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}{% if q.cached_avail.1 is not None %}<br>{% blocktrans with num=q.cached_avail.1 %}Currently available: {{ num }}{% endblocktrans %}{% endif %}"
href="{% url "control:event.items.quotas.show" event=q.event.slug organizer=q.event.organizer.slug quota=q.pk %}">
{% if q.size|default_if_none:"NONE" == "NONE" %}
<div class="progress">
</div>
{% else %}
<div class="progress">
<div class="progress-bar progress-bar-{% if q.cached_avail.0 < 10 %}danger{% elif q.cached_avail.0 < 100 %}warning{% else %}success{% endif %} progress-bar-{{ q.percent_paid }}">
</div>
</div>
{% endif %}
<div class="numbers">
{{ q.cached_availability_paid_orders|default_if_none:"?" }} / {{ q.size|default_if_none:"∞" }}
</div>
</a>
@@ -13,11 +13,9 @@
{% blocktrans with url=edit_url|add:"#tab-0-1-open" %}If you want to keep the answers, <a href="{{url}}">edit the question</a> and set it to hidden.{% endblocktrans %}
</p>
</div>
<ul>
{% for item in dependent %}
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.pk %}">{{ item }}</a></li>
{% endfor %}
</ul>
{% for item in dependent %}
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.pk %}">{{ item }}</a></li>
{% endfor %}
{% endif %}
<div class="form-group submit-group">
<a href="{% url "control:event.items.questions" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
@@ -24,9 +24,7 @@
<legend>{% trans "General" %}</legend>
{% bootstrap_field form.question layout="control" %}
{% bootstrap_field form.type layout="control" %}
{% if form.items %}
{% bootstrap_field form.items layout="control" %}
{% endif %}
{% bootstrap_field form.items layout="control" %}
{% bootstrap_field form.required layout="control" %}
<div class="alert alert-info alert-required-boolean">
{% blocktrans trimmed %}
@@ -130,16 +128,10 @@
<legend>{% trans "Advanced" %}</legend>
{% bootstrap_field form.help_text layout="control" %}
{% bootstrap_field form.identifier layout="control" %}
{% if form.ask_during_checkin %}
{% bootstrap_field form.ask_during_checkin layout="control" %}
{% endif %}
{% if form.show_during_checkin %}
{% bootstrap_field form.show_during_checkin layout="control" %}
{% endif %}
{% bootstrap_field form.ask_during_checkin layout="control" %}
{% bootstrap_field form.show_during_checkin layout="control" %}
{% bootstrap_field form.hidden layout="control" %}
{% if form.print_on_invoice %}
{% bootstrap_field form.print_on_invoice layout="control" %}
{% endif %}
{% bootstrap_field form.print_on_invoice layout="control" %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_dependency_question">
@@ -10,26 +10,11 @@
{% endblocktrans %}
</p>
{% csrf_token %}
{% if request.event.settings.feature_flag_order_level_questions %}
{% if 'event.items:write' in request.eventpermset %}
<p>
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}?container_type=P" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new per-ticket question" %}
</a>
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}?container_type=O" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new order-level question" %}
</a>
</p>
{% endif %}
<h2>{% trans "Per-ticket questions" %}</h2>
<p>{% trans "These questions are asked for every ticket, so possibly multiple times in the same order." %}</p>
{% else %}
{% if 'event.items:write' in request.eventpermset %}
<p>
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}?container_type=P" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %}
</a>
</p>
{% endif %}
{% if 'event.items:write' in request.eventpermset %}
<p>
<a href="{% url "control:event.items.questions.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new question" %}
</a>
</p>
{% endif %}
<div class="table-responsive">
<table class="table table-hover table-quotas">
@@ -47,8 +32,8 @@
<th class="action-col-2"></th>
</tr>
</thead>
<tbody data-dnd-url="{% url "control:event.items.questions.reorder" organizer=request.event.organizer.slug event=request.event.slug %}?container_type=P">
{% for q in questions %}{% if q.container_type == "P" %}
<tbody data-dnd-url="{% url "control:event.items.questions.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
{% for q in questions %}
<tr data-dnd-id="{{ q.id }}">
<td>
<strong>
@@ -78,6 +63,7 @@
{% if q.pk and q.ask_during_checkin %}
<span class="fa fa-check-square text-muted" data-toggle="tooltip" title="{% trans "Ask during check-in" %}"></span>
{% endif %}
</td>
<td>
{% if q.pk and q.hidden %}
@@ -116,90 +102,8 @@
{% endif %}
</td>
</tr>
{% endif %}{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% if request.event.settings.feature_flag_order_level_questions %}
<h2>
{% trans "Per-order questions" %}
<small><span class="label label-info" title="
{% trans "This functionality is in active development and expected to change significantly over the coming months." %}
{% trans "Per-order questions are currently not supported and will not be displayed in pretixPOS." %}
" data-toggle="tooltip">
<span class="fa fa-flask" aria-hidden="true"></span>
{% trans "Experimental feature" %}
</span></small>
</h2>
<p>{% trans "These questions are asked once per order." %}</p>
<div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Question" %}</th>
<th>{% trans "Type" %}</th>
<th class="iconcol"></th>
<th class="iconcol"></th>
<th class="iconcol"></th>
{% if 'event.items:write' in request.eventpermset %}
<th class="action-col-2"></th>
{% endif %}
<th class="action-col-2"></th>
</tr>
</thead>
<tbody data-dnd-url="{% url "control:event.items.questions.reorder" organizer=request.event.organizer.slug event=request.event.slug %}?container_type=O">
{% for q in questions %}{% if q.container_type == "O" %}
<tr data-dnd-id="{{ q.id }}">
<td>
<strong>
{{ q.question }}
</strong><br>
<small class="text-muted">{{ q.identifier }}</small>
</td>
<td>
{% if q.pk %}
{{ q.get_type_display }}
{% else %}
{% trans "System question" %}
{% endif %}
</td>
<td>
{% if q.required %}
<span class="fa fa-exclamation-circle text-muted" data-toggle="tooltip" title="{% trans "Required question" %}"></span>
{% endif %}
</td>
<td>
{% if q.pk and q.ask_during_checkin %}
<span class="fa fa-check-square text-muted" data-toggle="tooltip" title="{% trans "Ask during check-in" %}"></span>
{% endif %}
</td>
<td>
{% if q.pk and q.hidden %}
<span class="fa fa-eye-slash text-muted" data-toggle="tooltip" title="{% trans "Hidden question" %}"></span>
{% endif %}
</td>
{% if 'event.items:write' in request.eventpermset %}
<td class="dnd-container">
</td>
{% endif %}
<td class="text-right flip">
{% if q.pk %}
{% if 'event.items:write' in request.eventpermset %}
<a href="{% url "control:event.items.questions.edit" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.questions.delete" organizer=request.event.organizer.slug event=request.event.slug question=q.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
{% endif %}
{% else %}
{% if 'event.settings.general:write' in request.eventpermset %}
<a href="{% url "control:event.settings" organizer=request.event.organizer.slug event=request.event.slug %}#tab-0-2-open"
class="btn btn-default btn-sm"><i class="fa fa-wrench"></i></a>
{% endif %}
{% endif %}
</td>
</tr>
{% endif %}{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}
@@ -35,18 +35,6 @@
</div>
</details>
{% endif %}
{% if order_questions_form.fields %}
<details class="panel panel-default" open>
<summary class="panel-heading">
<h4 class="panel-title">
<strong>{% trans "Additional order information" %}</strong>
</h4>
</summary>
<div class="panel-body">
{% bootstrap_form order_questions_form layout="horizontal" %}
</div>
</details>
{% endif %}
{% for pos, forms in formgroups %}
<details class="panel panel-default" open>
<summary class="panel-heading">
@@ -1,28 +0,0 @@
{% load safelink %}
{% load i18n %}
{% load rich_text %}
{% if answer %}
{% if answer.file %}
<span class="fa fa-file"></span>
<a href="{{ answer.backend_file_url }}?token={% answer_token request answer %}">
{{ answer.file_name }}
</a>
<span class="label label-danger" data-toggle="tooltip"
title="{% trans "This file has been uploaded by a user and could contain viruses or other malicious content." %}">
{% trans "UNSAFE" %}
</span>
{% if answer.is_image %}
<br>
<a href="{{ answer.backend_file_url }}?token={% answer_token request answer %}" data-lightbox="order"
class="answer-thumb">
<img src="{{ answer.backend_file_url }}?token={% answer_token request answer %}">
</a>
{% endif %}
{% elif question.type == "M" %}
{{ answer.to_string_i18n|rich_text_snippet }}
{% else %}
{{ answer.to_string_i18n|linebreaksbr }}
{% endif %}
{% else %}
<em>{% trans "not answered" %}</em>
{% endif %}
@@ -427,14 +427,6 @@
</form>
</dd>
{% endif %}
{% if order.answers.all %}
<hr>
{% endif %}
{% for ans in order.answers.all %}
<dt>{{ ans.question.internal_name|default:ans.question.question }}</dt>
<dd>{% include "pretixcontrol/order/fragment_question_answer.html" with request=request question=ans.question answer=ans %}</dd>
{% endfor %}
</dl>
</div>
</div>
@@ -672,7 +664,31 @@
{% endif %}
</dt>
<dd>
{% include "pretixcontrol/order/fragment_question_answer.html" with request=request question=q answer=q.answer %}
{% if q.answer %}
{% if q.answer.file %}
<span class="fa fa-file"></span>
<a href="{{ q.answer.backend_file_url }}?token={% answer_token request q.answer %}">
{{ q.answer.file_name }}
</a>
<span class="label label-danger" data-toggle="tooltip"
title="{% trans "This file has been uploaded by a user and could contain viruses or other malicious content." %}">
{% trans "UNSAFE" %}
</span>
{% if q.answer.is_image %}
<br>
<a href="{{ q.answer.backend_file_url }}?token={% answer_token request q.answer %}" data-lightbox="order"
class="answer-thumb">
<img src="{{ q.answer.backend_file_url }}?token={% answer_token request q.answer %}">
</a>
{% endif %}
{% elif q.type == "M" %}
{{ q.answer.to_string_i18n|rich_text_snippet }}
{% else %}
{{ q.answer.to_string_i18n|linebreaksbr }}
{% endif %}
{% else %}
<em>{% trans "not answered" %}</em>
{% endif %}
</dd>
{% endfor %}
{% for q in line.additional_fields %}
@@ -817,7 +833,7 @@
<strong>{% trans "Pending total" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
<strong>{{ pending_sum|money:event.currency }}</strong>
<strong>{{ order.pending_sum|money:event.currency }}</strong>
</div>
<div class="clearfix"></div>
</div>
@@ -118,10 +118,6 @@
</td>
{% endif %}
<td>
<span class="fa fa-fw {% if d.last_seen and d.last_seen.is_recent %}fa-check-circle text-success{% else %}fa-circle text-danger{% endif %}"
data-toggle="tooltip"
title="{% if d.last_seen %}{% blocktrans with time=d.last_seen.last_seen|date:"SHORT_DATETIME_FORMAT" %}Last seen: {{ time }}{% endblocktrans %}{% else %}{% trans "No recent contact" %}{% endif %}"
></span>
{{ d.device_id }}
</td>
<td>
@@ -129,7 +125,6 @@
<del>{% endif %}
{{ d.name }}
{% if d.revoked %}</del>{% endif %}
{% if d.gate %}
<br>
<small class="text-muted">{{ d.gate.name }}</small>
@@ -72,6 +72,7 @@
{% endif %}
</dl>
</fieldset>
{% include "pretixcontrol/event/fragment_timeline.html" %}
<fieldset>
<legend>{% trans "Quotas" %}</legend>
<div class="table-responsive">
@@ -102,7 +102,7 @@
<a href="?{% url_replace request 'filter-ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
</th>
<th>
{% trans "Paid tickets per quota" %}
{% trans "Quota utilization" %}
</th>
<th>
{% trans "Status" %}
@@ -155,7 +155,7 @@
</td>
<td>
{% for q in s.first_quotas|slice:":3" %}
{% include "pretixcontrol/fragment_quota_box_paid.html" with quota=q %}
{% include "pretixcontrol/fragment_quota_box.html" with quota=q %}
{% endfor %}
{% if s.first_quotas|length > 3 %}
<a href="{% url "control:event.items.quotas" organizer=request.event.organizer.slug event=request.event.slug %}?subevent={{ s.id }}"
@@ -251,7 +251,7 @@
</td>
<td>
{% if e.voucher %}
<a href="{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=e.voucher.pk %}?next={{ request.get_full_path|urlencode }}">
<a href="{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=e.voucher.pk %}">
{{ e.voucher }}
</a>
{% elif not e.voucher and e.availability.0 == 100 and e.availability.1|default_if_none:"none" != "none" %}
+4 -3
View File
@@ -272,15 +272,16 @@ urlpatterns = [
re_path(r'^event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include([
re_path(r'^$', dashboards.event_index, name='event.index'),
re_path(r'^qrcode.(?P<filetype>(png|jpeg|gif|svg))$', event.EventQRCode.as_view(), name='event.qrcode'),
re_path(r'^widgets.json$', dashboards.event_index_widgets_lazy, name='event.index.widgets'),
re_path(r'^dashboard/partials/logs$', dashboards.event_index_log_lazy, name='event.index.logs'),
re_path(r'^dashboard/partials/warnings$', dashboards.event_index_warnings_lazy, name='event.index.warnings'),
re_path(r'^dashboard/partials/quotas$', dashboards.event_index_quotas_lazy, name='event.index.quotas'),
re_path(r'^dashboard/partials/waiting$', dashboards.event_index_waiting_lazy, name='event.index.waiting'),
re_path(r'^dashboard/partials/checkin$', dashboards.event_index_checkin_lazy, name='event.index.checkin'),
re_path(r'^dashboard/partials/comment$', event.EventComment.as_view(), name='event.index.comment'),
re_path(r'^live/$', event.EventLive.as_view(), name='event.live'),
re_path(r'^transfer_session/$', event.EventTransferSession.as_view(), name='event.transfer_session'),
re_path(r'^logs/$', event.EventLog.as_view(), name='event.log'),
re_path(r'^delete/$', event.EventDelete.as_view(), name='event.delete'),
re_path(r'^comment/$', event.EventComment.as_view(),
name='event.comment'),
re_path(r'^quickstart/$', event.QuickSetupView.as_view(), name='event.quick'),
re_path(r'^settings/$', event.EventUpdate.as_view(), name='event.settings'),
re_path(r'^settings/plugins$', event.EventPlugins.as_view(), name='event.settings.plugins'),
+63 -330
View File
@@ -33,317 +33,48 @@
# License for the specific language governing permissions and limitations under the License.
from datetime import timedelta
from decimal import Decimal
from zoneinfo import ZoneInfo
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.humanize.templatetags.humanize import intcomma
from django.db.models import (
Count, IntegerField, Max, Min, OuterRef, Prefetch, Q, Subquery, Sum,
Count, IntegerField, Max, Min, OuterRef, Q, Subquery,
)
from django.db.models.functions import Coalesce, Greatest
from django.dispatch import receiver
from django.http import JsonResponse
from django.http import Http404, JsonResponse
from django.shortcuts import render
from django.template.loader import get_template
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.html import conditional_escape, escape, format_html
from django.utils.html import (
conditional_escape, escape, format_html, format_html_join,
)
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, ngettext, pgettext
from pretix.base.decimal import round_decimal
from pretix.base.models import (
Item, ItemCategory, ItemVariation, Order, OrderPosition, OrderRefund,
Question, Quota, SubEvent, Voucher, WaitingListEntry,
Item, ItemCategory, Order, OrderRefund, Question, Quota, Voucher,
WaitingListEntry,
)
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.timeline import timeline_for_event
from pretix.control.signals import (
event_dashboard_widgets, user_dashboard_widgets,
event_dashboard_statistics, user_dashboard_widgets,
)
from pretix.helpers.daterange import daterange
from ...base.models.orders import CancellationRequest
from ...base.models.organizer import TeamQuerySet
from ...base.templatetags.money import money_filter
from ..logdisplay import OVERVIEW_BANLIST
NUM_WIDGET = '<div class="numwidget"><span class="num">{num}</span><span class="text">{text}</span></div>'
from .utils import prepare_quotas_for_boxes
@receiver(signal=event_dashboard_widgets)
def base_widgets(sender, subevent=None, lazy=False, **kwargs):
if not lazy:
prodc = Item.objects.filter(
event=sender, active=True,
).filter(
(Q(available_until__isnull=True) | Q(available_until__gte=now())) &
(Q(available_from__isnull=True) | Q(available_from__lte=now()))
).count()
if subevent:
opqs = OrderPosition.objects.filter(subevent=subevent)
else:
opqs = OrderPosition.objects
tickc = opqs.filter(
order__event=sender, item__admission=True,
order__status__in=(Order.STATUS_PAID, Order.STATUS_PENDING),
).count()
paidc = opqs.filter(
order__event=sender, item__admission=True,
order__status=Order.STATUS_PAID,
).count()
if subevent:
rev = opqs.filter(
order__event=sender, order__status=Order.STATUS_PAID
).aggregate(
sum=Sum('price')
)['sum'] or Decimal('0.00')
else:
rev = Order.objects.filter(
event=sender,
status=Order.STATUS_PAID
).aggregate(sum=Sum('total'))['sum'] or Decimal('0.00')
return [
def event_index_waiting_lazy(request, organizer, event):
wles = WaitingListEntry.objects.filter(event=request.event, voucher__isnull=True)
return render(
request,
'pretixcontrol/event/dashboard_partial_waiting.html',
{
'content': None if lazy else format_html(NUM_WIDGET, num=intcomma(tickc), text=_('Attendees (ordered)')),
'lazy': 'attendees-ordered',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.orders', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
},
{
'content': None if lazy else format_html(NUM_WIDGET, num=intcomma(paidc), text=_('Attendees (paid)')),
'lazy': 'attendees-paid',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.orders.overview', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
},
{
'content': None if lazy else format_html(
NUM_WIDGET,
num=money_filter(round_decimal(rev, sender.currency), sender.currency, hide_currency=True),
text=_('Total revenue ({currency})').format(currency=sender.currency)
),
'lazy': 'total-revenue',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.orders.overview', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
}) + ('?subevent={}'.format(subevent.pk) if subevent else '')
},
{
'content': None if lazy else format_html(NUM_WIDGET, num=prodc, text=_('Active products')),
'lazy': 'active-products',
'display_size': 'small',
'priority': 100,
'url': reverse('control:event.items', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
})
},
]
@receiver(signal=event_dashboard_widgets)
def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
widgets = []
wles = WaitingListEntry.objects.filter(event=sender, subevent=subevent, voucher__isnull=True)
if wles.exists():
if not lazy:
quota_cache = {}
happy = 0
tuples = wles.values('item', 'variation').order_by().annotate(cnt=Count('id'))
items = {
i.pk: i for i in sender.items.filter(id__in=[t['item'] for t in tuples]).prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=sender.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
)
}
vars = {
i.pk: i for i in ItemVariation.objects.filter(
item__event=sender, id__in=[t['variation'] for t in tuples if t['variation']]
).prefetch_related(
Prefetch('quotas',
to_attr='_subevent_quotas',
queryset=sender.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)),
)
}
for wlt in tuples:
item = items.get(wlt['item'])
variation = vars.get(wlt['variation'])
if not item:
continue
quotas = (
variation._get_quotas(subevent=subevent)
if variation
else item._get_quotas(subevent=subevent)
)
row = (
variation.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
if variation
else item.check_quotas(subevent=subevent, count_waitinglist=False, _cache=quota_cache)
)
if row[1] is None:
happy += wlt['cnt']
elif row[1] > 0:
happy += min(wlt['cnt'], row[1])
for q in quotas:
if q.size is not None:
quota_cache[q.pk] = (quota_cache[q.pk][0], quota_cache[q.pk][1] - min(wlt['cnt'], row[1]))
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET, num=intcomma(happy), text=_('available to give to people on waiting list')
),
'lazy': 'waitinglist-avail',
'priority': 50,
'url': reverse('control:event.orders.waitinglist', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
})
})
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET, num=intcomma(wles.count()), text=_('total waiting list length')
),
'lazy': 'waitinglist-length',
'display_size': 'small',
'priority': 50,
'url': reverse('control:event.orders.waitinglist', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
})
})
return widgets
@receiver(signal=event_dashboard_widgets)
def quota_widgets(sender, subevent=None, lazy=False, **kwargs):
widgets = []
quotas = sender.quotas.filter(subevent=subevent)
qa = QuotaAvailability()
if quotas:
qa.queue(*quotas)
qa.compute(allow_cache=True)
for q in quotas:
if not lazy:
status, left = qa.results[q] if q in qa.results else q.availability(allow_cache=True)
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET,
num='{}/{}'.format(intcomma(left), intcomma(q.size)) if q.size is not None else '\u221e',
text=format_html(_('{quota} left'), quota=q.name)
),
'lazy': 'quota-{}'.format(q.pk),
'display_size': 'small',
'priority': 50,
'url': reverse('control:event.items.quotas.show', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
'quota': q.id
})
})
return widgets
@receiver(signal=event_dashboard_widgets)
def shop_state_widget(sender, **kwargs):
return [{
'display_size': 'small',
'priority': 1000,
'content': format_html(
'<div class="shopstate">{t1}<br><span class="{cls}"><span class="fa {icon}"></span> {state}</span>{t2}</div>',
t1=_('Your ticket shop is'), t2=_('Click here to change'),
state=_('live') if sender.live and not sender.testmode else (
_('live and in test mode') if sender.live else (
_('not yet public') if not sender.testmode else (
_('in private test mode')
)
)
),
icon='fa-check-circle' if sender.live and not sender.testmode else (
'fa-warning' if sender.live else (
'fa-times-circle' if not sender.testmode else (
'fa-times-circle'
)
)
),
cls='live' if sender.live else 'off'
),
'url': reverse('control:event.live', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug
})
}]
@receiver(signal=event_dashboard_widgets)
def checkin_widget(sender, subevent=None, lazy=False, **kwargs):
widgets = []
qs = sender.checkin_lists.filter(subevent=subevent)
for cl in qs:
widgets.append({
'content': None if lazy else format_html(
NUM_WIDGET,
num='{}/{}'.format(intcomma(cl.inside_count), intcomma(cl.position_count)),
text=format_html(_('Present {list}'), list=cl.name)
),
'lazy': 'checkin-{}'.format(cl.pk),
'display_size': 'small',
'priority': 50,
'url': reverse('control:event.orders.checkinlists.show', kwargs={
'event': sender.slug,
'organizer': sender.organizer.slug,
'list': cl.pk
})
})
return widgets
@receiver(signal=event_dashboard_widgets)
def welcome_wizard_widget(sender, **kwargs):
template = get_template('pretixcontrol/event/dashboard_widget_welcome.html')
ctx = {
'title': _('Welcome to pretix!')
}
kwargs = {'event': sender.slug, 'organizer': sender.organizer.slug}
if not sender.items.exists():
ctx.update({
'subtitle': _('Get started with our setup tool'),
'text': _('To start selling tickets, you need to create products or quotas. The fastest way to create '
'this is to use our setup tool.'),
'button_text': _('Set up event'),
'button_url': reverse('control:event.quick', kwargs=kwargs)
})
else:
return []
return [{
'display_size': 'full',
'priority': 2000,
'content': template.render(ctx)
}]
'count': wles.count,
}
)
def build_json_response(widgets):
@@ -353,62 +84,38 @@ def build_json_response(widgets):
def event_index(request, organizer, event):
from pretix.control.forms.event import CommentForm
can_view_orders = request.user.has_event_permission(
request.organizer,
request.event,
'event.orders:read',
request=request
)
subevent = None
if request.GET.get("subevent", "") != "" and request.event.has_subevents:
i = request.GET.get("subevent", "")
try:
subevent = request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
can_change_event_settings = request.user.has_event_permission(request.organizer, request.event,
'event.settings.general:write', request=request)
widgets = []
stats = []
if can_view_orders:
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=True):
widgets.extend(result)
for r, result in event_dashboard_statistics.send(sender=request.event, request=request):
stats.append(result)
ctx = {
'widgets': rearrange(widgets),
'subevent': subevent,
'comment_form': CommentForm(initial={'comment': request.event.comment}, readonly=not can_change_event_settings),
'stats': format_html_join("", "{}", [(s,) for s in stats]),
}
ctx['timeline'] = [
{
'date': t.datetime.astimezone(request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(request.event.timezone)
}
for t in timeline_for_event(request.event, subevent)
]
if not request.event.has_subevents:
ctx['timeline'] = [
{
'date': t.datetime.astimezone(request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(request.event.timezone)
}
for t in timeline_for_event(request.event, None)
]
ctx['today'] = now().astimezone(request.event.timezone).date()
ctx['nearly_now'] = now().astimezone(request.event.timezone) - timedelta(seconds=20)
ctx['has_checkin_widgets'] = not request.event.has_subevents or request.event.checkin_lists.filter(subevent=None).exists()
resp = render(request, 'pretixcontrol/event/index.html', ctx)
return resp
def event_index_widgets_lazy(request, organizer, event):
subevent = None
if request.GET.get("subevent", "") != "" and request.event.has_subevents:
i = request.GET.get("subevent", "")
try:
subevent = request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
widgets = []
for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent, lazy=False):
widgets.extend(result)
return build_json_response(widgets)
def event_index_warnings_lazy(request, organizer, event):
can_view_orders = request.user.has_event_permission(request.organizer, request.event, 'event.orders:read',
request=request)
@@ -445,6 +152,32 @@ def event_index_warnings_lazy(request, organizer, event):
)
def event_index_quotas_lazy(request, organizer, event):
if request.event.has_subevents:
raise Http404()
quotas = request.event.quotas.filter(subevent=None)[:10]
prepare_quotas_for_boxes(quotas)
return render(
request,
'pretixcontrol/event/dashboard_partial_quotas.html',
{
'quotas': quotas,
}
)
def event_index_checkin_lazy(request, organizer, event):
lists = request.event.checkin_lists.filter(subevent=None)[:10]
return render(
request,
'pretixcontrol/event/dashboard_partial_checkin.html',
{
'lists': lists,
}
)
def event_index_log_lazy(request, organizer, event):
qs = request.event.logentry_set.all().select_related('user', 'content_type', 'api_token', 'oauth_application',
'device').order_by('-datetime')
+17 -19
View File
@@ -57,10 +57,9 @@ from django.db import transaction
from django.db.models import Count, ProtectedError
from django.forms import inlineformset_factory
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed,
JsonResponse,
Http404, HttpResponse, HttpResponseBadRequest, JsonResponse,
)
from django.shortcuts import redirect
from django.shortcuts import redirect, render
from django.urls import NoReverseMatch, reverse
from django.utils.functional import cached_property
from django.utils.html import conditional_escape, format_html
@@ -1299,24 +1298,23 @@ class EventLog(EventPermissionRequiredMixin, PaginationMixin, ListView):
return LogFilterForm(data=self.request.GET, organizer=self.request.organizer)
class EventComment(EventPermissionRequiredMixin, View):
class EventComment(EventPermissionRequiredMixin, UpdateView):
permission = 'event.settings.general:write'
form_class = CommentForm
template_name = 'pretixcontrol/event/dashboard_partial_comment_form.html'
def post(self, *args, **kwargs):
form = CommentForm(self.request.POST)
if form.is_valid():
self.request.event.comment = form.cleaned_data.get('comment')
self.request.event.save()
self.request.event.log_action('pretix.event.comment', user=self.request.user, data={
'new_comment': form.cleaned_data.get('comment')
})
messages.success(self.request, _('The comment has been updated.'))
else:
messages.error(self.request, _('Could not update the comment.'))
return redirect(self.get_success_url())
def get_object(self, queryset=None):
return self.request.event
def get(self, *args, **kwargs):
return HttpResponseNotAllowed(['POST'])
def form_valid(self, form):
form.save()
self.request.event.log_action('pretix.event.comment', user=self.request.user, data={
'new_comment': form.cleaned_data.get('comment')
})
return render(
self.request,
'pretixcontrol/event/dashboard_partial_comment.html',
)
def get_success_url(self) -> str:
return reverse('control:event.index', kwargs={
@@ -1568,7 +1566,7 @@ class WidgetSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
return ctx
class QuickSetupView(EventPermissionRequiredMixin, FormView):
class QuickSetupView(FormView):
template_name = 'pretixcontrol/event/quick_setup.html'
permission = 'event.settings.general:write'
form_class = QuickSetupForm
+93 -23
View File
@@ -65,7 +65,6 @@ from pretix.api.serializers.item import (
ItemVariationSerializer,
)
from pretix.base.forms import I18nFormSet
from pretix.base.forms.questions import get_fake_attendee_questions
from pretix.base.models import (
CartPosition, Item, ItemCategory, ItemProgramTime, ItemVariation, LogEntry,
OrderPosition, Question, QuestionAnswer, QuestionOption, Quota,
@@ -427,7 +426,7 @@ def reorder_categories(request, organizer, event):
FakeQuestion = namedtuple(
'FakeQuestion', 'id question position required container_type'
'FakeQuestion', 'id question position required'
)
@@ -441,8 +440,85 @@ class QuestionList(ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
questions = []
questions = get_fake_attendee_questions(self.request.event.settings)
if self.request.event.settings.attendee_names_asked:
questions.append(
FakeQuestion(
id='attendee_name_parts',
question=_('Attendee name'),
position=self.request.event.settings.system_question_order.get(
'attendee_name_parts', 0
),
required=self.request.event.settings.attendee_names_required,
)
)
if self.request.event.settings.attendee_emails_asked:
questions.append(
FakeQuestion(
id='attendee_email',
question=_('Attendee email'),
position=self.request.event.settings.system_question_order.get(
'attendee_email', 0
),
required=self.request.event.settings.attendee_emails_required,
)
)
if self.request.event.settings.attendee_company_asked:
questions.append(
FakeQuestion(
id='company',
question=_('Company'),
position=self.request.event.settings.system_question_order.get(
'company', 0
),
required=self.request.event.settings.attendee_company_required,
)
)
if self.request.event.settings.attendee_addresses_asked:
questions.append(
FakeQuestion(
id='street',
question=_('Street'),
position=self.request.event.settings.system_question_order.get(
'street', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions.append(
FakeQuestion(
id='zipcode',
question=_('ZIP code'),
position=self.request.event.settings.system_question_order.get(
'zipcode', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions.append(
FakeQuestion(
id='city',
question=_('City'),
position=self.request.event.settings.system_question_order.get(
'city', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions.append(
FakeQuestion(
id='country',
question=_('Country'),
position=self.request.event.settings.system_question_order.get(
'country', 0
),
required=self.request.event.settings.attendee_addresses_required,
)
)
questions += list(ctx['questions'])
questions.sort(key=lambda q: q.position)
@@ -459,16 +535,14 @@ def reorder_questions(request, organizer, event):
except (JSONDecodeError, KeyError, ValueError):
return HttpResponseBadRequest("expected JSON: {ids:[]}")
qs = request.event.questions.filter(container_type=request.GET['container_type'])
# filter system_questions - normal questions are int/digit, system_questions strings
custom_question_ids = [i for i in ids if i.isdigit()]
input_questions = list(qs.filter(id__in=custom_question_ids))
input_questions = list(request.event.questions.filter(id__in=custom_question_ids))
if len(input_questions) != len(custom_question_ids):
raise Http404(_("Some of the provided object ids are invalid."))
if len(input_questions) != qs.count():
if len(input_questions) != request.event.questions.count():
raise Http404(_("Not all objects have been selected."))
for q in input_questions:
@@ -482,19 +556,18 @@ def reorder_questions(request, organizer, event):
}
)
if request.GET['container_type'] == Question.ContainerType.ORDERPOSITION:
system_question_order = {}
for s in ('attendee_name_parts', 'attendee_email', 'company', 'street', 'zipcode', 'city', 'country'):
if s in ids:
system_question_order[s] = ids.index(s)
else:
system_question_order[s] = -1
request.event.settings.system_question_order = system_question_order
request.event.log_action(
'pretix.event.settings', user=request.user, data={
'system_question_order': system_question_order,
}
)
system_question_order = {}
for s in ('attendee_name_parts', 'attendee_email', 'company', 'street', 'zipcode', 'city', 'country'):
if s in ids:
system_question_order[s] = ids.index(s)
else:
system_question_order[s] = -1
request.event.settings.system_question_order = system_question_order
request.event.log_action(
'pretix.event.settings', user=request.user, data={
'system_question_order': system_question_order,
}
)
return HttpResponse()
@@ -732,9 +805,6 @@ class QuestionCreate(EventPermissionRequiredMixin, QuestionMixin, CreateView):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = Question(event=self.request.event)
kwargs['instance'].container_type = self.request.GET.get('container_type', Question.ContainerType.ORDERPOSITION)
if kwargs['instance'].container_type not in Question.ContainerType.values:
raise PermissionDenied
return kwargs
def get_success_url(self) -> str:
+2 -2
View File
@@ -269,8 +269,8 @@ class MailSettingsSetupView(TemplateView):
if settings.MAIL_CUSTOM_SENDER_DMARC_REQUIRED:
dmarc_record = get_dmarc_record(hostname)
if not dmarc_record:
dmarc_warning = _(
'We did not find a DMARC record for your domain. This means that there is a very high chance '
spf_warning = _(
'We did not find DMARC record for your domain. This means that there is a very high chance '
'most of the emails will be rejected or marked as spam. You should update the DNS settings '
'of your domain.'
)
+2 -14
View File
@@ -52,13 +52,13 @@ from pretix.base.forms import SafeSessionWizardView
from pretix.base.i18n import language
from pretix.base.models import Event, EventMetaValue, Organizer, Quota, Team
from pretix.base.models.organizer import TeamQuerySet
from pretix.base.services.quotas import QuotaAvailability
from pretix.control.forms.event import (
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
)
from pretix.control.forms.filter import EventFilterForm
from pretix.control.permissions import OrganizerPermissionRequiredMixin
from pretix.control.views import PaginationMixin
from pretix.control.views.utils import prepare_quotas_for_boxes
class EventList(PaginationMixin, ListView):
@@ -117,19 +117,7 @@ class EventList(PaginationMixin, ListView):
s.first_quotas = s.first_quotas[:4]
quotas += list(s.first_quotas)
qa = QuotaAvailability(early_out=False)
for q in quotas:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = qa.results[q]
q.cached_availability_paid_orders = qa.count_paid_orders.get(q, 0)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
prepare_quotas_for_boxes(quotas)
return ctx
@cached_property
+14 -41
View File
@@ -81,7 +81,7 @@ from pretix.base.i18n import language
from pretix.base.models import (
CachedFile, CachedTicket, Checkin, GiftCard, Invoice, InvoiceAddress, Item,
ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
ScheduledEventExport, SeatCategoryMapping, generate_secret,
ScheduledEventExport, generate_secret,
)
from pretix.base.models.orders import (
CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund,
@@ -382,7 +382,7 @@ class OrderOverpaidRefundBulkActionView(BaseOrderBulkActionView):
'provider': refund.provider,
}, user=self.request.user)
payment.payment_provider.execute_refund(refund)
return bool(proposals)
return True
except (ValueError, PaymentException):
return False
@@ -564,11 +564,10 @@ class OrderDetail(OrderView):
})
ctx['display_locale'] = dict(settings.LANGUAGES)[self.object.locale or self.request.event.settings.locale]
pending_sum = self.order.pending_sum
ctx['overpaid'] = pending_sum * -1
ctx['overpaid'] = self.order.pending_sum * -1
ctx['download_buttons'] = self.download_buttons
ctx['payment_refund_sum'] = self.order.payment_refund_sum
ctx['pending_sum'] = pending_sum
ctx['pending_sum'] = self.order.pending_sum
ctx['uncancelled_invoice'] = self.order.invoices.exclude(
Exists(self.order.invoices.filter(refers=OuterRef('pk'), is_cancellation=True))
).exclude(is_cancellation=True).first()
@@ -602,9 +601,8 @@ class OrderDetail(OrderView):
).prefetch_related(
'item__questions', 'issued_gift_cards', 'owned_gift_cards', 'linked_media',
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
Prefetch('all_checkins', queryset=Checkin.all.select_related('list', 'gate').order_by('datetime')),
Prefetch('all_checkins', queryset=Checkin.all.select_related('list').order_by('datetime')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device').order_by('datetime')),
Prefetch('subevent', queryset=self.request.event.subevents.all()),
).order_by('positionid')
positions = []
@@ -1987,37 +1985,20 @@ class OrderChange(OrderView):
def fees(self):
fees = list(self.order.fees.all())
for f in fees:
f.form = OrderFeeChangeForm(
prefix='of-{}'.format(f.pk),
instance=f,
data=self.request.POST if self.request.method == "POST" else None
)
f.form = OrderFeeChangeForm(prefix='of-{}'.format(f.pk), instance=f,
data=self.request.POST if self.request.method == "POST" else None)
return fees
@cached_property
def positions(self):
positions = list(self.order.positions.select_related(
'item', 'item__tax_rule', 'used_membership', 'used_membership__membership_type', 'tax_rule',
'seat',
).prefetch_related(
Prefetch(
'subevent',
queryset=self.request.event.subevents.all(),
),
'granted_memberships',
'issued_gift_cards',
'addons',
).annotate(
_seat_allowed=Exists(SeatCategoryMapping.objects.filter(subevent=OuterRef("subevent"), product=OuterRef("item")))
))
'seat', 'subevent',
).prefetch_related('granted_memberships', 'issued_gift_cards'))
for p in positions:
p.form = OrderPositionChangeForm(
prefix='op-{}'.format(p.pk),
instance=p,
items=self.items,
initial={'seat': p.seat.seat_guid if p.seat else None},
data=self.request.POST if self.request.method == "POST" else None
)
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p, items=self.items,
initial={'seat': p.seat.seat_guid if p.seat else None},
data=self.request.POST if self.request.method == "POST" else None)
return positions
def get_context_data(self, **kwargs):
@@ -2226,10 +2207,6 @@ class OrderModifyInformation(OrderQuestionsViewMixin, OrderView):
only_user_visible = False
all_optional = True
@property
def order_question_container(self):
return self.order
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['other_form'] = self.other_form
@@ -2591,11 +2568,7 @@ class AnswerDownload(EventPermissionRequiredMixin, OrderViewMixin, ListView):
answid = kwargs.get('answer')
token = request.GET.get('token', '')
answer = get_object_or_404(
QuestionAnswer,
Q(orderposition__order=self.order) | Q(order=self.order),
id=answid
)
answer = get_object_or_404(QuestionAnswer, orderposition__order=self.order, id=answid)
if not answer.file:
raise Http404()
if not check_token(request, answer, token):
@@ -2605,7 +2578,7 @@ class AnswerDownload(EventPermissionRequiredMixin, OrderViewMixin, ListView):
resp = FileResponse(answer.file, content_type=ftype or 'application/binary')
resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}"'.format(
self.request.event.slug.upper(), self.order.code,
answer.orderposition.positionid if answer.orderposition else '',
answer.orderposition.positionid,
os.path.basename(answer.file.name).split('.', 1)[1]
)
return resp
+11 -9
View File
@@ -108,9 +108,6 @@ from pretix.base.services.export import (
init_organizer_exporters, multiexport, scheduled_organizer_export,
)
from pretix.base.services.mail import mail, prefix_subject
from pretix.base.services.placeholders import (
prepare_sample_context_for_preview,
)
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.base.views.tasks import AsyncAction
from pretix.control.forms.exports import ScheduledOrganizerExportForm
@@ -348,11 +345,16 @@ class MailSettingsPreview(OrganizerPermissionRequiredMixin, View):
# get all supported placeholders with dummy values
def placeholders(self, item):
ctx = prepare_sample_context_for_preview(
MailSettingsForm(obj=self.request.organizer)._get_sample_context(
MailSettingsForm.base_context[item]
)
)
ctx = {}
for p, s in MailSettingsForm(obj=self.request.organizer)._get_sample_context(
MailSettingsForm.base_context[item]).items():
if s.strip().startswith('*'):
ctx[p] = s
else:
ctx[p] = '<span class="placeholder" title="{}">{}</span>'.format(
_('This value will be replaced based on dynamic parameters.'),
s
)
return self.SafeDict(ctx)
def post(self, request, *args, **kwargs):
@@ -1208,7 +1210,7 @@ class DeviceQueryMixin:
def get_queryset(self):
qs = self.request.organizer.devices.prefetch_related(
'limit_events', 'gate',
).select_related('last_seen').order_by('revoked', '-device_id')
).order_by('revoked', '-device_id')
if 'device' in self.request_data and '__ALL' not in self.request_data:
qs = qs.filter(
+15 -14
View File
@@ -69,6 +69,7 @@ from pretix.base.models.orders import CancellationRequest
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.base.services import tickets
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.timeline import timeline_for_event
from pretix.base.views.tasks import AsyncFormView
from pretix.control.forms.checkin import SimpleCheckinListForm
from pretix.control.forms.filter import SubEventFilterForm
@@ -83,6 +84,7 @@ from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.signals import subevent_forms
from pretix.control.views import PaginationMixin
from pretix.control.views.event import MetaDataEditorMixin
from pretix.control.views.utils import prepare_quotas_for_boxes
from pretix.helpers import GroupConcat
from pretix.helpers.compat import CompatDeleteView
from pretix.helpers.i18n import get_format_without_seconds
@@ -144,19 +146,7 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
s.first_quotas = s.first_quotas[:4]
quotas += list(s.first_quotas)
qa = QuotaAvailability(early_out=False)
for q in quotas:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = qa.results[q]
q.cached_availability_paid_orders = qa.count_paid_orders.get(q, 0)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
prepare_quotas_for_boxes(quotas)
return ctx
@@ -431,7 +421,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
days = (self.copy_from.date_from.astimezone(tz).date() - value.astimezone(tz).date()).days
return RelativeDateWrapper(RelativeDate(
days=abs(days),
base_date_name='event__date_from',
base_date_name='date_from',
time=value.astimezone(tz).time(),
minutes=None,
is_after=days < 0,
@@ -566,6 +556,17 @@ class SubEventDetail(EventPermissionRequiredMixin, DetailView):
for quota in ctx["quotas"]:
quota.cached_avail = qa.results[quota]
ctx['timeline'] = [
{
'date': t.datetime.astimezone(self.request.event.timezone).date(),
'entry': t,
'time': t.datetime.astimezone(self.request.event.timezone)
}
for t in timeline_for_event(self.request.event, self.object)
]
ctx['today'] = now().astimezone(self.request.event.timezone).date()
ctx['nearly_now'] = now().astimezone(self.request.event.timezone) - timedelta(seconds=20)
return super().get_context_data(
**kwargs,
**ctx,
-15
View File
@@ -46,7 +46,6 @@ from django.contrib.auth import update_session_auth_hash
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import BadRequest, PermissionDenied
from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.crypto import get_random_string
@@ -96,20 +95,6 @@ class RecentAuthenticationRequiredMixin:
def dispatch(self, request, *args, **kwargs):
tdelta = time.time() - request.session.get('pretix_auth_login_time', 0)
if tdelta > self.max_time:
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
# It's not useful to return a 302 redirect on a XMLHttpRequest request, because
# the XMLHttpRequest is unable to detect redirects.
return HttpResponse(
"Authentication required",
status=401,
headers={
# Appending ?next= is handled by client, because it should be the top-level context url,
# not the URL called in the background
"X-Login-Url": reverse('control:user.reauth')
}
)
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
return super().dispatch(request, *args, **kwargs)
+30
View File
@@ -0,0 +1,30 @@
from pretix.base.services.quotas import QuotaAvailability
def prepare_quotas_for_boxes(quotas):
qa = QuotaAvailability(early_out=False)
for q in quotas:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = qa.results[q]
q.cached_availability_paid_orders = qa.count_paid_orders.get(q, 0)
q.used = (
qa.count_paid_orders.get(q, 0) +
qa.count_pending_orders.get(q, 0) +
qa.count_exited_orders.get(q, 0) +
qa.count_vouchers.get(q, 0) +
qa.count_waitinglist.get(q, 0) +
qa.count_cart.get(q, 0)
)
if q.size is not None:
other_blocked = q.size - q.cached_availability_paid_orders - q.cached_avail[1]
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
q.percent_other = min(
100,
round(other_blocked / q.size * 100) if q.size > 0 else 100
)
-3
View File
@@ -53,7 +53,6 @@ from django.shortcuts import redirect, render
from django.urls import resolve, reverse
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
@@ -351,8 +350,6 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
return super().post(request, *args, **kwargs)
def get_success_url(self) -> str:
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
return self.request.GET.get("next")
return reverse('control:event.vouchers', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
-28
View File
@@ -22,7 +22,6 @@
import contextlib
from django.conf import settings
from django.contrib.postgres.indexes import BrinIndex
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.db import connection, transaction
from django.db.models import (
@@ -286,30 +285,3 @@ def get_deterministic_ordering(model, ordering):
# on the primary key to provide total ordering.
ordering.append("-pk")
return ordering
@contextlib.contextmanager
def conditional_atomic(do_atomic, **kwargs):
if do_atomic:
with transaction.atomic(**kwargs):
yield
else:
yield
class IgnoreOnSQLiteMixin:
# Mixin to allow defining PostgreSQL-specific indexes that will just not be created
# on SQLite. SQLite is supported for testing only anyways!
def create_sql(self, model, schema_editor, *args, **kwargs):
if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
return ""
return super().create_sql(model, schema_editor, *args, **kwargs)
def remove_sql(self, model, schema_editor, **kwargs):
if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
return ""
return super().remove_sql(model, schema_editor, **kwargs)
class BrinIndexIgnoredOnSQLite(IgnoreOnSQLiteMixin, BrinIndex):
pass
+2 -6
View File
@@ -24,21 +24,17 @@ import hashlib
from django.core.signing import BadSignature, TimestampSigner
class SafeDownloadSigner(TimestampSigner):
pass
def get_token(request, answer):
if not request.session.session_key:
request.session.create()
payload = '{}:{}'.format(request.session.session_key, answer.pk)
signer = SafeDownloadSigner()
signer = TimestampSigner()
return signer.sign(hashlib.sha1(payload.encode()).hexdigest())
def check_token(request, answer, token):
payload = hashlib.sha1('{}:{}'.format(request.session.session_key, answer.pk).encode()).hexdigest()
signer = SafeDownloadSigner()
signer = TimestampSigner()
try:
return payload == signer.unsign(token, max_age=3600 * 24)
except BadSignature:
+3 -3
View File
@@ -28,7 +28,7 @@ from django.contrib.auth import login as auth_login
from django.contrib.gis import geoip2
from django.core.cache import cache
from django.utils.formats import date_format
from django.utils.timezone import localtime, now, override
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_countries.fields import Country
from geoip2.errors import AddressNotFoundError
@@ -167,13 +167,13 @@ def handle_login_source(user, request):
})
if user.known_login_sources.count() > 1:
# Do not send on first login or first login after introduction of this feature:
with language(user.locale), override(user.timezone):
with language(user.locale):
mail(
user.email,
_('New sign-in to your account'),
'pretixcontrol/email/login_notice.txt',
{
'when': date_format(localtime(src.last_seen), 'DATETIME_FORMAT'),
'when': date_format(src.last_seen, 'DATETIME_FORMAT'),
'agent': src.agent_type,
'os': src.os_type,
# ua-parser returns "Other" for unidentified desktop devices.
+222 -367
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
"PO-Revision-Date: 2026-07-15 14:20+0000\n"
"Last-Translator: Llewelyn Barkhuizen <llewelyn@akademia.ac.za>\n"
"Language-Team: Afrikaans <https://translate.pretix.eu/projects/pretix/pretix/"
@@ -19,6 +19,38 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.7.1\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/_base_settings.py
msgid "English"
msgstr "Engels"
@@ -1169,19 +1201,6 @@ msgstr ""
msgid "Download a spreadsheet of all currently registered customer accounts."
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/permissions.py
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/exporters/customers.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/customers.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
@@ -1314,66 +1333,6 @@ msgstr ""
msgid "No"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Purchase ticket"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
#: pretix/base/models/memberships.py pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Start time"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "End time"
msgstr ""
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
msgctxt "export_category"
msgid "Invoices"
@@ -2201,6 +2160,30 @@ msgstr ""
msgid "Position ID"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/forms/questions.py pretix/base/models/memberships.py
#: pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/modelimport_orders.py pretix/base/modelimport_vouchers.py
#: pretix/base/models/items.py pretix/base/models/vouchers.py
@@ -2592,6 +2575,12 @@ msgstr ""
msgid "Gift card code"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/organizer.py
#: pretix/control/forms/event.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/email_setup.html
@@ -2705,7 +2694,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/plugins/reports/exporters.py pretix/plugins/sendmail/forms.py
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_giftcards.html
msgid "Expired"
@@ -2871,7 +2860,7 @@ msgid "This combination of credentials is not known to our system."
msgstr ""
#: pretix/base/forms/auth.py pretix/base/forms/user.py
#: pretix/control/views/user.py pretix/presale/forms/customer.py
#: pretix/presale/forms/customer.py
msgid "For security reasons, please wait 5 minutes before you try again."
msgstr ""
@@ -3794,7 +3783,6 @@ msgstr ""
#: pretix/base/modelimport_vouchers.py pretix/base/models/vouchers.py
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/views/vouchers.py
msgid "Price effect"
@@ -4412,8 +4400,8 @@ msgstr ""
#: pretix/base/models/event.py
msgid ""
"This will be used to let users know if the event is in a different timezone, "
"and to let us calculate the local time of a user."
"This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times."
msgstr ""
#: pretix/base/models/event.py pretix/base/models/organizer.py
@@ -5708,6 +5696,23 @@ msgstr ""
msgid "Number of times this membership can be used in a purchase."
msgstr ""
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/models/orders.py
msgid "pending"
msgstr ""
@@ -6486,7 +6491,7 @@ msgstr ""
msgid "It is currently not possible to create vouchers for add-on products."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"You need to select a specific product or quota if this voucher should "
"reserve tickets."
@@ -6505,7 +6510,7 @@ msgid ""
"usages."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"If you want this voucher to block quota, you need to select a specific date."
msgstr ""
@@ -6974,12 +6979,6 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
"usable."
msgstr ""
#: pretix/base/pdf.py
msgid "Ticket code (barcode content)"
msgstr ""
@@ -7536,6 +7535,11 @@ msgid ""
"permissions."
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
msgid "Devices"
msgstr ""
@@ -8661,12 +8665,6 @@ msgid ""
"card."
msgstr ""
#: pretix/base/services/orders.py
msgid ""
"You cannot change the ticket secret of a position that has been used to "
"issue a gift card."
msgstr ""
#: pretix/base/services/orders.py
#, python-brace-format
msgid ""
@@ -10199,11 +10197,11 @@ msgstr ""
msgid "We'll show this publicly to allow attendees to contact you."
msgstr ""
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid "Contact URL"
msgstr ""
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid ""
"If you set this, the footer contact link will point here instead of using "
"the email address above. Please note that you still need to add a contact "
@@ -11684,7 +11682,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
@@ -12880,7 +12877,7 @@ msgstr ""
msgid "Valid orders"
msgstr ""
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Paid (or canceled with paid fee)"
msgstr ""
@@ -12904,7 +12901,7 @@ msgstr ""
msgid "Cancellations"
msgstr ""
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Canceled (fully)"
msgstr ""
@@ -13079,22 +13076,6 @@ msgstr ""
msgid "Inactive"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
msgid "Date from"
@@ -13132,6 +13113,13 @@ msgstr ""
msgid "not yet activated"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Has no memberships"
msgstr ""
@@ -13359,36 +13347,6 @@ msgstr ""
msgid "Search email address or subject"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/forms/filter.py
msgid "All sources"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Team actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Customer actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Device actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "User email"
msgstr ""
#: pretix/control/forms/filter.py pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/forms/global_settings.py
msgid "Additional footer text"
msgstr ""
@@ -14566,7 +14524,6 @@ msgstr ""
#: pretix/control/forms/renderers.py
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgctxt "form_bulk"
msgid "change"
msgstr ""
@@ -14680,53 +14637,6 @@ msgid ""
"should show them."
msgstr ""
#: pretix/control/forms/vouchers.py
#, python-format
msgid ""
"You cannot reduce the maximum number of redemptions to %(max_usages)s, "
"because at least one of the selected vouchers has already been redeemed "
"%(max_redeemed)s times."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"You cannot create a voucher that allows selection of a quota but has no date "
"selected."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "The selected quota does not match the selected subevent."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "There is no sufficient quota available to perform this change."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the maximum number of usages in bulk is not supported if any of the "
"selected vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgctxt "subevent"
msgid ""
"Changing the date in bulk is not supported if any of the selected vouchers "
"is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the product to a quota is not supported if any of the selected "
"vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"This change cannot be completed because not all assigned seats of the "
"vouchers are still available"
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "Codes"
msgstr ""
@@ -16432,6 +16342,10 @@ msgstr ""
msgid "Account history"
msgstr ""
#: pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
msgid "Admin sessions"
@@ -16700,7 +16614,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/users/create.html
#: pretix/control/templates/pretixcontrol/users/form.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/add.html
@@ -17118,8 +17031,8 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/checkin/checkins.html
#: pretix/control/templates/pretixcontrol/checkin/index.html
#: pretix/control/templates/pretixcontrol/checkin/lists.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#: pretix/control/templates/pretixcontrol/items/question.html
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/orders/index.html
@@ -17130,6 +17043,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/index.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
#: pretix/control/templates/pretixcontrol/organizers/teams.html
@@ -17264,7 +17178,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
msgid "Select all results on other pages as well"
msgstr ""
@@ -17820,22 +17733,6 @@ msgstr ""
msgid "We found an SPF record on your domain that includes this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DKIM record should be set up as a CNAME record like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DKIM record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DMARC record could look like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DMARC record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Verification"
msgstr ""
@@ -17996,94 +17893,6 @@ msgid ""
"undeletable data, such as orders not performed in test mode."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/delete.html
msgid ""
"This operation will destroy your event including all configuration, "
@@ -18198,6 +18007,69 @@ msgstr ""
msgid "Create QR code"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Update comment"
@@ -18372,6 +18244,44 @@ msgstr ""
msgid "Enable test mode"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "All actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Team actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Customer actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
@@ -19270,20 +19180,6 @@ msgstr ""
msgid "More quotas"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
msgid "Open event dashboard"
@@ -19294,10 +19190,6 @@ msgctxt "typography"
msgid "The quick brown fox jumps over the lazy dog."
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
msgid "Specific object selected"
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_quota_box.html
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html
msgid "Quota:"
@@ -20312,7 +20204,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, python-format
msgid "%(number)s selected"
msgstr ""
@@ -20399,7 +20290,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
msgid "Edit selected"
msgstr ""
@@ -20696,13 +20586,6 @@ msgid ""
"the customer account."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/views/orders.py
msgid ""
"Ticket secrets of order positions that have been used to issue a gift card "
"can not be changed. Only the link will be changed in this case."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
msgid ""
"Removing or splitting this position will also remove or split all add-ons to "
@@ -21115,6 +20998,11 @@ msgstr ""
msgid "Create a refund"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Cancel transfer"
msgstr ""
@@ -22191,7 +22079,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Advanced settings"
msgstr ""
@@ -24348,13 +24235,11 @@ msgid "Copy codes"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Voucher details"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid ""
"If you choose \"any product\" for a specific quota and choose to reserve "
@@ -24362,10 +24247,6 @@ msgid ""
"voucher holder if another quota associated with the product is sold out!"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgid "Change multiple vouchers"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Delete voucher"
@@ -24705,8 +24586,7 @@ msgstr ""
#, python-brace-format
msgid ""
"You cannot accept the invitation for \"{}\" as you already are part of this "
"team. If you want to add a different user or create a new account, log out "
"and click the invitation link again."
"team."
msgstr ""
#: pretix/control/views/auth.py
@@ -25309,30 +25189,6 @@ msgid ""
"record."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We could not find a CNAME record pointing to our DKIM key for domain you are "
"trying to use. This means that there is a very high change most of the "
"emails will be rejected or marked as spam. We strongly recommend setting up "
"DKIM through a CNAME record. You can do so through the DNS settings at the "
"provider you registered your domain with."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We found a CNAME record for a DKIM key, but it is not pointing to the right "
"location. This means that there is a very high chance most of the emails "
"will be rejected or marked as spam. You should update the DNS settings of "
"your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We did not find DMARC record for your domain. This means that there is a "
"very high chance most of the emails will be rejected or marked as spam. You "
"should update the DNS settings of your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid "The verification code was incorrect, please try again."
msgstr ""
@@ -26390,7 +26246,6 @@ msgid "Macao"
msgstr ""
#: pretix/helpers/daterange.py
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html
#: pretix/presale/templates/pretixpresale/fragment_calendar.html
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html
#: pretix/presale/templates/pretixpresale/organizers/index.html
@@ -27578,6 +27433,11 @@ msgstr ""
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "<a target=\"_blank\" rel=\"noopener\" href=\"{docs_url}\">{text}</a>"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
@@ -28212,13 +28072,8 @@ msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"Redirection will only be allowed to URLs that start with one of these "
"prefixes. Enter one allowed URL prefix per line. URL prefixes must include a "
"slash after the hostname."
msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"All values must be URLs that include at last one slash after the hostname."
"prefixes. Enter one or more allowed URL prefix per line. URL prefixes must "
"include a slash after the hostname."
msgstr ""
#: pretix/plugins/sendmail/apps.py
+222 -367
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
@@ -17,6 +17,38 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/_base_settings.py
msgid "English"
msgstr ""
@@ -1167,19 +1199,6 @@ msgstr ""
msgid "Download a spreadsheet of all currently registered customer accounts."
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/permissions.py
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/exporters/customers.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/customers.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
@@ -1312,66 +1331,6 @@ msgstr ""
msgid "No"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Purchase ticket"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
#: pretix/base/models/memberships.py pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Start time"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "End time"
msgstr ""
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
msgctxt "export_category"
msgid "Invoices"
@@ -2199,6 +2158,30 @@ msgstr ""
msgid "Position ID"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/forms/questions.py pretix/base/models/memberships.py
#: pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/modelimport_orders.py pretix/base/modelimport_vouchers.py
#: pretix/base/models/items.py pretix/base/models/vouchers.py
@@ -2590,6 +2573,12 @@ msgstr ""
msgid "Gift card code"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/organizer.py
#: pretix/control/forms/event.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/email_setup.html
@@ -2703,7 +2692,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/plugins/reports/exporters.py pretix/plugins/sendmail/forms.py
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_giftcards.html
msgid "Expired"
@@ -2869,7 +2858,7 @@ msgid "This combination of credentials is not known to our system."
msgstr ""
#: pretix/base/forms/auth.py pretix/base/forms/user.py
#: pretix/control/views/user.py pretix/presale/forms/customer.py
#: pretix/presale/forms/customer.py
msgid "For security reasons, please wait 5 minutes before you try again."
msgstr ""
@@ -3792,7 +3781,6 @@ msgstr ""
#: pretix/base/modelimport_vouchers.py pretix/base/models/vouchers.py
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/views/vouchers.py
msgid "Price effect"
@@ -4410,8 +4398,8 @@ msgstr ""
#: pretix/base/models/event.py
msgid ""
"This will be used to let users know if the event is in a different timezone, "
"and to let us calculate the local time of a user."
"This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times."
msgstr ""
#: pretix/base/models/event.py pretix/base/models/organizer.py
@@ -5706,6 +5694,23 @@ msgstr ""
msgid "Number of times this membership can be used in a purchase."
msgstr ""
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/models/orders.py
msgid "pending"
msgstr ""
@@ -6484,7 +6489,7 @@ msgstr ""
msgid "It is currently not possible to create vouchers for add-on products."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"You need to select a specific product or quota if this voucher should "
"reserve tickets."
@@ -6503,7 +6508,7 @@ msgid ""
"usages."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"If you want this voucher to block quota, you need to select a specific date."
msgstr ""
@@ -6972,12 +6977,6 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
"usable."
msgstr ""
#: pretix/base/pdf.py
msgid "Ticket code (barcode content)"
msgstr ""
@@ -7534,6 +7533,11 @@ msgid ""
"permissions."
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
msgid "Devices"
msgstr ""
@@ -8659,12 +8663,6 @@ msgid ""
"card."
msgstr ""
#: pretix/base/services/orders.py
msgid ""
"You cannot change the ticket secret of a position that has been used to "
"issue a gift card."
msgstr ""
#: pretix/base/services/orders.py
#, python-brace-format
msgid ""
@@ -10197,11 +10195,11 @@ msgstr ""
msgid "We'll show this publicly to allow attendees to contact you."
msgstr ""
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid "Contact URL"
msgstr ""
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid ""
"If you set this, the footer contact link will point here instead of using "
"the email address above. Please note that you still need to add a contact "
@@ -11682,7 +11680,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
@@ -12878,7 +12875,7 @@ msgstr ""
msgid "Valid orders"
msgstr ""
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Paid (or canceled with paid fee)"
msgstr ""
@@ -12902,7 +12899,7 @@ msgstr ""
msgid "Cancellations"
msgstr ""
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Canceled (fully)"
msgstr ""
@@ -13077,22 +13074,6 @@ msgstr ""
msgid "Inactive"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
msgid "Date from"
@@ -13130,6 +13111,13 @@ msgstr ""
msgid "not yet activated"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Has no memberships"
msgstr ""
@@ -13357,36 +13345,6 @@ msgstr ""
msgid "Search email address or subject"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/forms/filter.py
msgid "All sources"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Team actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Customer actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Device actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "User email"
msgstr ""
#: pretix/control/forms/filter.py pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/forms/global_settings.py
msgid "Additional footer text"
msgstr ""
@@ -14564,7 +14522,6 @@ msgstr ""
#: pretix/control/forms/renderers.py
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgctxt "form_bulk"
msgid "change"
msgstr ""
@@ -14678,53 +14635,6 @@ msgid ""
"should show them."
msgstr ""
#: pretix/control/forms/vouchers.py
#, python-format
msgid ""
"You cannot reduce the maximum number of redemptions to %(max_usages)s, "
"because at least one of the selected vouchers has already been redeemed "
"%(max_redeemed)s times."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"You cannot create a voucher that allows selection of a quota but has no date "
"selected."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "The selected quota does not match the selected subevent."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "There is no sufficient quota available to perform this change."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the maximum number of usages in bulk is not supported if any of the "
"selected vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgctxt "subevent"
msgid ""
"Changing the date in bulk is not supported if any of the selected vouchers "
"is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the product to a quota is not supported if any of the selected "
"vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"This change cannot be completed because not all assigned seats of the "
"vouchers are still available"
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "Codes"
msgstr ""
@@ -16430,6 +16340,10 @@ msgstr ""
msgid "Account history"
msgstr ""
#: pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
msgid "Admin sessions"
@@ -16698,7 +16612,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/users/create.html
#: pretix/control/templates/pretixcontrol/users/form.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/add.html
@@ -17116,8 +17029,8 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/checkin/checkins.html
#: pretix/control/templates/pretixcontrol/checkin/index.html
#: pretix/control/templates/pretixcontrol/checkin/lists.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#: pretix/control/templates/pretixcontrol/items/question.html
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/orders/index.html
@@ -17128,6 +17041,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/index.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
#: pretix/control/templates/pretixcontrol/organizers/teams.html
@@ -17262,7 +17176,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
msgid "Select all results on other pages as well"
msgstr ""
@@ -17818,22 +17731,6 @@ msgstr ""
msgid "We found an SPF record on your domain that includes this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DKIM record should be set up as a CNAME record like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DKIM record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DMARC record could look like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DMARC record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Verification"
msgstr ""
@@ -17994,94 +17891,6 @@ msgid ""
"undeletable data, such as orders not performed in test mode."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/delete.html
msgid ""
"This operation will destroy your event including all configuration, "
@@ -18196,6 +18005,69 @@ msgstr ""
msgid "Create QR code"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Update comment"
@@ -18370,6 +18242,44 @@ msgstr ""
msgid "Enable test mode"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "All actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Team actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Customer actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
@@ -19268,20 +19178,6 @@ msgstr ""
msgid "More quotas"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
msgid "Open event dashboard"
@@ -19292,10 +19188,6 @@ msgctxt "typography"
msgid "The quick brown fox jumps over the lazy dog."
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
msgid "Specific object selected"
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_quota_box.html
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html
msgid "Quota:"
@@ -20310,7 +20202,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, python-format
msgid "%(number)s selected"
msgstr ""
@@ -20397,7 +20288,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
msgid "Edit selected"
msgstr ""
@@ -20694,13 +20584,6 @@ msgid ""
"the customer account."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/views/orders.py
msgid ""
"Ticket secrets of order positions that have been used to issue a gift card "
"can not be changed. Only the link will be changed in this case."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
msgid ""
"Removing or splitting this position will also remove or split all add-ons to "
@@ -21113,6 +20996,11 @@ msgstr ""
msgid "Create a refund"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Cancel transfer"
msgstr ""
@@ -22189,7 +22077,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Advanced settings"
msgstr ""
@@ -24346,13 +24233,11 @@ msgid "Copy codes"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Voucher details"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid ""
"If you choose \"any product\" for a specific quota and choose to reserve "
@@ -24360,10 +24245,6 @@ msgid ""
"voucher holder if another quota associated with the product is sold out!"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgid "Change multiple vouchers"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Delete voucher"
@@ -24703,8 +24584,7 @@ msgstr ""
#, python-brace-format
msgid ""
"You cannot accept the invitation for \"{}\" as you already are part of this "
"team. If you want to add a different user or create a new account, log out "
"and click the invitation link again."
"team."
msgstr ""
#: pretix/control/views/auth.py
@@ -25307,30 +25187,6 @@ msgid ""
"record."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We could not find a CNAME record pointing to our DKIM key for domain you are "
"trying to use. This means that there is a very high change most of the "
"emails will be rejected or marked as spam. We strongly recommend setting up "
"DKIM through a CNAME record. You can do so through the DNS settings at the "
"provider you registered your domain with."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We found a CNAME record for a DKIM key, but it is not pointing to the right "
"location. This means that there is a very high chance most of the emails "
"will be rejected or marked as spam. You should update the DNS settings of "
"your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We did not find DMARC record for your domain. This means that there is a "
"very high chance most of the emails will be rejected or marked as spam. You "
"should update the DNS settings of your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid "The verification code was incorrect, please try again."
msgstr ""
@@ -26388,7 +26244,6 @@ msgid "Macao"
msgstr ""
#: pretix/helpers/daterange.py
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html
#: pretix/presale/templates/pretixpresale/fragment_calendar.html
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html
#: pretix/presale/templates/pretixpresale/organizers/index.html
@@ -27576,6 +27431,11 @@ msgstr ""
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "<a target=\"_blank\" rel=\"noopener\" href=\"{docs_url}\">{text}</a>"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
@@ -28210,13 +28070,8 @@ msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"Redirection will only be allowed to URLs that start with one of these "
"prefixes. Enter one allowed URL prefix per line. URL prefixes must include a "
"slash after the hostname."
msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"All values must be URLs that include at last one slash after the hostname."
"prefixes. Enter one or more allowed URL prefix per line. URL prefixes must "
"include a slash after the hostname."
msgstr ""
#: pretix/plugins/sendmail/apps.py
File diff suppressed because it is too large Load Diff
+222 -367
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
"PO-Revision-Date: 2024-12-11 01:00+0000\n"
"Last-Translator: Neriman Memmedli <nerim4n@pm.me>\n"
"Language-Team: Azerbaijani <https://translate.pretix.eu/projects/pretix/"
@@ -19,6 +19,38 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.8.4\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/_base_settings.py
msgid "English"
msgstr "İngiliscə"
@@ -1169,19 +1201,6 @@ msgstr ""
msgid "Download a spreadsheet of all currently registered customer accounts."
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/permissions.py
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/exporters/customers.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/customers.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
@@ -1314,66 +1333,6 @@ msgstr ""
msgid "No"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Purchase ticket"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
#: pretix/base/models/memberships.py pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Start time"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "End time"
msgstr ""
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
msgctxt "export_category"
msgid "Invoices"
@@ -2201,6 +2160,30 @@ msgstr ""
msgid "Position ID"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/forms/questions.py pretix/base/models/memberships.py
#: pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/modelimport_orders.py pretix/base/modelimport_vouchers.py
#: pretix/base/models/items.py pretix/base/models/vouchers.py
@@ -2592,6 +2575,12 @@ msgstr ""
msgid "Gift card code"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/organizer.py
#: pretix/control/forms/event.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/email_setup.html
@@ -2705,7 +2694,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/plugins/reports/exporters.py pretix/plugins/sendmail/forms.py
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_giftcards.html
msgid "Expired"
@@ -2871,7 +2860,7 @@ msgid "This combination of credentials is not known to our system."
msgstr ""
#: pretix/base/forms/auth.py pretix/base/forms/user.py
#: pretix/control/views/user.py pretix/presale/forms/customer.py
#: pretix/presale/forms/customer.py
msgid "For security reasons, please wait 5 minutes before you try again."
msgstr ""
@@ -3794,7 +3783,6 @@ msgstr ""
#: pretix/base/modelimport_vouchers.py pretix/base/models/vouchers.py
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/views/vouchers.py
msgid "Price effect"
@@ -4412,8 +4400,8 @@ msgstr ""
#: pretix/base/models/event.py
msgid ""
"This will be used to let users know if the event is in a different timezone, "
"and to let us calculate the local time of a user."
"This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times."
msgstr ""
#: pretix/base/models/event.py pretix/base/models/organizer.py
@@ -5708,6 +5696,23 @@ msgstr ""
msgid "Number of times this membership can be used in a purchase."
msgstr ""
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/models/orders.py
msgid "pending"
msgstr ""
@@ -6486,7 +6491,7 @@ msgstr ""
msgid "It is currently not possible to create vouchers for add-on products."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"You need to select a specific product or quota if this voucher should "
"reserve tickets."
@@ -6505,7 +6510,7 @@ msgid ""
"usages."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"If you want this voucher to block quota, you need to select a specific date."
msgstr ""
@@ -6974,12 +6979,6 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
"usable."
msgstr ""
#: pretix/base/pdf.py
msgid "Ticket code (barcode content)"
msgstr ""
@@ -7536,6 +7535,11 @@ msgid ""
"permissions."
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
msgid "Devices"
msgstr ""
@@ -8661,12 +8665,6 @@ msgid ""
"card."
msgstr ""
#: pretix/base/services/orders.py
msgid ""
"You cannot change the ticket secret of a position that has been used to "
"issue a gift card."
msgstr ""
#: pretix/base/services/orders.py
#, python-brace-format
msgid ""
@@ -10199,11 +10197,11 @@ msgstr ""
msgid "We'll show this publicly to allow attendees to contact you."
msgstr ""
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid "Contact URL"
msgstr ""
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid ""
"If you set this, the footer contact link will point here instead of using "
"the email address above. Please note that you still need to add a contact "
@@ -11684,7 +11682,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
@@ -12880,7 +12877,7 @@ msgstr ""
msgid "Valid orders"
msgstr ""
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Paid (or canceled with paid fee)"
msgstr ""
@@ -12904,7 +12901,7 @@ msgstr ""
msgid "Cancellations"
msgstr ""
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Canceled (fully)"
msgstr ""
@@ -13079,22 +13076,6 @@ msgstr ""
msgid "Inactive"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
msgid "Date from"
@@ -13132,6 +13113,13 @@ msgstr ""
msgid "not yet activated"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Has no memberships"
msgstr ""
@@ -13359,36 +13347,6 @@ msgstr ""
msgid "Search email address or subject"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/forms/filter.py
msgid "All sources"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Team actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Customer actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Device actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "User email"
msgstr ""
#: pretix/control/forms/filter.py pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/forms/global_settings.py
msgid "Additional footer text"
msgstr ""
@@ -14566,7 +14524,6 @@ msgstr ""
#: pretix/control/forms/renderers.py
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgctxt "form_bulk"
msgid "change"
msgstr ""
@@ -14680,53 +14637,6 @@ msgid ""
"should show them."
msgstr ""
#: pretix/control/forms/vouchers.py
#, python-format
msgid ""
"You cannot reduce the maximum number of redemptions to %(max_usages)s, "
"because at least one of the selected vouchers has already been redeemed "
"%(max_redeemed)s times."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"You cannot create a voucher that allows selection of a quota but has no date "
"selected."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "The selected quota does not match the selected subevent."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "There is no sufficient quota available to perform this change."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the maximum number of usages in bulk is not supported if any of the "
"selected vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgctxt "subevent"
msgid ""
"Changing the date in bulk is not supported if any of the selected vouchers "
"is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the product to a quota is not supported if any of the selected "
"vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"This change cannot be completed because not all assigned seats of the "
"vouchers are still available"
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "Codes"
msgstr ""
@@ -16432,6 +16342,10 @@ msgstr ""
msgid "Account history"
msgstr ""
#: pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
msgid "Admin sessions"
@@ -16700,7 +16614,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/users/create.html
#: pretix/control/templates/pretixcontrol/users/form.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/add.html
@@ -17118,8 +17031,8 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/checkin/checkins.html
#: pretix/control/templates/pretixcontrol/checkin/index.html
#: pretix/control/templates/pretixcontrol/checkin/lists.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#: pretix/control/templates/pretixcontrol/items/question.html
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/orders/index.html
@@ -17130,6 +17043,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/index.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
#: pretix/control/templates/pretixcontrol/organizers/teams.html
@@ -17264,7 +17178,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
msgid "Select all results on other pages as well"
msgstr ""
@@ -17820,22 +17733,6 @@ msgstr ""
msgid "We found an SPF record on your domain that includes this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DKIM record should be set up as a CNAME record like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DKIM record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DMARC record could look like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DMARC record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Verification"
msgstr ""
@@ -17996,94 +17893,6 @@ msgid ""
"undeletable data, such as orders not performed in test mode."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/delete.html
msgid ""
"This operation will destroy your event including all configuration, "
@@ -18198,6 +18007,69 @@ msgstr ""
msgid "Create QR code"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Update comment"
@@ -18372,6 +18244,44 @@ msgstr ""
msgid "Enable test mode"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "All actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Team actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Customer actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
@@ -19270,20 +19180,6 @@ msgstr ""
msgid "More quotas"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
msgid "Open event dashboard"
@@ -19294,10 +19190,6 @@ msgctxt "typography"
msgid "The quick brown fox jumps over the lazy dog."
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
msgid "Specific object selected"
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_quota_box.html
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html
msgid "Quota:"
@@ -20312,7 +20204,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, python-format
msgid "%(number)s selected"
msgstr ""
@@ -20399,7 +20290,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
msgid "Edit selected"
msgstr ""
@@ -20696,13 +20586,6 @@ msgid ""
"the customer account."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/views/orders.py
msgid ""
"Ticket secrets of order positions that have been used to issue a gift card "
"can not be changed. Only the link will be changed in this case."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
msgid ""
"Removing or splitting this position will also remove or split all add-ons to "
@@ -21115,6 +20998,11 @@ msgstr ""
msgid "Create a refund"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Cancel transfer"
msgstr ""
@@ -22191,7 +22079,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Advanced settings"
msgstr ""
@@ -24348,13 +24235,11 @@ msgid "Copy codes"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Voucher details"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid ""
"If you choose \"any product\" for a specific quota and choose to reserve "
@@ -24362,10 +24247,6 @@ msgid ""
"voucher holder if another quota associated with the product is sold out!"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgid "Change multiple vouchers"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Delete voucher"
@@ -24705,8 +24586,7 @@ msgstr ""
#, python-brace-format
msgid ""
"You cannot accept the invitation for \"{}\" as you already are part of this "
"team. If you want to add a different user or create a new account, log out "
"and click the invitation link again."
"team."
msgstr ""
#: pretix/control/views/auth.py
@@ -25309,30 +25189,6 @@ msgid ""
"record."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We could not find a CNAME record pointing to our DKIM key for domain you are "
"trying to use. This means that there is a very high change most of the "
"emails will be rejected or marked as spam. We strongly recommend setting up "
"DKIM through a CNAME record. You can do so through the DNS settings at the "
"provider you registered your domain with."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We found a CNAME record for a DKIM key, but it is not pointing to the right "
"location. This means that there is a very high chance most of the emails "
"will be rejected or marked as spam. You should update the DNS settings of "
"your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We did not find DMARC record for your domain. This means that there is a "
"very high chance most of the emails will be rejected or marked as spam. You "
"should update the DNS settings of your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid "The verification code was incorrect, please try again."
msgstr ""
@@ -26392,7 +26248,6 @@ msgid "Macao"
msgstr ""
#: pretix/helpers/daterange.py
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html
#: pretix/presale/templates/pretixpresale/fragment_calendar.html
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html
#: pretix/presale/templates/pretixpresale/organizers/index.html
@@ -27580,6 +27435,11 @@ msgstr ""
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "<a target=\"_blank\" rel=\"noopener\" href=\"{docs_url}\">{text}</a>"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
@@ -28214,13 +28074,8 @@ msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"Redirection will only be allowed to URLs that start with one of these "
"prefixes. Enter one allowed URL prefix per line. URL prefixes must include a "
"slash after the hostname."
msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"All values must be URLs that include at last one slash after the hostname."
"prefixes. Enter one or more allowed URL prefix per line. URL prefixes must "
"include a slash after the hostname."
msgstr ""
#: pretix/plugins/sendmail/apps.py
+236 -404
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
"PO-Revision-Date: 2026-06-25 14:00+0000\n"
"Last-Translator: Kim Lozano <joaquim.lozano@upc.edu>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix/"
@@ -19,6 +19,38 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.6.1\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr "Botiga desactivada"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr "S'ha acabat la prevenda"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr "No ha començat la prevenda"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr "En venda"
#: pretix/_base_settings.py
msgid "English"
msgstr "Anglès"
@@ -1342,24 +1374,6 @@ msgstr "Compte de Stripe"
msgid "Download a spreadsheet of all currently registered customer accounts."
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/permissions.py
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#, fuzzy
#| msgctxt "refund_source"
#| msgid "Customer"
msgid "Customers"
msgstr "Client"
#: pretix/base/exporters/customers.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
#, fuzzy
#| msgid "Members"
msgid "Memberships"
msgstr "Membres"
#: pretix/base/exporters/customers.py pretix/base/models/customers.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
@@ -1499,74 +1513,6 @@ msgstr "Si"
msgid "No"
msgstr "No"
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr "Mode de prova"
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr "Cancel·lat"
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#, fuzzy
#| msgid "Members"
msgid "Membership type"
msgstr "Membres"
#: pretix/base/exporters/customers.py
#, fuzzy
#| msgid "Purchased"
msgid "Purchase ticket"
msgstr "Comprat"
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
#: pretix/base/models/memberships.py pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr "Data d'inici"
#: pretix/base/exporters/customers.py
#, fuzzy
#| msgid "Date"
msgid "Start time"
msgstr "Data"
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr "Data de finalització"
#: pretix/base/exporters/customers.py
#, fuzzy
#| msgid "End: %(time)s"
msgid "End time"
msgstr "Final: %(time)s"
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
#, fuzzy
#| msgid "Invoices"
@@ -2444,6 +2390,30 @@ msgstr "cancel·lat"
msgid "Position ID"
msgstr "Identificador de la posició"
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/forms/questions.py pretix/base/models/memberships.py
#: pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr "Data d'inici"
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr "Data de finalització"
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/modelimport_orders.py pretix/base/modelimport_vouchers.py
#: pretix/base/models/items.py pretix/base/models/vouchers.py
@@ -2884,6 +2854,12 @@ msgstr ""
msgid "Gift card code"
msgstr "Codi del val regal"
#: pretix/base/exporters/orderlist.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr "Mode de prova"
#: pretix/base/exporters/orderlist.py pretix/base/models/organizer.py
#: pretix/control/forms/event.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/email_setup.html
@@ -2997,7 +2973,7 @@ msgstr "Expirat i amb valor"
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/plugins/reports/exporters.py pretix/plugins/sendmail/forms.py
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_giftcards.html
msgid "Expired"
@@ -3183,7 +3159,7 @@ msgid "This combination of credentials is not known to our system."
msgstr "El sistema no reconeix aquesta combinació de credencials."
#: pretix/base/forms/auth.py pretix/base/forms/user.py
#: pretix/control/views/user.py pretix/presale/forms/customer.py
#: pretix/presale/forms/customer.py
msgid "For security reasons, please wait 5 minutes before you try again."
msgstr ""
"Per raons de seguretat, si us plau espereu 5 minuts abans de tornar-ho a "
@@ -4210,7 +4186,6 @@ msgstr "Permet traspassar la quota"
#: pretix/base/modelimport_vouchers.py pretix/base/models/vouchers.py
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/views/vouchers.py
msgid "Price effect"
@@ -4934,8 +4909,8 @@ msgstr ""
#: pretix/base/models/event.py
msgid ""
"This will be used to let users know if the event is in a different timezone, "
"and to let us calculate the local time of a user."
"This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times."
msgstr ""
#: pretix/base/models/event.py pretix/base/models/organizer.py
@@ -6473,6 +6448,25 @@ msgstr ""
msgid "Number of times this membership can be used in a purchase."
msgstr "Número de vegades que es pot utilitzar aquest val."
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr "Cancel·lat"
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#, fuzzy
#| msgid "Members"
msgid "Membership type"
msgstr "Membres"
#: pretix/base/models/orders.py
msgid "pending"
msgstr "pendent"
@@ -7333,7 +7327,7 @@ msgstr "Aquesta variació no pertany a aquest producte."
msgid "It is currently not possible to create vouchers for add-on products."
msgstr "Actualment no es poden crear vals per a complements de productes."
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"You need to select a specific product or quota if this voucher should "
"reserve tickets."
@@ -7361,7 +7355,7 @@ msgid ""
msgstr ""
"El número màxim per comanda no pot ser menor que el número mínim per comanda."
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"If you want this voucher to block quota, you need to select a specific date."
msgstr ""
@@ -7905,12 +7899,6 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr "Aquesta targeta regal ja s'ha fet servir. Si us plau torneu a provar."
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
"usable."
msgstr ""
#: pretix/base/pdf.py
msgid "Ticket code (barcode content)"
msgstr "Codi de tiquet (contingut del codi de barres)"
@@ -8561,6 +8549,14 @@ msgid ""
"permissions."
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#, fuzzy
#| msgctxt "refund_source"
#| msgid "Customer"
msgid "Customers"
msgstr "Client"
#: pretix/base/permissions.py pretix/control/navigation.py
msgid "Devices"
msgstr ""
@@ -9953,14 +9949,6 @@ msgid ""
"card."
msgstr ""
#: pretix/base/services/orders.py
#, fuzzy
#| msgid "You cannot select a quota that belongs to a different event."
msgid ""
"You cannot change the ticket secret of a position that has been used to "
"issue a gift card."
msgstr "No podeu seleccionar una quota que pertany a un esdeveniment diferent."
#: pretix/base/services/orders.py
#, python-brace-format
msgid ""
@@ -11834,13 +11822,13 @@ msgid "We'll show this publicly to allow attendees to contact you."
msgstr ""
"Això es mostrarà públicament per permetre que els assistents us contactin."
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
#, fuzzy
#| msgid "Contact address"
msgid "Contact URL"
msgstr "Adreça de contacte"
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid ""
"If you set this, the footer contact link will point here instead of using "
"the email address above. Please note that you still need to add a contact "
@@ -13876,7 +13864,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
@@ -15316,7 +15303,7 @@ msgstr "Totes les comandes"
msgid "Valid orders"
msgstr "Comandes pagades"
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Paid (or canceled with paid fee)"
msgstr "Paga (o cancel·lat amb tarifa de pagament)"
@@ -15345,7 +15332,7 @@ msgstr "Pendent o pagat"
msgid "Cancellations"
msgstr "Cancel·lació"
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "Canceled (paid fee)"
msgid "Canceled (fully)"
@@ -15559,22 +15546,6 @@ msgstr "La tenda està activa i la pre-venda en marxa"
msgid "Inactive"
msgstr "Inactiu"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr "No ha començat la prevenda"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr "S'ha acabat la prevenda"
#: pretix/control/forms/filter.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
#, fuzzy
@@ -15624,6 +15595,15 @@ msgstr "Inactiu"
msgid "not yet activated"
msgstr "Inactiu"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
#, fuzzy
#| msgid "Members"
msgid "Memberships"
msgstr "Membres"
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "Event end"
@@ -15897,42 +15877,6 @@ msgstr "Revocar l'accés"
msgid "Search email address or subject"
msgstr "Requereix adreces de correu per tiquet"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "All orders"
msgid "All sources"
msgstr "Totes les comandes"
#: pretix/control/forms/filter.py
msgid "Team actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Customer actions"
msgstr ""
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "Device name"
msgid "Device actions"
msgstr "Nom del dispositiu"
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "Order details"
msgid "User email"
msgstr "Detalls de la comanda"
#: pretix/control/forms/filter.py pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/forms/global_settings.py
msgid "Additional footer text"
msgstr ""
@@ -17262,7 +17206,6 @@ msgstr "Opcional"
#: pretix/control/forms/renderers.py
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, fuzzy
#| msgid "Save changes"
msgctxt "form_bulk"
@@ -17384,65 +17327,6 @@ msgid ""
"should show them."
msgstr ""
#: pretix/control/forms/vouchers.py
#, python-format
msgid ""
"You cannot reduce the maximum number of redemptions to %(max_usages)s, "
"because at least one of the selected vouchers has already been redeemed "
"%(max_redeemed)s times."
msgstr ""
#: pretix/control/forms/vouchers.py
#, fuzzy
#| msgid ""
#| "You cannot create a voucher that blocks quota as the selected product or "
#| "quota is currently sold out or completely reserved."
msgid ""
"You cannot create a voucher that allows selection of a quota but has no date "
"selected."
msgstr ""
"No podeu crear un val que bloca la quota ja que el producte o quota "
"seleccionats s'han venut o reservat completament."
#: pretix/control/forms/vouchers.py
#, fuzzy
#| msgid "The selected product does not allow to select a seat."
msgid "The selected quota does not match the selected subevent."
msgstr "El producte seleccionat no permet triar un seient."
#: pretix/control/forms/vouchers.py
#, fuzzy
#| msgid ""
#| "There is not enough quota available on quota \"{}\" to perform the "
#| "operation."
msgid "There is no sufficient quota available to perform this change."
msgstr "No hi ha prou quota disponible a \"{}\" per realitzar l'operació."
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the maximum number of usages in bulk is not supported if any of the "
"selected vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgctxt "subevent"
msgid ""
"Changing the date in bulk is not supported if any of the selected vouchers "
"is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the product to a quota is not supported if any of the selected "
"vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"This change cannot be completed because not all assigned seats of the "
"vouchers are still available"
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "Codes"
msgstr ""
@@ -19390,6 +19274,10 @@ msgstr ""
msgid "Account history"
msgstr ""
#: pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
msgid "Admin sessions"
@@ -19675,7 +19563,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/users/create.html
#: pretix/control/templates/pretixcontrol/users/form.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/add.html
@@ -20129,8 +20016,8 @@ msgstr "Esborrar"
#: pretix/control/templates/pretixcontrol/checkin/checkins.html
#: pretix/control/templates/pretixcontrol/checkin/index.html
#: pretix/control/templates/pretixcontrol/checkin/lists.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#: pretix/control/templates/pretixcontrol/items/question.html
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/orders/index.html
@@ -20141,6 +20028,7 @@ msgstr "Esborrar"
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/index.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
#: pretix/control/templates/pretixcontrol/organizers/teams.html
@@ -20292,7 +20180,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
msgid "Select all results on other pages as well"
msgstr ""
@@ -20936,22 +20823,6 @@ msgstr ""
msgid "We found an SPF record on your domain that includes this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DKIM record should be set up as a CNAME record like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DKIM record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DMARC record could look like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DMARC record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#, fuzzy
#| msgid "Verification failed"
@@ -21126,94 +20997,6 @@ msgid ""
"undeletable data, such as orders not performed in test mode."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/delete.html
msgid ""
"This operation will destroy your event including all configuration, "
@@ -21332,6 +21115,69 @@ msgstr "URL de sistema:"
msgid "Create QR code"
msgstr "Codi de comanda"
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Update comment"
@@ -21523,6 +21369,44 @@ msgstr ""
msgid "Enable test mode"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "All actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Team actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Customer actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
@@ -22535,20 +22419,6 @@ msgstr ""
msgid "More quotas"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr "Botiga desactivada"
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr "En venda"
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
msgid "Open event dashboard"
@@ -22559,12 +22429,6 @@ msgctxt "typography"
msgid "The quick brown fox jumps over the lazy dog."
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#, fuzzy
#| msgid "Specific seat"
msgid "Specific object selected"
msgstr "Seient específic"
#: pretix/control/templates/pretixcontrol/fragment_quota_box.html
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html
msgid "Quota:"
@@ -23682,7 +23546,6 @@ msgstr "Pot canviar les comandes"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, python-format
msgid "%(number)s selected"
msgstr ""
@@ -23773,7 +23636,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
msgid "Edit selected"
msgstr ""
@@ -24079,13 +23941,6 @@ msgid ""
"the customer account."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/views/orders.py
msgid ""
"Ticket secrets of order positions that have been used to issue a gift card "
"can not be changed. Only the link will be changed in this case."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
#, fuzzy
#| msgid ""
@@ -24546,6 +24401,11 @@ msgstr ""
msgid "Create a refund"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Cancel transfer"
msgstr ""
@@ -25770,7 +25630,6 @@ msgstr "Pot canviar les comandes"
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Advanced settings"
msgstr "Configuració avançada"
@@ -28139,13 +27998,11 @@ msgid "Copy codes"
msgstr "Copiar codis"
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Voucher details"
msgstr "Detalls del val"
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid ""
"If you choose \"any product\" for a specific quota and choose to reserve "
@@ -28153,12 +28010,6 @@ msgid ""
"voucher holder if another quota associated with the product is sold out!"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, fuzzy
#| msgid "Create multiple vouchers"
msgid "Change multiple vouchers"
msgstr "Crear múltiples vals"
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Delete voucher"
@@ -28522,8 +28373,7 @@ msgstr ""
#, python-brace-format
msgid ""
"You cannot accept the invitation for \"{}\" as you already are part of this "
"team. If you want to add a different user or create a new account, log out "
"and click the invitation link again."
"team."
msgstr ""
#: pretix/control/views/auth.py
@@ -29163,30 +29013,6 @@ msgid ""
"record."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We could not find a CNAME record pointing to our DKIM key for domain you are "
"trying to use. This means that there is a very high change most of the "
"emails will be rejected or marked as spam. We strongly recommend setting up "
"DKIM through a CNAME record. You can do so through the DNS settings at the "
"provider you registered your domain with."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We found a CNAME record for a DKIM key, but it is not pointing to the right "
"location. This means that there is a very high chance most of the emails "
"will be rejected or marked as spam. You should update the DNS settings of "
"your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We did not find DMARC record for your domain. This means that there is a "
"very high chance most of the emails will be rejected or marked as spam. You "
"should update the DNS settings of your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
#, fuzzy
#| msgid "This gift card was used in the meantime. Please try again."
@@ -30374,7 +30200,6 @@ msgid "Macao"
msgstr ""
#: pretix/helpers/daterange.py
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html
#: pretix/presale/templates/pretixpresale/fragment_calendar.html
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html
#: pretix/presale/templates/pretixpresale/organizers/index.html
@@ -31714,6 +31539,11 @@ msgstr ""
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "<a target=\"_blank\" rel=\"noopener\" href=\"{docs_url}\">{text}</a>"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
@@ -32439,13 +32269,8 @@ msgstr "URL base de la redirecció"
#: pretix/plugins/returnurl/views.py
msgid ""
"Redirection will only be allowed to URLs that start with one of these "
"prefixes. Enter one allowed URL prefix per line. URL prefixes must include a "
"slash after the hostname."
msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"All values must be URLs that include at last one slash after the hostname."
"prefixes. Enter one or more allowed URL prefix per line. URL prefixes must "
"include a slash after the hostname."
msgstr ""
#: pretix/plugins/sendmail/apps.py
@@ -38462,6 +38287,13 @@ msgstr "Kosovo"
#~ msgid "Full access"
#~ msgstr "Nom complet"
#, fuzzy
#~| msgid "You cannot select a quota that belongs to a different event."
#~ msgid ""
#~ "You cannot cancel a position that has been used to issue a gift card."
#~ msgstr ""
#~ "No podeu seleccionar una quota que pertany a un esdeveniment diferent."
#~ msgid "Automatically on payment"
#~ msgstr "Automàticament durant el pagament"
+231 -461
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
"PO-Revision-Date: 2026-01-29 19:42+0000\n"
"Last-Translator: Jiří Pastrňák <jiri@pastrnak.email>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/"
@@ -19,6 +19,38 @@ msgstr ""
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 5.15.2\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr "Obchod vypnut"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr "Předprodej ukončen"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr "Předprodej zatím nezačal"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr "V prodeji"
#: pretix/_base_settings.py
msgid "English"
msgstr "Angličtina"
@@ -1270,19 +1302,6 @@ msgstr "Zákaznické účty"
msgid "Download a spreadsheet of all currently registered customer accounts."
msgstr "Stažení tabulky všech aktuálně registrovaných zákaznických účtů."
#: pretix/base/exporters/customers.py pretix/base/permissions.py
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr "Zákazníci"
#: pretix/base/exporters/customers.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr "Členství"
#: pretix/base/exporters/customers.py pretix/base/models/customers.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
@@ -1415,71 +1434,6 @@ msgstr "Ano"
msgid "No"
msgstr "Ne"
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr "Testovací režím"
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr "Zrušeno"
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr "Druh členství"
#: pretix/base/exporters/customers.py
#, fuzzy
msgid "Purchase ticket"
msgstr "Zakoupeno"
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
#: pretix/base/models/memberships.py pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr "Datum začátku"
#: pretix/base/exporters/customers.py
#, fuzzy
#| msgid "Start time from"
msgid "Start time"
msgstr "Čas začátku od"
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr "Datum konce"
#: pretix/base/exporters/customers.py
#, fuzzy
#| msgid "End: %(time)s"
msgid "End time"
msgstr "Konec: %(time)s"
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
msgctxt "export_category"
msgid "Invoices"
@@ -2335,6 +2289,30 @@ msgstr "zrušeno"
msgid "Position ID"
msgstr "ID pozice"
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/forms/questions.py pretix/base/models/memberships.py
#: pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr "Datum začátku"
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr "Datum konce"
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/modelimport_orders.py pretix/base/modelimport_vouchers.py
#: pretix/base/models/items.py pretix/base/models/vouchers.py
@@ -2739,6 +2717,12 @@ msgstr "Stáhněte si tabulku všech transakcí s dárkovými poukazy."
msgid "Gift card code"
msgstr "kód dárkové poukázky"
#: pretix/base/exporters/orderlist.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr "Testovací režím"
#: pretix/base/exporters/orderlist.py pretix/base/models/organizer.py
#: pretix/control/forms/event.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/email_setup.html
@@ -2855,7 +2839,7 @@ msgstr "Expirované a s hodnotou"
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/plugins/reports/exporters.py pretix/plugins/sendmail/forms.py
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_giftcards.html
msgid "Expired"
@@ -3023,7 +3007,7 @@ msgid "This combination of credentials is not known to our system."
msgstr "Zadané přihlašovací údaje nejsou platné."
#: pretix/base/forms/auth.py pretix/base/forms/user.py
#: pretix/control/views/user.py pretix/presale/forms/customer.py
#: pretix/presale/forms/customer.py
msgid "For security reasons, please wait 5 minutes before you try again."
msgstr ""
"Z bezpečnostních důvodů, prosím, počkejte pět minut a potom to zkuste znovu."
@@ -3995,7 +3979,6 @@ msgstr "Ignorování kvóty"
#: pretix/base/modelimport_vouchers.py pretix/base/models/vouchers.py
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/views/vouchers.py
msgid "Price effect"
@@ -4687,8 +4670,8 @@ msgstr ""
#: pretix/base/models/event.py
msgid ""
"This will be used to let users know if the event is in a different timezone, "
"and to let us calculate the local time of a user."
"This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times."
msgstr ""
#: pretix/base/models/event.py pretix/base/models/organizer.py
@@ -6155,6 +6138,23 @@ msgstr ""
msgid "Number of times this membership can be used in a purchase."
msgstr "Maximální počet využití."
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr "Zrušeno"
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr "Druh členství"
#: pretix/base/models/orders.py
msgid "pending"
msgstr "chybějící"
@@ -6990,7 +6990,7 @@ msgstr "Tato varianta nepatří k tomuto produktu."
msgid "It is currently not possible to create vouchers for add-on products."
msgstr "V současné době není možné vytvářet poukazy na doplňkové produkty."
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"You need to select a specific product or quota if this voucher should "
"reserve tickets."
@@ -7013,7 +7013,7 @@ msgid ""
"usages."
msgstr "Maximální počet použití nesmí být nižší než minimální počet použití."
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"If you want this voucher to block quota, you need to select a specific date."
msgstr ""
@@ -7536,15 +7536,6 @@ msgstr "Tato dárková karta není organizátorem akce akceptována."
msgid "This gift card was used in the meantime. Please try again."
msgstr "Tato dárková karta byla mezitím použita. Zkuste to prosím znovu."
#: pretix/base/payment.py
#, fuzzy
#| msgid ""
#| "This payment provider does not exist or the respective plugin is disabled."
msgid ""
"This payment provider exists for historical purposes only and is no longer "
"usable."
msgstr "Tento poskytovatel plateb neexistuje nebo příslušný plugin je vypnutý."
#: pretix/base/pdf.py
msgid "Ticket code (barcode content)"
msgstr "Kód vstupenky (obsah čárkového kódu)"
@@ -8150,6 +8141,11 @@ msgid ""
"permissions."
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr "Zákazníci"
#: pretix/base/permissions.py pretix/control/navigation.py
msgid "Devices"
msgstr "Zařízení"
@@ -9477,16 +9473,6 @@ msgid ""
"card."
msgstr "Cenu pozice, která byla použita k vydání dárkové karty, nelze změnit."
#: pretix/base/services/orders.py
#, fuzzy
#| msgid ""
#| "You cannot change the price of a position that has been used to issue a "
#| "gift card."
msgid ""
"You cannot change the ticket secret of a position that has been used to "
"issue a gift card."
msgstr "Cenu pozice, která byla použita k vydání dárkové karty, nelze změnit."
#: pretix/base/services/orders.py
#, python-brace-format
msgid ""
@@ -11302,13 +11288,13 @@ msgstr "Kontaktní e-mail"
msgid "We'll show this publicly to allow attendees to contact you."
msgstr "Tento údaj zobrazíme veřejně, aby vás účastníci mohli kontaktovat."
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
#, fuzzy
#| msgid "Contact"
msgid "Contact URL"
msgstr "Kontakt"
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid ""
"If you set this, the footer contact link will point here instead of using "
"the email address above. Please note that you still need to add a contact "
@@ -13301,7 +13287,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
@@ -14611,7 +14596,7 @@ msgstr "Všechny objednávky"
msgid "Valid orders"
msgstr "Platné objednávky"
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Paid (or canceled with paid fee)"
msgstr "Zaplaceno (nebo zrušeno se zaplaceným poplatkem)"
@@ -14635,7 +14620,7 @@ msgstr "Probíhající nebo zaplacené"
msgid "Cancellations"
msgstr "Storna"
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Canceled (fully)"
msgstr "Zrušeno (kompletní)"
@@ -14815,22 +14800,6 @@ msgstr "Obchod naživo a předprodej běží"
msgid "Inactive"
msgstr "Neaktivní"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr "Předprodej zatím nezačal"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr "Předprodej ukončen"
#: pretix/control/forms/filter.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
msgid "Date from"
@@ -14868,6 +14837,13 @@ msgstr "aktivní"
msgid "not yet activated"
msgstr "zatím neaktivováno"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr "Členství"
#: pretix/control/forms/filter.py
msgid "Has no memberships"
msgstr "Nemá žádné členství"
@@ -15097,42 +15073,6 @@ msgstr "Odvolaná zařízení"
msgid "Search email address or subject"
msgstr "Vyžadovat e-mailové adresy na lístek"
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr "Zdroj"
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "All vouchers"
msgid "All sources"
msgstr "Všechny poukázky"
#: pretix/control/forms/filter.py
msgid "Team actions"
msgstr "Týmové akce"
#: pretix/control/forms/filter.py
msgid "Customer actions"
msgstr "Zákaznické akce"
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "Device status"
msgid "Device actions"
msgstr "Stav zařízení"
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "Order details"
msgid "User email"
msgstr "Podrobnosti o objednávce"
#: pretix/control/forms/filter.py pretix/control/navigation.py
msgid "All users"
msgstr "Všechny uživatelé"
#: pretix/control/forms/global_settings.py
msgid "Additional footer text"
msgstr "Další text v zápatí"
@@ -16500,7 +16440,6 @@ msgstr "Volitelné"
#: pretix/control/forms/renderers.py
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgctxt "form_bulk"
msgid "change"
msgstr "změnit"
@@ -16616,65 +16555,6 @@ msgstr ""
"Poukázka odpovídá pouze skrytým produktům, ale nezvolili jste, že je mají "
"zobrazit."
#: pretix/control/forms/vouchers.py
#, python-format
msgid ""
"You cannot reduce the maximum number of redemptions to %(max_usages)s, "
"because at least one of the selected vouchers has already been redeemed "
"%(max_redeemed)s times."
msgstr ""
#: pretix/control/forms/vouchers.py
#, fuzzy
#| msgid ""
#| "You cannot create a voucher that blocks quota as the selected product or "
#| "quota is currently sold out or completely reserved."
msgid ""
"You cannot create a voucher that allows selection of a quota but has no date "
"selected."
msgstr ""
"Nelze vytvořit poukaz, který blokuje kvótu, protože vybraný produkt nebo "
"kvóta jsou aktuálně vyprodány nebo zcela rezervovány."
#: pretix/control/forms/vouchers.py
#, fuzzy
#| msgid "The selected product does not allow to select a seat."
msgid "The selected quota does not match the selected subevent."
msgstr "Vybraný produkt neumožňuje výběr místa."
#: pretix/control/forms/vouchers.py
#, fuzzy
#| msgid ""
#| "There is not enough quota available on quota \"{}\" to perform the "
#| "operation."
msgid "There is no sufficient quota available to perform this change."
msgstr "Na kvótu „{}“ není k dispozici dostatek kvóty k provedení operace."
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the maximum number of usages in bulk is not supported if any of the "
"selected vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgctxt "subevent"
msgid ""
"Changing the date in bulk is not supported if any of the selected vouchers "
"is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the product to a quota is not supported if any of the selected "
"vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"This change cannot be completed because not all assigned seats of the "
"vouchers are still available"
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "Codes"
msgstr "Kupónové kódy"
@@ -18533,6 +18413,10 @@ msgstr "Autorizované aplikace"
msgid "Account history"
msgstr "Historie účtu"
#: pretix/control/navigation.py
msgid "All users"
msgstr "Všechny uživatelé"
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
msgid "Admin sessions"
@@ -18811,7 +18695,6 @@ msgstr "Nastavit nové heslo"
#: pretix/control/templates/pretixcontrol/users/create.html
#: pretix/control/templates/pretixcontrol/users/form.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/add.html
@@ -19267,8 +19150,8 @@ msgstr "Smazat"
#: pretix/control/templates/pretixcontrol/checkin/checkins.html
#: pretix/control/templates/pretixcontrol/checkin/index.html
#: pretix/control/templates/pretixcontrol/checkin/lists.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#: pretix/control/templates/pretixcontrol/items/question.html
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/orders/index.html
@@ -19279,6 +19162,7 @@ msgstr "Smazat"
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/index.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
#: pretix/control/templates/pretixcontrol/organizers/teams.html
@@ -19413,7 +19297,6 @@ msgstr "Čas"
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
msgid "Select all results on other pages as well"
msgstr "Vybrat všechny výsledky i na dalších stránkách"
@@ -20129,34 +20012,6 @@ msgid "We found an SPF record on your domain that includes this system. Great!"
msgstr ""
"Našli jsme záznam SPF na vaší doméně, který obsahuje tento systém. Skvělé!"
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#, fuzzy
#| msgid "Your new SPF record could look like this:"
msgid "Your new DKIM record should be set up as a CNAME record like this:"
msgstr "Váš nový záznam SPF může vypadat takto:"
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#, fuzzy
#| msgid ""
#| "We found an SPF record on your domain that includes this system. Great!"
msgid "We found a DKIM record on your domain for this system. Great!"
msgstr ""
"Našli jsme záznam SPF na vaší doméně, který obsahuje tento systém. Skvělé!"
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#, fuzzy
#| msgid "Your new SPF record could look like this:"
msgid "Your new DMARC record could look like this:"
msgstr "Váš nový záznam SPF může vypadat takto:"
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
#, fuzzy
#| msgid ""
#| "We found an SPF record on your domain that includes this system. Great!"
msgid "We found a DMARC record on your domain for this system. Great!"
msgstr ""
"Našli jsme záznam SPF na vaší doméně, který obsahuje tento systém. Skvělé!"
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Verification"
msgstr "Ověřování"
@@ -20356,103 +20211,6 @@ msgstr ""
"nesmazatelná data, například objednávky, které nebyly provedeny v testovacím "
"režimu."
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr "Kontrola"
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
"Tato akce obsahuje <strong>přeplacené objednávky</strong>, například z "
"důvodu duplicitních pokusů o platbu. Měli byste tyto případy zkontrolovat a "
"uvažovat možnost vrácení přeplacené částky uživateli."
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show overpaid orders"
msgstr "Zobrazit přeplacené objednávky"
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
"Tato akce obsahuje <strong>nevyřízené náhrady</strong>, o které byste se "
"měli postarat."
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show pending refunds"
msgstr "Zobrazit čekající náhrady"
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
"Tato akce obsahuje <strong>vyžádaná zrušení</strong>, o které byste se měli "
"postarat."
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders requesting cancellation"
msgstr "Zobrazit objednávky s požadavkem na zrušení"
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
"Tato akce obsahuje <strong>nevyřízená schválení</strong>, o která byste se "
"měli postarat."
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders pending approval"
msgstr "Zobrazit objednávky čekající na schválení"
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/delete.html
msgid ""
"This operation will destroy your event including all configuration, "
@@ -20577,6 +20335,78 @@ msgstr "Adresa obchodu:"
msgid "Create QR code"
msgstr "Vytvořit kód QR"
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
"Tato akce obsahuje <strong>přeplacené objednávky</strong>, například z "
"důvodu duplicitních pokusů o platbu. Měli byste tyto případy zkontrolovat a "
"uvažovat možnost vrácení přeplacené částky uživateli."
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show overpaid orders"
msgstr "Zobrazit přeplacené objednávky"
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
"Tato akce obsahuje <strong>nevyřízené náhrady</strong>, o které byste se "
"měli postarat."
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show pending refunds"
msgstr "Zobrazit čekající náhrady"
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
"Tato akce obsahuje <strong>vyžádaná zrušení</strong>, o které byste se měli "
"postarat."
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders requesting cancellation"
msgstr "Zobrazit objednávky s požadavkem na zrušení"
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
"Tato akce obsahuje <strong>nevyřízená schválení</strong>, o která byste se "
"měli postarat."
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders pending approval"
msgstr "Zobrazit objednávky čekající na schválení"
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Update comment"
@@ -20758,6 +20588,44 @@ msgstr ""
msgid "Enable test mode"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "All actions"
msgstr "Všechny akce"
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Team actions"
msgstr "Týmové akce"
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Customer actions"
msgstr "Zákaznické akce"
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr "Kontrola"
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
@@ -21720,20 +21588,6 @@ msgstr "Žádné termíny"
msgid "More quotas"
msgstr "Další kvóty"
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr "Obchod vypnut"
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr "V prodeji"
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
msgid "Open event dashboard"
@@ -21744,12 +21598,6 @@ msgctxt "typography"
msgid "The quick brown fox jumps over the lazy dog."
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#, fuzzy
#| msgid "Specific seat"
msgid "Specific object selected"
msgstr "Přidělené místo"
#: pretix/control/templates/pretixcontrol/fragment_quota_box.html
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html
msgid "Quota:"
@@ -22806,7 +22654,6 @@ msgstr "Změnit více zařízení"
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, python-format
msgid "%(number)s selected"
msgstr "%(number)s vybráno"
@@ -22903,7 +22750,6 @@ msgstr "Smazat vybrané"
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
msgid "Edit selected"
msgstr "Upravit vybrané"
@@ -23205,13 +23051,6 @@ msgid ""
"the customer account."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/views/orders.py
msgid ""
"Ticket secrets of order positions that have been used to issue a gift card "
"can not be changed. Only the link will be changed in this case."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
msgid ""
"Removing or splitting this position will also remove or split all add-ons to "
@@ -23648,6 +23487,11 @@ msgstr ""
msgid "Create a refund"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr "Zdroj"
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Cancel transfer"
msgstr ""
@@ -24795,7 +24639,6 @@ msgstr "Změnit více zařízení"
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Advanced settings"
msgstr "Rozšířená nastavení"
@@ -27126,13 +26969,11 @@ msgid "Copy codes"
msgstr "Kopírovat kódy"
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Voucher details"
msgstr "Podrobnosti o poukázce"
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid ""
"If you choose \"any product\" for a specific quota and choose to reserve "
@@ -27140,12 +26981,6 @@ msgid ""
"voucher holder if another quota associated with the product is sold out!"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, fuzzy
#| msgid "Change multiple devices"
msgid "Change multiple vouchers"
msgstr "Změnit více zařízení"
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Delete voucher"
@@ -27502,8 +27337,7 @@ msgstr ""
#, python-brace-format
msgid ""
"You cannot accept the invitation for \"{}\" as you already are part of this "
"team. If you want to add a different user or create a new account, log out "
"and click the invitation link again."
"team."
msgstr ""
#: pretix/control/views/auth.py
@@ -28137,66 +27971,6 @@ msgstr ""
"Měli byste aktualizovat nastavení DNS své domény tak, aby zahrnovalo tento "
"systém v záznamu SPF."
#: pretix/control/views/mailsetup.py
#, fuzzy
#| msgid ""
#| "We could not find an SPF record set for the domain you are trying to use. "
#| "This means that there is a very high change most of the emails will be "
#| "rejected or marked as spam. We strongly recommend setting an SPF record "
#| "on the domain. You can do so through the DNS settings at the provider you "
#| "registered your domain with."
msgid ""
"We could not find a CNAME record pointing to our DKIM key for domain you are "
"trying to use. This means that there is a very high change most of the "
"emails will be rejected or marked as spam. We strongly recommend setting up "
"DKIM through a CNAME record. You can do so through the DNS settings at the "
"provider you registered your domain with."
msgstr ""
"Nepodařilo se nám najít záznam SPF nastavený pro doménu, kterou se snažíte "
"použít. To znamená, že je velmi vysoká pravděpodobnost, že většina e-mailů "
"bude odmítnuta nebo označena jako spam. Důrazně doporučujeme nastavit záznam "
"SPF pro danou doménu. Můžete tak učinit prostřednictvím nastavení DNS u "
"poskytovatele, u kterého jste doménu zaregistrovali."
#: pretix/control/views/mailsetup.py
#, fuzzy
#| msgid ""
#| "We found an SPF record set for the domain you are trying to use, but it "
#| "does not include this system's email server. This means that there is a "
#| "very high chance most of the emails will be rejected or marked as spam. "
#| "You should update the DNS settings of your domain to include this system "
#| "in the SPF record."
msgid ""
"We found a CNAME record for a DKIM key, but it is not pointing to the right "
"location. This means that there is a very high chance most of the emails "
"will be rejected or marked as spam. You should update the DNS settings of "
"your domain."
msgstr ""
"Našli jsme záznam SPF nastavený pro doménu, kterou se snažíte použít, ale "
"neobsahuje e-mailový server tohoto systému. To znamená, že je velmi vysoká "
"pravděpodobnost, že většina e-mailů bude odmítnuta nebo označena jako spam. "
"Měli byste aktualizovat nastavení DNS své domény tak, aby zahrnovalo tento "
"systém v záznamu SPF."
#: pretix/control/views/mailsetup.py
#, fuzzy
#| msgid ""
#| "We found an SPF record set for the domain you are trying to use, but it "
#| "does not include this system's email server. This means that there is a "
#| "very high chance most of the emails will be rejected or marked as spam. "
#| "You should update the DNS settings of your domain to include this system "
#| "in the SPF record."
msgid ""
"We did not find DMARC record for your domain. This means that there is a "
"very high chance most of the emails will be rejected or marked as spam. You "
"should update the DNS settings of your domain."
msgstr ""
"Našli jsme záznam SPF nastavený pro doménu, kterou se snažíte použít, ale "
"neobsahuje e-mailový server tohoto systému. To znamená, že je velmi vysoká "
"pravděpodobnost, že většina e-mailů bude odmítnuta nebo označena jako spam. "
"Měli byste aktualizovat nastavení DNS své domény tak, aby zahrnovalo tento "
"systém v záznamu SPF."
#: pretix/control/views/mailsetup.py
msgid "The verification code was incorrect, please try again."
msgstr ""
@@ -29299,7 +29073,6 @@ msgid "Macao"
msgstr ""
#: pretix/helpers/daterange.py
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html
#: pretix/presale/templates/pretixpresale/fragment_calendar.html
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html
#: pretix/presale/templates/pretixpresale/organizers/index.html
@@ -30562,6 +30335,11 @@ msgstr ""
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "<a target=\"_blank\" rel=\"noopener\" href=\"{docs_url}\">{text}</a>"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
@@ -31247,13 +31025,8 @@ msgstr "Přesměrování URL"
#: pretix/plugins/returnurl/views.py
msgid ""
"Redirection will only be allowed to URLs that start with one of these "
"prefixes. Enter one allowed URL prefix per line. URL prefixes must include a "
"slash after the hostname."
msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"All values must be URLs that include at last one slash after the hostname."
"prefixes. Enter one or more allowed URL prefix per line. URL prefixes must "
"include a slash after the hostname."
msgstr ""
#: pretix/plugins/sendmail/apps.py
@@ -36150,9 +35923,6 @@ msgstr "Přístup k zápisu"
msgid "Kosovo"
msgstr "Kosovo"
#~ msgid "All actions"
#~ msgstr "Všechny akce"
#, fuzzy
#~| msgid "Account information"
#~ msgid "Account invitation"
+222 -371
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
"POT-Creation-Date: 2026-07-07 09:30+0000\n"
"PO-Revision-Date: 2023-11-14 11:00+0000\n"
"Last-Translator: Charliecoleg <DM229135@colegsirgar.ac.uk>\n"
"Language-Team: Welsh <https://translate.pretix.eu/projects/pretix/pretix/cy/"
@@ -20,6 +20,38 @@ msgstr ""
"(n==3) ? 3 :(n==6) ? 4 : 5;\n"
"X-Generator: Weblate 5.1.1\n"
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: htmlcov/d_daa1541d0cbf5e2b_dashboards_py.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/_base_settings.py
msgid "English"
msgstr "Saesneg"
@@ -1191,19 +1223,6 @@ msgstr ""
msgid "Download a spreadsheet of all currently registered customer accounts."
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/permissions.py
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/exporters/customers.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/customers.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
@@ -1336,66 +1355,6 @@ msgstr ""
msgid "No"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Purchase ticket"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
#: pretix/base/models/memberships.py pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "Start time"
msgstr ""
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/customers.py
msgid "End time"
msgstr ""
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
msgctxt "export_category"
msgid "Invoices"
@@ -2223,6 +2182,30 @@ msgstr ""
msgid "Position ID"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/forms/questions.py pretix/base/models/memberships.py
#: pretix/control/forms/rrule.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/search/payments.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "Start date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/user/staff_session_edit.html
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
#: pretix/plugins/checkinlists/exporters.py
msgid "End date"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/exporters/waitinglist.py
#: pretix/base/modelimport_orders.py pretix/base/modelimport_vouchers.py
#: pretix/base/models/items.py pretix/base/models/vouchers.py
@@ -2614,6 +2597,12 @@ msgstr ""
msgid "Gift card code"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/memberships.py
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/event/live.html
msgid "Test mode"
msgstr ""
#: pretix/base/exporters/orderlist.py pretix/base/models/organizer.py
#: pretix/control/forms/event.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/email_setup.html
@@ -2727,7 +2716,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/plugins/reports/exporters.py pretix/plugins/sendmail/forms.py
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_giftcards.html
msgid "Expired"
@@ -2893,7 +2882,7 @@ msgid "This combination of credentials is not known to our system."
msgstr ""
#: pretix/base/forms/auth.py pretix/base/forms/user.py
#: pretix/control/views/user.py pretix/presale/forms/customer.py
#: pretix/presale/forms/customer.py
msgid "For security reasons, please wait 5 minutes before you try again."
msgstr ""
@@ -3822,7 +3811,6 @@ msgstr ""
#: pretix/base/modelimport_vouchers.py pretix/base/models/vouchers.py
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/views/vouchers.py
msgid "Price effect"
@@ -4440,8 +4428,8 @@ msgstr ""
#: pretix/base/models/event.py
msgid ""
"This will be used to let users know if the event is in a different timezone, "
"and to let us calculate the local time of a user."
"This will be used to let users know if the event is in a different timezone "
"and lets us calculate users local times."
msgstr ""
#: pretix/base/models/event.py pretix/base/models/organizer.py
@@ -5736,6 +5724,23 @@ msgstr ""
msgid "Number of times this membership can be used in a purchase."
msgstr ""
#: pretix/base/models/memberships.py pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html
#: pretix/control/templates/pretixcontrol/orders/overview.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/plugins/reports/exporters.py
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
msgid "Canceled"
msgstr ""
#: pretix/base/models/memberships.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
msgid "Membership type"
msgstr ""
#: pretix/base/models/orders.py
msgid "pending"
msgstr ""
@@ -6521,7 +6526,7 @@ msgstr ""
msgid "It is currently not possible to create vouchers for add-on products."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"You need to select a specific product or quota if this voucher should "
"reserve tickets."
@@ -6540,7 +6545,7 @@ msgid ""
"usages."
msgstr ""
#: pretix/base/models/vouchers.py pretix/control/forms/vouchers.py
#: pretix/base/models/vouchers.py
msgid ""
"If you want this voucher to block quota, you need to select a specific date."
msgstr ""
@@ -7009,12 +7014,6 @@ msgstr ""
msgid "This gift card was used in the meantime. Please try again."
msgstr ""
#: pretix/base/payment.py
msgid ""
"This payment provider exists for historical purposes only and is no longer "
"usable."
msgstr ""
#: pretix/base/pdf.py
msgid "Ticket code (barcode content)"
msgstr ""
@@ -7578,6 +7577,11 @@ msgid ""
"permissions."
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/organizers/customers.html
msgid "Customers"
msgstr ""
#: pretix/base/permissions.py pretix/control/navigation.py
msgid "Devices"
msgstr ""
@@ -8717,12 +8721,6 @@ msgid ""
"card."
msgstr ""
#: pretix/base/services/orders.py
msgid ""
"You cannot change the ticket secret of a position that has been used to "
"issue a gift card."
msgstr ""
#: pretix/base/services/orders.py
#, python-brace-format
msgid ""
@@ -10260,13 +10258,13 @@ msgstr ""
msgid "We'll show this publicly to allow attendees to contact you."
msgstr ""
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
#, fuzzy
#| msgid "Content"
msgid "Contact URL"
msgstr "Cynnwys"
#: pretix/base/settings.py
#: pretix/base/settings.py pretix/control/forms/event.py
msgid ""
"If you set this, the footer contact link will point here instead of using "
"the email address above. Please note that you still need to add a contact "
@@ -11753,7 +11751,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/control/templates/pretixcontrol/organizers/customer_membership.html
#: pretix/control/templates/pretixcontrol/organizers/customers.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
@@ -12951,7 +12948,7 @@ msgstr ""
msgid "Valid orders"
msgstr ""
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Paid (or canceled with paid fee)"
msgstr ""
@@ -12975,7 +12972,7 @@ msgstr ""
msgid "Cancellations"
msgstr "Dileadau"
#: pretix/control/forms/filter.py pretix/plugins/sendmail/forms.py
#: pretix/control/forms/filter.py
msgid "Canceled (fully)"
msgstr ""
@@ -13150,22 +13147,6 @@ msgstr ""
msgid "Inactive"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale not started"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
msgid "Presale over"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html
msgid "Date from"
@@ -13203,6 +13184,13 @@ msgstr ""
msgid "not yet activated"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/organizers/customer.html
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
#: pretix/presale/views/customer.py
msgid "Memberships"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Has no memberships"
msgstr ""
@@ -13430,38 +13418,6 @@ msgstr ""
msgid "Search email address or subject"
msgstr ""
#: pretix/control/forms/filter.py
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr "Tarddiad"
#: pretix/control/forms/filter.py
msgid "All sources"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Team actions"
msgstr ""
#: pretix/control/forms/filter.py
msgid "Customer actions"
msgstr ""
#: pretix/control/forms/filter.py
#, fuzzy
#| msgid "Redirection"
msgid "Device actions"
msgstr "Ailgyfeiriad"
#: pretix/control/forms/filter.py
msgid "User email"
msgstr ""
#: pretix/control/forms/filter.py pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/forms/global_settings.py
msgid "Additional footer text"
msgstr ""
@@ -14649,7 +14605,6 @@ msgstr "Dewisol"
#: pretix/control/forms/renderers.py
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgctxt "form_bulk"
msgid "change"
msgstr "newid"
@@ -14763,55 +14718,6 @@ msgid ""
"should show them."
msgstr ""
#: pretix/control/forms/vouchers.py
#, python-format
msgid ""
"You cannot reduce the maximum number of redemptions to %(max_usages)s, "
"because at least one of the selected vouchers has already been redeemed "
"%(max_redeemed)s times."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"You cannot create a voucher that allows selection of a quota but has no date "
"selected."
msgstr ""
#: pretix/control/forms/vouchers.py
#, fuzzy
#| msgid "The subevent does not belong to this event."
msgid "The selected quota does not match the selected subevent."
msgstr "Nid yw'r digwyddiad yn perthyn i'r digwyddiad hwn."
#: pretix/control/forms/vouchers.py
msgid "There is no sufficient quota available to perform this change."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the maximum number of usages in bulk is not supported if any of the "
"selected vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgctxt "subevent"
msgid ""
"Changing the date in bulk is not supported if any of the selected vouchers "
"is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"Changing the product to a quota is not supported if any of the selected "
"vouchers is assigned a seat."
msgstr ""
#: pretix/control/forms/vouchers.py
msgid ""
"This change cannot be completed because not all assigned seats of the "
"vouchers are still available"
msgstr ""
#: pretix/control/forms/vouchers.py
msgid "Codes"
msgstr ""
@@ -16518,6 +16424,10 @@ msgstr ""
msgid "Account history"
msgstr ""
#: pretix/control/navigation.py
msgid "All users"
msgstr ""
#: pretix/control/navigation.py
#: pretix/control/templates/pretixcontrol/user/staff_session_list.html
msgid "Admin sessions"
@@ -16786,7 +16696,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/users/create.html
#: pretix/control/templates/pretixcontrol/users/form.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
#: pretix/control/templates/pretixcontrol/waitinglist/edit.html
#: pretix/plugins/autocheckin/templates/pretixplugins/autocheckin/add.html
@@ -17210,8 +17119,8 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/checkin/checkins.html
#: pretix/control/templates/pretixcontrol/checkin/index.html
#: pretix/control/templates/pretixcontrol/checkin/lists.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
#: pretix/control/templates/pretixcontrol/items/question.html
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/orders/index.html
@@ -17222,6 +17131,7 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html
#: pretix/control/templates/pretixcontrol/organizers/index.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/organizers/reusable_media.html
#: pretix/control/templates/pretixcontrol/organizers/teams.html
@@ -17356,7 +17266,6 @@ msgstr "Stamp amser"
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/organizers/outgoing_mails.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
msgid "Select all results on other pages as well"
msgstr ""
@@ -17927,22 +17836,6 @@ msgstr ""
msgid "We found an SPF record on your domain that includes this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DKIM record should be set up as a CNAME record like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DKIM record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Your new DMARC record could look like this:"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "We found a DMARC record on your domain for this system. Great!"
msgstr ""
#: pretix/control/templates/pretixcontrol/email_setup_simple.html
msgid "Verification"
msgstr ""
@@ -18103,94 +17996,6 @@ msgid ""
"undeletable data, such as orders not performed in test mode."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_logs.html
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/dashboard_partial_warnings.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/delete.html
msgid ""
"This operation will destroy your event including all configuration, "
@@ -18305,6 +18110,69 @@ msgstr ""
msgid "Create QR code"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>overpaid orders</strong>, for example due to "
"duplicate payment attempts. You should review the cases and consider "
"refunding the overpaid amount to the user."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show overpaid orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending refunds</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show pending refunds"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>requested cancellations</strong> that you should "
"take care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders requesting cancellation"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>pending approvals</strong> that you should take "
"care of."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show orders pending approval"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"This event contains <strong>fully paid orders</strong> that are not marked "
"as paid, probably because no quota was left at the time their payment "
"arrived. You should review the cases and consider either refunding the "
"customer or creating more space."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show affected orders"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid ""
"Orders in this event could not be <strong>synced to an external system</"
"strong> as configured."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
msgid "Show sync problems"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/index.html
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Update comment"
@@ -18479,6 +18347,44 @@ msgstr ""
msgid "Enable test mode"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "All actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Team actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
msgid "Customer actions"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "Personal data was cleared from this log entry."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
msgid "This change was performed by a pretix administrator."
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/event/logs_embed.html
#: pretix/control/templates/pretixcontrol/includes/logs.html
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
#: pretix/control/templates/pretixcontrol/search/payments.html
msgid "Inspect"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/logs.html
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
#: pretix/control/templates/pretixcontrol/organizers/logs.html
@@ -19396,20 +19302,6 @@ msgstr ""
msgid "More quotas"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/views/dashboards.py
msgid "Shop disabled"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
#: pretix/control/templates/pretixcontrol/subevents/detail.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/views/dashboards.py
msgid "On sale"
msgstr ""
#: pretix/control/templates/pretixcontrol/events/index.html
#: pretix/control/templates/pretixcontrol/organizers/detail.html
msgid "Open event dashboard"
@@ -19420,10 +19312,6 @@ msgctxt "typography"
msgid "The quick brown fox jumps over the lazy dog."
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_log_filter_form.html
msgid "Specific object selected"
msgstr ""
#: pretix/control/templates/pretixcontrol/fragment_quota_box.html
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html
msgid "Quota:"
@@ -20454,7 +20342,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quota_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/subevents/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#, python-format
msgid "%(number)s selected"
msgstr ""
@@ -20545,7 +20432,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/items/quotas.html
#: pretix/control/templates/pretixcontrol/organizers/devices.html
#: pretix/control/templates/pretixcontrol/subevents/index.html
#: pretix/control/templates/pretixcontrol/vouchers/index.html
msgid "Edit selected"
msgstr ""
@@ -20842,13 +20728,6 @@ msgid ""
"the customer account."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
#: pretix/control/views/orders.py
msgid ""
"Ticket secrets of order positions that have been used to issue a gift card "
"can not be changed. Only the link will be changed in this case."
msgstr ""
#: pretix/control/templates/pretixcontrol/order/change.html
msgid ""
"Removing or splitting this position will also remove or split all add-ons to "
@@ -21263,6 +21142,11 @@ msgstr ""
msgid "Create a refund"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html
#: pretix/control/templates/pretixcontrol/orders/refunds.html
msgid "Source"
msgstr "Tarddiad"
#: pretix/control/templates/pretixcontrol/order/index.html
msgid "Cancel transfer"
msgstr ""
@@ -22353,7 +22237,6 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/device_bulk_edit.html
#: pretix/control/templates/pretixcontrol/organizers/device_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Advanced settings"
msgstr ""
@@ -24534,13 +24417,11 @@ msgid "Copy codes"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Voucher details"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid ""
"If you choose \"any product\" for a specific quota and choose to reserve "
@@ -24548,10 +24429,6 @@ msgid ""
"voucher holder if another quota associated with the product is sold out!"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/bulk_edit.html
msgid "Change multiple vouchers"
msgstr ""
#: pretix/control/templates/pretixcontrol/vouchers/delete.html
#: pretix/control/templates/pretixcontrol/vouchers/detail.html
msgid "Delete voucher"
@@ -24893,8 +24770,7 @@ msgstr ""
#, python-brace-format
msgid ""
"You cannot accept the invitation for \"{}\" as you already are part of this "
"team. If you want to add a different user or create a new account, log out "
"and click the invitation link again."
"team."
msgstr ""
#: pretix/control/views/auth.py
@@ -25505,30 +25381,6 @@ msgid ""
"record."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We could not find a CNAME record pointing to our DKIM key for domain you are "
"trying to use. This means that there is a very high change most of the "
"emails will be rejected or marked as spam. We strongly recommend setting up "
"DKIM through a CNAME record. You can do so through the DNS settings at the "
"provider you registered your domain with."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We found a CNAME record for a DKIM key, but it is not pointing to the right "
"location. This means that there is a very high chance most of the emails "
"will be rejected or marked as spam. You should update the DNS settings of "
"your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid ""
"We did not find DMARC record for your domain. This means that there is a "
"very high chance most of the emails will be rejected or marked as spam. You "
"should update the DNS settings of your domain."
msgstr ""
#: pretix/control/views/mailsetup.py
msgid "The verification code was incorrect, please try again."
msgstr ""
@@ -26592,7 +26444,6 @@ msgid "Macao"
msgstr ""
#: pretix/helpers/daterange.py
#: pretix/presale/templates/pretixpresale/event/fragment_subevent_list.html
#: pretix/presale/templates/pretixpresale/fragment_calendar.html
#: pretix/presale/templates/pretixpresale/fragment_week_calendar.html
#: pretix/presale/templates/pretixpresale/organizers/index.html
@@ -27789,6 +27640,11 @@ msgstr ""
msgid "PayPal account"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
#, python-brace-format
msgid "<a target=\"_blank\" rel=\"noopener\" href=\"{docs_url}\">{text}</a>"
msgstr ""
#: pretix/plugins/paypal/payment.py pretix/plugins/paypal2/payment.py
msgid "Click here for a tutorial on how to obtain the required keys"
msgstr ""
@@ -28425,13 +28281,8 @@ msgstr "Ailgyfeirio URIs"
#: pretix/plugins/returnurl/views.py
msgid ""
"Redirection will only be allowed to URLs that start with one of these "
"prefixes. Enter one allowed URL prefix per line. URL prefixes must include a "
"slash after the hostname."
msgstr ""
#: pretix/plugins/returnurl/views.py
msgid ""
"All values must be URLs that include at last one slash after the hostname."
"prefixes. Enter one or more allowed URL prefix per line. URL prefixes must "
"include a slash after the hostname."
msgstr ""
#: pretix/plugins/sendmail/apps.py
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
"PO-Revision-Date: 2026-07-31 18:00+0000\n"
"PO-Revision-Date: 2026-04-22 18:00+0000\n"
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
"da/>\n"
@@ -16,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 2026.7.1\n"
"X-Generator: Weblate 5.17\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
msgid "Marked as paid"
@@ -1306,62 +1306,62 @@ msgstr ""
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "January"
msgstr "januar"
msgstr "Januar"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "February"
msgstr "februar"
msgstr "Februar"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "March"
msgstr "marts"
msgstr "Marts"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "April"
msgstr "april"
msgstr "April"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "May"
msgstr "maj"
msgstr "Maj"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "June"
msgstr "juni"
msgstr "Juni"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "July"
msgstr "juli"
msgstr "Juli"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "August"
msgstr "august"
msgstr "August"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "September"
msgstr "september"
msgstr "September"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "October"
msgstr "oktober"
msgstr "Oktober"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "November"
msgstr "november"
msgstr "November"
#: pretix/static/pretixpresale/js/widget/widget.js
#: pretix/static/pretixpresale/widget/src/i18n.ts
msgid "December"
msgstr "december"
msgstr "December"
#~ msgid "iDEAL"
#~ msgstr "iDEAL"
File diff suppressed because it is too large Load Diff
-7
View File
@@ -94,8 +94,6 @@ Checkout
Chrome
Client-ID
Client-Secret
CNAME
CNAME-Eintrag
CODE.txt
Community-Übersetzungen
CONFIRM
@@ -125,11 +123,6 @@ Debug-Modus
dekodi
Desktop-Apps
Di
DKIM
DKIM-Eintrag
DKIM-Schlüssel
DMARC
DMARC-Eintrag
Do
Downloadformat
Dr
File diff suppressed because it is too large Load Diff
@@ -94,8 +94,6 @@ Checkout
Chrome
Client-ID
Client-Secret
CNAME
CNAME-Eintrag
CODE.txt
Community-Übersetzungen
CONFIRM
@@ -125,11 +123,6 @@ Debug-Modus
dekodi
Desktop-Apps
Di
DKIM
DKIM-Eintrag
DKIM-Schlüssel
DMARC
DMARC-Eintrag
Do
Downloadformat
Dr

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