mirror of
https://github.com/pretix/pretix.git
synced 2026-01-01 18:32:27 +00:00
Compare commits
56 Commits
datasync
...
ttf-pragra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c896adf48 | ||
|
|
36e4bfb7f2 | ||
|
|
99e10adad4 | ||
|
|
10b5f76356 | ||
|
|
39a0093c6b | ||
|
|
d8bf3d0b07 | ||
|
|
4e56ce8927 | ||
|
|
807df01f5d | ||
|
|
067e11c265 | ||
|
|
b4264c0ae7 | ||
|
|
61eff28978 | ||
|
|
4e89772c2d | ||
|
|
3212dd9b40 | ||
|
|
97c1fb9101 | ||
|
|
d5bccf8726 | ||
|
|
d768c46fa1 | ||
|
|
5a506bfbd6 | ||
|
|
3508d22591 | ||
|
|
4a6dd12884 | ||
|
|
60b906d8b7 | ||
|
|
4285612162 | ||
|
|
a3b1e4d208 | ||
|
|
3a6d7b8e92 | ||
|
|
a5d01aa2d1 | ||
|
|
89d8ca0fc2 | ||
|
|
34b656989f | ||
|
|
154f10af8f | ||
|
|
782d659c59 | ||
|
|
1b4308e101 | ||
|
|
9a119c35a8 | ||
|
|
a8ac1b1a94 | ||
|
|
6338dceb9e | ||
|
|
e4a171c11f | ||
|
|
e9edcfdfdc | ||
|
|
ef3ff52be3 | ||
|
|
a8f74d87ec | ||
|
|
6f920e6bcd | ||
|
|
a6201c841f | ||
|
|
b5ac28e36c | ||
|
|
bf5e1aeaff | ||
|
|
3f6d230c01 | ||
|
|
a4aa3cbd3b | ||
|
|
8ee90cd1c4 | ||
|
|
8d1e679a84 | ||
|
|
87f829f4d2 | ||
|
|
75dcb920a7 | ||
|
|
e68f0a7402 | ||
|
|
4255dbfb83 | ||
|
|
9def5cc7b2 | ||
|
|
17a467887c | ||
|
|
0736babf3c | ||
|
|
a5b773924c | ||
|
|
391918afe7 | ||
|
|
d8f9f9478d | ||
|
|
4d9f1a8efc | ||
|
|
23b07e29cd |
@@ -359,3 +359,65 @@ Performing a ticket search
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer or check-in list does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested check-in list does not exist.
|
||||
|
||||
.. _`rest-checkin-annul`:
|
||||
|
||||
Annulment of a check-in
|
||||
-----------------------
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/checkinrpc/annul/
|
||||
|
||||
If a check-in was made in error and the person was not let in, it can be annulled. We do not recommend this to be used
|
||||
in case of manual check-ins or user interfaces because it is too prone for human errors. It is mostly intended for
|
||||
automated entry systems like a turnstile or automated door, where the check-in is first created, then the door is
|
||||
opened, and then the check-in may be annulled if the system knows that the turnstile did not turn or was out of
|
||||
order.
|
||||
|
||||
This endpoint supports passing multiple check-in lists for the context of a multi-event scan. However, each
|
||||
check-in list passed needs to be from a distinct event.
|
||||
|
||||
Check-ins created by a device can only be annulled by the same device. The datetime of annulment may not be more than
|
||||
15 minutes after the datetime of check-in (value subject to change).
|
||||
|
||||
A status code of 404 is returned if no check-in was found for the given nonce. A status code of 400 is returned when
|
||||
multiple check-ins match the nonce, the input is invalid in another way, the annulment is made from the wrong device,
|
||||
the check-in is already in an annulled or failed state, or the datetime constraint is not valid.
|
||||
|
||||
:<json string nonce: ``nonce`` value of the original check-in.
|
||||
:<json array lists: List of check-in list IDs to search on. No two check-in lists may be from the same event.
|
||||
:<json datetime datetime: Specifies the client-side datetime of the annulment. If not supplied, the current time will be used.
|
||||
:<json string error_explanation: A human-readable description of why the check-in was annulled (optional).
|
||||
:>json string status: ``"ok"``
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/checkinrpc/annul/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
{
|
||||
"lists": [1],
|
||||
"nonce": "Pvrk50vUzQd0DhdpNRL4I4OcXsvg70uA",
|
||||
"error_explanation": "Turnstile did not turn"
|
||||
}
|
||||
|
||||
**Example successful response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status": "ok",
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: Invalid or incomplete request, see above
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested nonce does not exist.
|
||||
|
||||
200
doc/development/api/datasync.rst
Normal file
200
doc/development/api/datasync.rst
Normal file
@@ -0,0 +1,200 @@
|
||||
.. highlight:: python
|
||||
:linenothreshold: 5
|
||||
|
||||
Data sync providers
|
||||
===================
|
||||
|
||||
.. warning:: This feature is considered **experimental**. It might change at any time without prior notice.
|
||||
|
||||
pretix provides connectivity to many external services through plugins. A common requirement
|
||||
is unidirectionally sending (order, customer, ticket, ...) data into external systems.
|
||||
The transfer is usually triggered by signals provided by pretix core (e.g. :data:`order_placed`),
|
||||
but performed asynchronously.
|
||||
|
||||
Such plugins should use the :class:`OutboundSyncProvider` API to utilize the queueing, retry and mapping mechanisms as well as the user interface for configuration and monitoring.
|
||||
|
||||
An :class:`OutboundSyncProvider` for registering event participants in a mailing list could start
|
||||
like this, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pretix.base.datasync.datasync import OutboundSyncProvider
|
||||
|
||||
class MyListSyncProvider(OutboundSyncProvider):
|
||||
identifier = "my_list"
|
||||
display_name = "My Mailing List Service"
|
||||
# ...c
|
||||
|
||||
|
||||
The plugin must register listeners in `signals.py` for all signals that should to trigger a sync and
|
||||
within it has to call :meth:`MyListSyncProvider.enqueue_order` to enqueue the order for synchronization:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@receiver(order_placed, dispatch_uid="mylist_order_placed")
|
||||
def on_order_placed(sender, order, **kwargs):
|
||||
MyListSyncProvider.enqueue_order(order, "order_placed")
|
||||
|
||||
|
||||
Furthermore, most of these plugins need to translate data from some pretix objects (e.g. orders)
|
||||
into an external system's data structures. Sometimes, there is only one reasonable way or the
|
||||
plugin author makes an opinionated decision what information from which objects should be
|
||||
transferred into which data structures in the external system.
|
||||
|
||||
Otherwise, you can use a :class:`PropertyMappingFormSet` to let the user set up a mapping from pretix model fields
|
||||
to external data fields. You could store the mapping information either in the event settings, or in a separate
|
||||
data model. Your implementation of :attr:`OutboundSyncProvider.mappings`
|
||||
needs to provide a list of mappings, which can be e.g. static objects or model instances, as long as they
|
||||
have at least the properties defined in
|
||||
:class:`pretix.base.datasync.datasync.StaticMapping`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# class MyListSyncProvider, contd.
|
||||
def mappings(self):
|
||||
return [
|
||||
StaticMapping(
|
||||
id=1, pretix_model='Order', external_object_type='Contact',
|
||||
pretix_id_field='email', external_id_field='email',
|
||||
property_mappings=self.event.settings.mylist_order_mapping,
|
||||
))
|
||||
]
|
||||
|
||||
|
||||
Currently, we support `orders` and `order positions` as data sources, with the data fields defined in
|
||||
:func:`pretix.base.datasync.sourcefields.get_data_fields`.
|
||||
|
||||
To perform the actual sync, implement :func:`sync_object_with_properties` and optionally
|
||||
:func:`finalize_sync_order`. The former is called for each object to be created according to the ``mappings``.
|
||||
For each order that was enqueued using :func:`enqueue_order`:
|
||||
|
||||
- each Mapping with ``pretix_model == "Order"`` results in one call to :func:`sync_object_with_properties`,
|
||||
- each Mapping with ``pretix_model == "OrderPosition"`` results in one call to
|
||||
:func:`sync_object_with_properties` per order position,
|
||||
- :func:`finalize_sync_order` is called one time after all calls to :func:`sync_object_with_properties`.
|
||||
|
||||
|
||||
Implementation examples
|
||||
-----------------------
|
||||
|
||||
For example implementations, see the test cases in :mod:`tests.base.test_datasync`.
|
||||
|
||||
In :class:`SimpleOrderSync`, a basic data transfer of order data only is
|
||||
shown. Therein, a ``sync_object_with_properties`` method is defined as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pretix.base.datasync.utils import assign_properties
|
||||
|
||||
def sync_object_with_properties(
|
||||
self, external_id_field, id_value, properties: list, inputs: dict,
|
||||
mapping, mapped_objects: dict, **kwargs,
|
||||
):
|
||||
# First, we query the external service if our object-to-sync already exists there.
|
||||
# This is necessary to make sure our method is idempotent, i.e. handles already synced
|
||||
# data gracefully.
|
||||
pre_existing_object = self.fake_api_client.retrieve_object(
|
||||
mapping.external_object_type,
|
||||
external_id_field,
|
||||
id_value
|
||||
)
|
||||
|
||||
# We use the helper function ``assign_properties`` to update a pre-existing object.
|
||||
update_values = assign_properties(
|
||||
new_values=properties,
|
||||
old_values=pre_existing_object or {},
|
||||
is_new=pre_existing_object is None,
|
||||
list_sep=";",
|
||||
)
|
||||
|
||||
# Then we can send our new data to the external service. The specifics of course depends
|
||||
# on your API, e.g. you may need to use different endpoints for creating or updating an
|
||||
# object, or pass the identifier separately instead of in the same dictionary as the
|
||||
# other properties.
|
||||
result = self.fake_api_client.create_or_update_object(mapping.external_object_type, {
|
||||
**update_values,
|
||||
external_id_field: id_value,
|
||||
"_id": pre_existing_object and pre_existing_object.get("_id"),
|
||||
})
|
||||
|
||||
# Finally, return a dictionary containing at least `object_type`, `external_id_field`,
|
||||
# `id_value`, `external_link_href`, and `external_link_display_name` keys.
|
||||
# Further keys may be provided for your internal use. This dictionary is provided
|
||||
# in following calls in the ``mapped_objects`` dict, to allow creating associations
|
||||
# to this object.
|
||||
return {
|
||||
"object_type": mapping.external_object_type,
|
||||
"external_id_field": external_id_field,
|
||||
"id_value": id_value,
|
||||
"external_link_href": f"https://example.org/external-system/{mapping.external_object_type}/{id_value}/",
|
||||
"external_link_display_name": f"Contact #{id_value} - Jane Doe",
|
||||
"my_result": result,
|
||||
}
|
||||
|
||||
.. note:: The result dictionaries of earlier invocations of :func:`sync_object_with_properties` are
|
||||
only provided in subsequent calls of the same sync run, such that a mapping can
|
||||
refer to e.g. the external id of an object created by a preceding mapping.
|
||||
However, the result dictionaries are currently not provided across runs. This will
|
||||
likely change in a future revision of this API, to allow easier integration of external
|
||||
systems that do not allow retrieving/updating data by a pretix-provided key.
|
||||
|
||||
``mapped_objects`` is a dictionary of lists of dictionaries. The keys to the dictionary are
|
||||
the mapping identifiers (``mapping.id``), the lists contain the result dictionaries returned
|
||||
by :func:`sync_object_with_properties`.
|
||||
|
||||
|
||||
In :class:`OrderAndTicketAssociationSync`, an example is given where orders, order positions,
|
||||
and the association between them are transferred.
|
||||
|
||||
|
||||
The OutboundSyncProvider base class
|
||||
-----------------------------------
|
||||
|
||||
.. autoclass:: pretix.base.datasync.datasync.OutboundSyncProvider
|
||||
:members:
|
||||
|
||||
|
||||
Property mapping format
|
||||
-----------------------
|
||||
|
||||
To allow the user to configure property mappings, you can use the PropertyMappingFormSet,
|
||||
which will generate the required ``property_mappings`` value automatically. If you need
|
||||
to specify the property mappings programmatically, you can refer to the description below
|
||||
on their format.
|
||||
|
||||
.. autoclass:: pretix.control.forms.mapping.PropertyMappingFormSet
|
||||
:members: to_property_mappings_json
|
||||
|
||||
A simple JSON-serialized ``property_mappings`` list for mapping some order information can look like this:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"pretix_field": "email",
|
||||
"external_field": "orderemail",
|
||||
"value_map": "",
|
||||
"overwrite": "overwrite",
|
||||
},
|
||||
{
|
||||
"pretix_field": "order_status",
|
||||
"external_field": "status",
|
||||
"value_map": "{\"n\": \"pending\", \"p\": \"paid\", \"e\": \"expired\", \"c\": \"canceled\", \"r\": \"refunded\"}",
|
||||
"overwrite": "overwrite",
|
||||
},
|
||||
{
|
||||
"pretix_field": "order_total",
|
||||
"external_field": "total",
|
||||
"value_map": "",
|
||||
"overwrite": "overwrite",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Translating mappings on Event copy
|
||||
----------------------------------
|
||||
|
||||
Property mappings can contain references to event-specific primary keys. Therefore, plugins must register to the
|
||||
event_copy_data signal and call translate_property_mappings on all property mappings they store.
|
||||
|
||||
.. autofunction:: pretix.base.datasync.utils.translate_property_mappings
|
||||
@@ -30,7 +30,7 @@ Check-ins
|
||||
|
||||
.. automodule:: pretix.base.signals
|
||||
:no-index:
|
||||
:members: checkin_created
|
||||
:members: checkin_created, checkin_annulled
|
||||
|
||||
|
||||
Frontend
|
||||
|
||||
@@ -18,5 +18,6 @@ Contents:
|
||||
customview
|
||||
cookieconsent
|
||||
auth
|
||||
datasync
|
||||
general
|
||||
quality
|
||||
|
||||
@@ -33,10 +33,10 @@ dependencies = [
|
||||
"celery==5.5.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=44.0.0",
|
||||
"css-inline==0.16.*",
|
||||
"css-inline==0.17.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"Django[argon2]==4.2.*,>=4.2.15",
|
||||
"django-bootstrap3==25.1",
|
||||
"django-bootstrap3==25.2",
|
||||
"django-compressor==4.5.1",
|
||||
"django-countries==7.6.*",
|
||||
"django-filter==25.1",
|
||||
@@ -81,17 +81,17 @@ dependencies = [
|
||||
"pycountry",
|
||||
"pycparser==2.22",
|
||||
"pycryptodome==3.23.*",
|
||||
"pypdf==5.8.*",
|
||||
"pypdf==5.9.*",
|
||||
"python-bidi==0.6.*", # Support for Arabic in reportlab
|
||||
"python-dateutil==2.9.*",
|
||||
"pytz",
|
||||
"pytz-deprecation-shim==0.1.*",
|
||||
"pyuca",
|
||||
"qrcode==8.2",
|
||||
"redis==6.2.*",
|
||||
"redis==6.3.*",
|
||||
"reportlab==4.4.*",
|
||||
"requests==2.31.*",
|
||||
"sentry-sdk==2.31.*",
|
||||
"sentry-sdk==2.34.*",
|
||||
"sepaxml==2.6.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
|
||||
@@ -104,3 +104,14 @@ class MiniCheckinListSerializer(I18nAwareModelSerializer):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CheckinRPCAnnulInputSerializer(serializers.Serializer):
|
||||
lists = serializers.PrimaryKeyRelatedField(required=True, many=True, queryset=CheckinList.objects.none())
|
||||
nonce = serializers.CharField(required=True, allow_null=False)
|
||||
datetime = serializers.DateTimeField(required=False, allow_null=True)
|
||||
error_explanation = serializers.CharField(required=False, allow_null=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['lists'].child_relation.queryset = CheckinList.objects.filter(event__in=self.context['events']).select_related('event')
|
||||
|
||||
@@ -132,6 +132,8 @@ urlpatterns = [
|
||||
name="checkinrpc.redeem"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/checkinrpc/search/$', checkin.CheckinRPCSearchView.as_view(),
|
||||
name="checkinrpc.search"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/checkinrpc/annul/$', checkin.CheckinRPCAnnulView.as_view(),
|
||||
name="checkinrpc.annul"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/settings/$', organizer.OrganizerSettingsView.as_view(),
|
||||
name="organizer.settings"),
|
||||
re_path(r'^organizers/(?P<organizer>[^/]+)/giftcards/(?P<giftcard>[^/]+)/', include(giftcard_router.urls)),
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import operator
|
||||
from datetime import timedelta
|
||||
from functools import reduce
|
||||
|
||||
import django_filters
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError as BaseValidationError
|
||||
from django.db import transaction
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import (
|
||||
Count, Exists, F, Max, OrderBy, OuterRef, Prefetch, Q, Subquery,
|
||||
prefetch_related_objects,
|
||||
@@ -39,17 +40,19 @@ from django.utils.translation import gettext
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from packaging.version import parse
|
||||
from rest_framework import views, viewsets
|
||||
from rest_framework import status, views, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.exceptions import (
|
||||
NotFound, PermissionDenied, ValidationError,
|
||||
)
|
||||
from rest_framework.fields import DateTimeField
|
||||
from rest_framework.generics import ListAPIView
|
||||
from rest_framework.permissions import SAFE_METHODS
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.serializers.checkin import (
|
||||
CheckinListSerializer, CheckinRPCRedeemInputSerializer,
|
||||
MiniCheckinListSerializer,
|
||||
CheckinListSerializer, CheckinRPCAnnulInputSerializer,
|
||||
CheckinRPCRedeemInputSerializer, MiniCheckinListSerializer,
|
||||
)
|
||||
from pretix.api.serializers.item import QuestionSerializer
|
||||
from pretix.api.serializers.order import (
|
||||
@@ -66,6 +69,8 @@ from pretix.base.models.orders import PrintLog
|
||||
from pretix.base.services.checkin import (
|
||||
CheckInError, RequiredQuestionsError, SQLLogic, perform_checkin,
|
||||
)
|
||||
from pretix.base.signals import checkin_annulled
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
with scopes_disabled():
|
||||
class CheckinListFilter(FilterSet):
|
||||
@@ -999,3 +1004,79 @@ class CheckinRPCSearchView(ListAPIView):
|
||||
qs = qs.none()
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class CheckinRPCAnnulView(views.APIView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
if isinstance(self.request.auth, (TeamAPIToken, Device)):
|
||||
events = self.request.auth.get_events_with_permission(('can_change_orders', 'can_checkin_orders'))
|
||||
elif self.request.user.is_authenticated:
|
||||
events = self.request.user.get_events_with_permission(('can_change_orders', 'can_checkin_orders'), self.request).filter(
|
||||
organizer=self.request.organizer
|
||||
)
|
||||
else:
|
||||
raise ValueError("unknown authentication method")
|
||||
|
||||
s = CheckinRPCAnnulInputSerializer(data=request.data, context={'events': events})
|
||||
s.is_valid(raise_exception=True)
|
||||
|
||||
with transaction.atomic():
|
||||
try:
|
||||
qs = Checkin.all.all()
|
||||
if isinstance(request.auth, Device):
|
||||
qs = qs.filter(device=request.auth)
|
||||
ci = qs.select_for_update(
|
||||
of=OF_SELF,
|
||||
).select_related("position", "position__order", "position__order__event").get(
|
||||
list__in=s.validated_data['lists'],
|
||||
nonce=s.validated_data['nonce'],
|
||||
)
|
||||
if connection.features.has_select_for_update_of and ci.position_id:
|
||||
# Lock position as well, can't do it with of= above because relation is nullable
|
||||
OrderPosition.objects.select_for_update(of=OF_SELF).get(pk=ci.position_id)
|
||||
|
||||
if not ci.successful or not ci.position:
|
||||
raise ValidationError("Cannot annul an unsuccessful checkin")
|
||||
except Checkin.DoesNotExist:
|
||||
raise NotFound("No check-in found based on nonce")
|
||||
except Checkin.MultipleObjectsReturned:
|
||||
raise ValidationError("Multiple check-ins found based on nonce")
|
||||
|
||||
annulment_time = s.validated_data.get("datetime") or now()
|
||||
|
||||
if annulment_time - ci.datetime > timedelta(minutes=15):
|
||||
# Compare to sent datetime, which makes this cheatable, but allows offline annulment of checkins
|
||||
ci.position.order.log_action('pretix.event.checkin.annulment.ignored', data={
|
||||
'checkin': ci.pk,
|
||||
'position': ci.position.id,
|
||||
'positionid': ci.position.positionid,
|
||||
'datetime': annulment_time,
|
||||
'error_explanation': s.validated_data.get("error_explanation"),
|
||||
'type': ci.type,
|
||||
'list': ci.list_id,
|
||||
}, user=request.user, auth=request.auth)
|
||||
return Response({
|
||||
"non_field_errors": ["Annulment is not allowed more than 15 minutes after check-in"]
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if ci.device and ci.device != request.auth:
|
||||
return Response({
|
||||
"non_field_errors": ["Annulment is only allowed from the same device"]
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
ci.successful = False
|
||||
ci.error_reason = Checkin.REASON_ANNULLED
|
||||
ci.error_explanation = s.validated_data.get("error_explanation")
|
||||
ci.save(update_fields=["successful", "error_reason", "error_explanation"])
|
||||
ci.position.order.log_action('pretix.event.checkin.annulled', data={
|
||||
'checkin': ci.pk,
|
||||
'position': ci.position.id,
|
||||
'positionid': ci.position.positionid,
|
||||
'datetime': annulment_time,
|
||||
'error_explanation': s.validated_data.get("error_explanation"),
|
||||
'type': ci.type,
|
||||
'list': ci.list_id,
|
||||
}, user=request.user, auth=request.auth)
|
||||
checkin_annulled.send(ci.position.order.event, checkin=ci)
|
||||
|
||||
return Response({"status": "ok"}, status=status.HTTP_200_OK)
|
||||
|
||||
@@ -46,7 +46,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import invoice # NOQA
|
||||
from . import notifications # NOQA
|
||||
from . import email # NOQA
|
||||
from .services import auth, checkin, currencies, export, mail, tickets, cart, modelimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
|
||||
from .services import auth, checkin, currencies, datasync, export, mail, tickets, cart, modelimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
|
||||
from .models import _transactions # NOQA
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
|
||||
params['client_id'] = provider.configuration['client_id']
|
||||
params['client_secret'] = provider.configuration['client_secret']
|
||||
|
||||
resp = None
|
||||
try:
|
||||
resp = requests.post(
|
||||
endpoint,
|
||||
@@ -214,7 +215,10 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
except RequestException:
|
||||
logger.exception('Could not retrieve authorization token')
|
||||
if resp:
|
||||
logger.exception(f'Could not retrieve authorization token. Response: {resp.text}')
|
||||
else:
|
||||
logger.exception('Could not retrieve authorization token')
|
||||
raise ValidationError(
|
||||
_('Login was not successful. Error message: "{error}".').format(
|
||||
error='could not reach login provider',
|
||||
@@ -222,6 +226,7 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
|
||||
)
|
||||
|
||||
if 'access_token' not in data:
|
||||
logger.error(f'Could not find access token. Response: {data}')
|
||||
raise ValidationError(
|
||||
_('Login was not successful. Error message: "{error}".').format(
|
||||
error='access token missing',
|
||||
@@ -229,6 +234,7 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
|
||||
)
|
||||
|
||||
endpoint = provider.configuration['provider_config']['userinfo_endpoint']
|
||||
resp = None
|
||||
try:
|
||||
# https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
||||
resp = requests.get(
|
||||
@@ -240,7 +246,10 @@ def oidc_validate_authorization(provider, code, redirect_uri, pkce_code_verifier
|
||||
resp.raise_for_status()
|
||||
userinfo = resp.json()
|
||||
except RequestException:
|
||||
logger.exception('Could not retrieve user info')
|
||||
if resp:
|
||||
logger.exception(f'Could not retrieve user info. Response: {resp.text}')
|
||||
else:
|
||||
logger.exception('Could not retrieve user info')
|
||||
raise ValidationError(
|
||||
_('Login was not successful. Error message: "{error}".').format(
|
||||
error='could not fetch user info',
|
||||
|
||||
21
src/pretix/base/datasync/__init__.py
Normal file
21
src/pretix/base/datasync/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
436
src/pretix/base/datasync/datasync.py
Normal file
436
src/pretix/base/datasync/datasync.py
Normal file
@@ -0,0 +1,436 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import json
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from datetime import timedelta
|
||||
from functools import cached_property
|
||||
from typing import List, Optional, Protocol
|
||||
|
||||
import sentry_sdk
|
||||
from django.db import DatabaseError, transaction
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.datasync.sourcefields import (
|
||||
EVENT, EVENT_OR_SUBEVENT, ORDER, ORDER_POSITION, get_data_fields,
|
||||
)
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.logentrytype_registry import make_link
|
||||
from pretix.base.models.datasync import OrderSyncQueue, OrderSyncResult
|
||||
from pretix.base.signals import EventPluginRegistry
|
||||
from pretix.helpers import OF_SELF
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
datasync_providers = EventPluginRegistry({"identifier": lambda o: o.identifier})
|
||||
|
||||
|
||||
class BaseSyncError(Exception):
|
||||
def __init__(self, messages, full_message=None):
|
||||
self.messages = messages
|
||||
self.full_message = full_message
|
||||
|
||||
|
||||
class UnrecoverableSyncError(BaseSyncError):
|
||||
"""
|
||||
A SyncProvider encountered a permanent problem, where a retry will not be successful.
|
||||
"""
|
||||
failure_mode = "permanent"
|
||||
|
||||
|
||||
class SyncConfigError(UnrecoverableSyncError):
|
||||
"""
|
||||
A SyncProvider is misconfigured in a way where a retry without configuration change will
|
||||
not be successful.
|
||||
"""
|
||||
failure_mode = "config"
|
||||
|
||||
|
||||
class RecoverableSyncError(BaseSyncError):
|
||||
"""
|
||||
A SyncProvider has encountered a temporary problem, and the sync should be retried
|
||||
at a later time.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ObjectMapping(Protocol):
|
||||
id: int
|
||||
pretix_model: str
|
||||
external_object_type: str
|
||||
pretix_id_field: str
|
||||
external_id_field: str
|
||||
property_mappings: str
|
||||
|
||||
|
||||
StaticMapping = namedtuple('StaticMapping', ('id', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mappings'))
|
||||
|
||||
|
||||
class OutboundSyncProvider:
|
||||
max_attempts = 5
|
||||
|
||||
def __init__(self, event):
|
||||
self.event = event
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
@classmethod
|
||||
@property
|
||||
def display_name(cls):
|
||||
return str(cls.identifier)
|
||||
|
||||
@classmethod
|
||||
def enqueue_order(cls, order, triggered_by, not_before=None):
|
||||
"""
|
||||
Adds an order to the sync queue. May only be called on derived classes which define an ``identifier`` attribute.
|
||||
|
||||
Should be called in the appropriate signal receivers, e.g.::
|
||||
|
||||
@receiver(order_placed, dispatch_uid="mysync_order_placed")
|
||||
def on_order_placed(sender, order, **kwargs):
|
||||
MySyncProvider.enqueue_order(order, "order_placed")
|
||||
|
||||
:param order: the Order that should be synced
|
||||
:param triggered_by: the reason why the order should be synced, e.g. name of the signal
|
||||
(currently only used internally for logging)
|
||||
"""
|
||||
if not hasattr(cls, 'identifier'):
|
||||
raise TypeError('Call this method on a derived class that defines an "identifier" attribute.')
|
||||
OrderSyncQueue.objects.update_or_create(
|
||||
order=order,
|
||||
sync_provider=cls.identifier,
|
||||
in_flight=False,
|
||||
defaults={
|
||||
"event": order.event,
|
||||
"triggered_by": triggered_by,
|
||||
"not_before": not_before or now(),
|
||||
"need_manual_retry": None,
|
||||
},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_external_link_info(cls, event, external_link_href, external_link_display_name):
|
||||
return {
|
||||
"href": external_link_href,
|
||||
"val": external_link_display_name,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_external_link_html(cls, event, external_link_href, external_link_display_name):
|
||||
info = cls.get_external_link_info(event, external_link_href, external_link_display_name)
|
||||
return make_link(info, '{val}')
|
||||
|
||||
def next_retry_date(self, sq):
|
||||
"""
|
||||
Optionally override to configure a different retry backoff behavior
|
||||
"""
|
||||
return now() + timedelta(hours=1)
|
||||
|
||||
def should_sync_order(self, order):
|
||||
"""
|
||||
Optionally override this method to exclude certain orders from sync by returning ``False``
|
||||
"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def mappings(self):
|
||||
"""
|
||||
Implementations must override this property to provide the data mappings as a list of objects.
|
||||
|
||||
They can return instances of the ``StaticMapping`` `namedtuple` defined above, or create their own
|
||||
class (e.g. a Django model).
|
||||
|
||||
:return: The returned objects must have at least the following properties:
|
||||
|
||||
- `id`: Unique identifier for this mapping. If the mappings are Django models, the database primary key
|
||||
should be used. This may be referenced in other mappings, to establish relations between objects.
|
||||
- `pretix_model`: Which pretix model to use as data source in this mapping. Possible values are
|
||||
the keys of ``sourcefields.AVAILABLE_MODELS``
|
||||
- `external_object_type`: Destination object type in the target system. opaque string of maximum 128 characters.
|
||||
- `pretix_id_field`: Which pretix data field should be used to identify the mapped object. Any ``DataFieldInfo.key``
|
||||
returned by ``sourcefields.get_data_fields()`` for the combination of ``Event`` and ``pretix_model``.
|
||||
- `external_id_field`: Destination identifier field in the target system.
|
||||
- `property_mappings`: Mapping configuration as generated by ``PropertyMappingFormSet.to_property_mappings_list()``.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def sync_queued_orders(self, queued_orders):
|
||||
"""
|
||||
This method should catch all Exceptions and handle them appropriately. It should never throw
|
||||
an Exception, as that may block the entire queue.
|
||||
"""
|
||||
for queue_item in queued_orders:
|
||||
with transaction.atomic():
|
||||
try:
|
||||
sq = (
|
||||
OrderSyncQueue.objects
|
||||
.select_for_update(of=OF_SELF, nowait=True)
|
||||
.select_related("order")
|
||||
.get(pk=queue_item.pk)
|
||||
)
|
||||
if sq.in_flight:
|
||||
continue
|
||||
sq.in_flight = True
|
||||
sq.in_flight_since = now()
|
||||
sq.save()
|
||||
except DatabaseError:
|
||||
# Either select_for_update failed to lock the row, or we couldn't set in_flight
|
||||
# as this order is already in flight (UNIQUE violation). In either case, we ignore
|
||||
# this order for now.
|
||||
continue
|
||||
|
||||
try:
|
||||
mapped_objects = self.sync_order(sq.order)
|
||||
if not all(all(not res or res.sync_info.get("action", "") == "nothing_to_do" for res in res_list) for res_list in mapped_objects.values()):
|
||||
sq.order.log_action("pretix.event.order.data_sync.success", {
|
||||
"provider": self.identifier,
|
||||
"objects": {
|
||||
mapping_id: [osr and osr.to_result_dict() for osr in results]
|
||||
for mapping_id, results in mapped_objects.items()
|
||||
},
|
||||
})
|
||||
sq.delete()
|
||||
except UnrecoverableSyncError as e:
|
||||
sq.set_sync_error(e.failure_mode, e.messages, e.full_message)
|
||||
except RecoverableSyncError as e:
|
||||
sq.failed_attempts += 1
|
||||
sq.not_before = self.next_retry_date(sq)
|
||||
# model changes saved by set_sync_error / clear_in_flight calls below
|
||||
if sq.failed_attempts >= self.max_attempts:
|
||||
logger.exception('Failed to sync order (max attempts exceeded)')
|
||||
sentry_sdk.capture_exception(e)
|
||||
sq.set_sync_error("exceeded", e.messages, e.full_message)
|
||||
else:
|
||||
logger.info(
|
||||
f"Could not sync order {sq.order.code} to {type(self).__name__} "
|
||||
f"(transient error, attempt #{sq.failed_attempts}, next {sq.not_before})",
|
||||
exc_info=True,
|
||||
)
|
||||
sq.clear_in_flight()
|
||||
except Exception as e:
|
||||
logger.exception('Failed to sync order (unhandled exception)')
|
||||
sentry_sdk.capture_exception(e)
|
||||
sq.set_sync_error("internal", [], str(e))
|
||||
|
||||
@cached_property
|
||||
def data_fields(self):
|
||||
return {
|
||||
f.key: f
|
||||
for f in get_data_fields(self.event)
|
||||
}
|
||||
|
||||
def get_field_value(self, inputs, mapping_entry):
|
||||
key = mapping_entry["pretix_field"]
|
||||
try:
|
||||
field = self.data_fields[key]
|
||||
except KeyError:
|
||||
with language(self.event.settings.locale):
|
||||
raise SyncConfigError([_(
|
||||
'Field "{field_name}" is not valid for {available_inputs}. Please check your {provider_name} settings.'
|
||||
).format(key=key, available_inputs="/".join(inputs.keys()), provider_name=self.display_name)])
|
||||
input = inputs[field.required_input]
|
||||
val = field.getter(input)
|
||||
if isinstance(val, list):
|
||||
if field.enum_opts and mapping_entry.get("value_map"):
|
||||
map = json.loads(mapping_entry["value_map"])
|
||||
try:
|
||||
val = [map[el] for el in val]
|
||||
except KeyError:
|
||||
with language(self.event.settings.locale):
|
||||
raise SyncConfigError([_(
|
||||
'Please update value mapping for field "{field_name}" - option "{val}" not assigned'
|
||||
).format(field_name=key, val=val)])
|
||||
|
||||
val = ",".join(val)
|
||||
return val
|
||||
|
||||
def get_properties(self, inputs: dict, property_mappings: List[dict]):
|
||||
return [
|
||||
(m["external_field"], self.get_field_value(inputs, m), m["overwrite"])
|
||||
for m in property_mappings
|
||||
]
|
||||
|
||||
def sync_object_with_properties(
|
||||
self,
|
||||
external_id_field: str,
|
||||
id_value,
|
||||
properties: list,
|
||||
inputs: dict,
|
||||
mapping: ObjectMapping,
|
||||
mapped_objects: dict,
|
||||
**kwargs,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
This method is called for each object that needs to be created/updated in the external system -- which these are is
|
||||
determined by the implementation of the `mapping` property.
|
||||
|
||||
:param external_id_field: Identifier field in the external system as provided in ``mapping.external_identifier``
|
||||
:param id_value: Identifier contents as retrieved from the property specified by ``mapping.pretix_identifier`` of the model
|
||||
specified by ``mapping.pretix_model``
|
||||
:param properties: All properties defined in ``mapping.property_mappings``, as list of three-tuples
|
||||
``(external_field, value, overwrite)``
|
||||
:param inputs: All pretix model instances from which data can be retrieved for this mapping.
|
||||
Dictionary mapping from sourcefields.ORDER_POSITION, .ORDER, .EVENT, .EVENT_OR_SUBEVENT to the
|
||||
relevant Django model.
|
||||
Most providers don't need to use this parameter directly, as `properties` and `id_value`
|
||||
already contain the values as evaluated from the available inputs.
|
||||
:param mapping: The mapping object as returned by ``self.mappings``
|
||||
:param mapped_objects: Information about objects that were synced in the same sync run, by mapping definitions
|
||||
*before* the current one in order of ``self.mappings``.
|
||||
Type is a dictionary ``{mapping.id: [list of OrderSyncResult objects]}``
|
||||
Useful to create associations between objects in the target system.
|
||||
|
||||
Example code to create return value::
|
||||
|
||||
return {
|
||||
# optional:
|
||||
"action": "nothing_to_do", # to inform that no action was taken, because the data was already up-to-date.
|
||||
# other values for action (e.g. create, update) currently have no special
|
||||
# meaning, but are visible for debugging purposes to admins.
|
||||
|
||||
# optional:
|
||||
"external_link_href": "https://external-system.example.com/backend/link/to/contact/123/",
|
||||
"external_link_display_name": "Contact #123 - Jane Doe",
|
||||
"...optionally further values you need in mapped_objects for association": 123456789,
|
||||
}
|
||||
|
||||
The return value needs to be a JSON serializable dict, or None.
|
||||
|
||||
Return None only in case you decide this object should not be synced at all in this mapping. Do not return None in
|
||||
case the object is already up-to-date in the target system (return "action": "nothing_to_do" instead).
|
||||
|
||||
This method needs to be idempotent, i.e. calling it multiple times with the same input values should create
|
||||
only a single object in the target system.
|
||||
|
||||
Subsequent calls with the same mapping and id_value should update the existing object, instead of creating a new one.
|
||||
In a SQL database, you might use an `INSERT OR UPDATE` or `UPSERT` statement; many REST APIs provide an equivalent API call.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def sync_object(
|
||||
self,
|
||||
inputs: dict,
|
||||
mapping,
|
||||
mapped_objects: dict,
|
||||
):
|
||||
logger.debug("Syncing object %r, %r, %r", inputs, mapping, mapped_objects)
|
||||
properties = self.get_properties(inputs, mapping.property_mappings)
|
||||
logger.debug("Properties: %r", properties)
|
||||
|
||||
id_value = self.get_field_value(inputs, {"pretix_field": mapping.pretix_id_field})
|
||||
if not id_value:
|
||||
return None
|
||||
|
||||
info = self.sync_object_with_properties(
|
||||
external_id_field=mapping.external_id_field,
|
||||
id_value=id_value,
|
||||
properties=properties,
|
||||
inputs=inputs,
|
||||
mapping=mapping,
|
||||
mapped_objects=mapped_objects,
|
||||
)
|
||||
if not info:
|
||||
return None
|
||||
external_link_href = info.pop('external_link_href', None)
|
||||
external_link_display_name = info.pop('external_link_display_name', None)
|
||||
obj, created = OrderSyncResult.objects.update_or_create(
|
||||
order=inputs.get(ORDER), order_position=inputs.get(ORDER_POSITION), sync_provider=self.identifier,
|
||||
mapping_id=mapping.id,
|
||||
defaults=dict(
|
||||
external_object_type=mapping.external_object_type,
|
||||
external_id_field=mapping.external_id_field,
|
||||
id_value=id_value,
|
||||
external_link_href=external_link_href,
|
||||
external_link_display_name=external_link_display_name,
|
||||
sync_info=info,
|
||||
transmitted=now(),
|
||||
)
|
||||
)
|
||||
return obj
|
||||
|
||||
def sync_order(self, order):
|
||||
if not self.should_sync_order(order):
|
||||
logger.debug("Skipping order %r", order)
|
||||
return
|
||||
|
||||
logger.debug("Syncing order %r", order)
|
||||
positions = list(
|
||||
order.all_positions
|
||||
.prefetch_related("answers", "answers__question")
|
||||
.select_related(
|
||||
"voucher",
|
||||
)
|
||||
)
|
||||
order_inputs = {ORDER: order, EVENT: self.event}
|
||||
mapped_objects = {}
|
||||
for mapping in self.mappings:
|
||||
if mapping.pretix_model == 'Order':
|
||||
mapped_objects[mapping.id] = [
|
||||
self.sync_object(order_inputs, mapping, mapped_objects)
|
||||
]
|
||||
elif mapping.pretix_model == 'OrderPosition':
|
||||
mapped_objects[mapping.id] = [
|
||||
self.sync_object({
|
||||
**order_inputs, EVENT_OR_SUBEVENT: op.subevent or self.event, ORDER_POSITION: op
|
||||
}, mapping, mapped_objects)
|
||||
for op in positions
|
||||
]
|
||||
else:
|
||||
raise SyncConfigError("Invalid pretix model '{}'".format(mapping.pretix_model))
|
||||
self.finalize_sync_order(order)
|
||||
return mapped_objects
|
||||
|
||||
def filter_mapped_objects(self, mapped_objects, inputs):
|
||||
"""
|
||||
For order positions, only
|
||||
"""
|
||||
if ORDER_POSITION in inputs:
|
||||
return {
|
||||
mapping_id: [
|
||||
osr for osr in results
|
||||
if osr and (osr.order_position_id is None or osr.order_position_id == inputs[ORDER_POSITION].id)
|
||||
]
|
||||
for mapping_id, results in mapped_objects.items()
|
||||
}
|
||||
else:
|
||||
return mapped_objects
|
||||
|
||||
def finalize_sync_order(self, order):
|
||||
"""
|
||||
Called after ``sync_object`` has been called successfully for all objects of a specific order. Can
|
||||
be used for saving bulk information per order.
|
||||
"""
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Called after all orders of an event have been synced. Can be used for clean-up tasks (e.g. closing
|
||||
a session).
|
||||
"""
|
||||
pass
|
||||
547
src/pretix/base/datasync/sourcefields.py
Normal file
547
src/pretix/base/datasync/sourcefields.py
Normal file
@@ -0,0 +1,547 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from collections import namedtuple
|
||||
from functools import partial
|
||||
|
||||
from django.db.models import Max, Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.models import Checkin, InvoiceAddress, Order, Question
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
|
||||
|
||||
def get_answer(op, question_identifier=None):
|
||||
a = None
|
||||
if op.addon_to:
|
||||
if "answers" in getattr(op.addon_to, "_prefetched_objects_cache", {}):
|
||||
try:
|
||||
a = [
|
||||
a
|
||||
for a in op.addon_to.answers.all()
|
||||
if a.question.identifier == question_identifier
|
||||
][0]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
a = op.addon_to.answers.filter(
|
||||
question__identifier=question_identifier
|
||||
).first()
|
||||
|
||||
if "answers" in getattr(op, "_prefetched_objects_cache", {}):
|
||||
try:
|
||||
a = [
|
||||
a
|
||||
for a in op.answers.all()
|
||||
if a.question.identifier == question_identifier
|
||||
][0]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
a = op.answers.filter(question__identifier=question_identifier).first()
|
||||
|
||||
if not a:
|
||||
return ""
|
||||
else:
|
||||
if a.question.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
|
||||
return [str(o.identifier) for o in a.options.all()]
|
||||
if a.question.type == Question.TYPE_BOOLEAN:
|
||||
return a.answer == "True"
|
||||
return a.answer
|
||||
|
||||
|
||||
def get_payment_date(order):
|
||||
if order.status == Order.STATUS_PENDING:
|
||||
return None
|
||||
|
||||
return isoformat_or_none(order.payments.aggregate(m=Max("payment_date"))["m"])
|
||||
|
||||
|
||||
def isoformat_or_none(dt):
|
||||
return dt and dt.isoformat()
|
||||
|
||||
|
||||
def first_checkin_on_list(list_pk, position):
|
||||
checkin = position.checkins.filter(
|
||||
list__pk=list_pk, type=Checkin.TYPE_ENTRY
|
||||
).first()
|
||||
if checkin:
|
||||
return isoformat_or_none(checkin.datetime)
|
||||
|
||||
|
||||
def split_name_on_last_space(name, part):
|
||||
name_parts = name.rsplit(" ", 1)
|
||||
return name_parts[part] if len(name_parts) > part else ""
|
||||
|
||||
|
||||
def normalize_email(email):
|
||||
if email:
|
||||
local, host = email.split("@")
|
||||
host = host.encode("idna").decode()
|
||||
return f"{local}@{host}"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
ORDER_POSITION = 'position'
|
||||
ORDER = 'order'
|
||||
EVENT = 'event'
|
||||
EVENT_OR_SUBEVENT = 'event_or_subevent'
|
||||
AVAILABLE_MODELS = {
|
||||
'OrderPosition': (ORDER_POSITION, ORDER, EVENT_OR_SUBEVENT, EVENT),
|
||||
'Order': (ORDER, EVENT),
|
||||
}
|
||||
|
||||
|
||||
DataFieldInfo = namedtuple(
|
||||
'DataFieldInfo',
|
||||
field_names=('required_input', 'key', 'label', 'type', 'enum_opts', 'getter', 'deprecated'),
|
||||
defaults=[False]
|
||||
)
|
||||
|
||||
|
||||
def get_invoice_address_or_empty(order):
|
||||
try:
|
||||
return order.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
return InvoiceAddress()
|
||||
|
||||
|
||||
def get_data_fields(event, for_model=None):
|
||||
"""
|
||||
Returns tuple of (required_input, key, label, type, enum_opts, getter)
|
||||
|
||||
Type is one of the Question types as defined in Question.TYPE_CHOICES.
|
||||
|
||||
The data type of the return value of `getter` depends on `type`:
|
||||
- TYPE_CHOICE_MULTIPLE: list of strings
|
||||
- TYPE_CHOICE: list, containing zero or one strings
|
||||
- TYPE_BOOLEAN: boolean
|
||||
- all other (including TYPE_NUMBER): string
|
||||
"""
|
||||
name_scheme = PERSON_NAME_SCHEMES[event.settings.name_scheme]
|
||||
name_headers = []
|
||||
if name_scheme and len(name_scheme["fields"]) > 1:
|
||||
for k, label, w in name_scheme["fields"]:
|
||||
name_headers.append(label)
|
||||
|
||||
src_fields = (
|
||||
[
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_name",
|
||||
_("Attendee name"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: position.attendee_name
|
||||
or (position.addon_to.attendee_name if position.addon_to else None),
|
||||
),
|
||||
]
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_name_" + k,
|
||||
_("Attendee") + ": " + label,
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
partial(
|
||||
lambda k, position: (
|
||||
position.attendee_name_parts
|
||||
or (position.addon_to.attendee_name_parts if position.addon_to else {})
|
||||
or {}
|
||||
).get(k, ""),
|
||||
k,
|
||||
),
|
||||
deprecated=len(name_scheme["fields"]) == 1,
|
||||
)
|
||||
for k, label, w in name_scheme["fields"]
|
||||
]
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_email",
|
||||
_("Attendee email"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: normalize_email(
|
||||
position.attendee_email
|
||||
or (position.addon_to.attendee_email if position.addon_to else None)
|
||||
),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_or_order_email",
|
||||
_("Attendee or order email"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: normalize_email(
|
||||
position.attendee_email
|
||||
or (position.addon_to.attendee_email if position.addon_to else None)
|
||||
or position.order.email
|
||||
),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_company",
|
||||
_("Attendee company"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: position.company or (position.addon_to.company if position.addon_to else None),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_street",
|
||||
_("Attendee address street"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: position.street or (position.addon_to.street if position.addon_to else None),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_zipcode",
|
||||
_("Attendee address ZIP code"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: position.zipcode or (position.addon_to.zipcode if position.addon_to else None),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_city",
|
||||
_("Attendee address city"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: position.city or (position.addon_to.city if position.addon_to else None),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_country",
|
||||
_("Attendee address country"),
|
||||
Question.TYPE_COUNTRYCODE,
|
||||
None,
|
||||
lambda position: str(
|
||||
position.country or (position.addon_to.attendee_name if position.addon_to else "")
|
||||
),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_company",
|
||||
_("Invoice address company"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: get_invoice_address_or_empty(order).company,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_name",
|
||||
_("Invoice address name"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: get_invoice_address_or_empty(order).name,
|
||||
),
|
||||
]
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_name_" + k,
|
||||
_("Invoice address") + ": " + label,
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
partial(
|
||||
lambda k, order: (get_invoice_address_or_empty(order).name_parts or {}).get(
|
||||
k, ""
|
||||
),
|
||||
k,
|
||||
),
|
||||
deprecated=len(name_scheme["fields"]) == 1,
|
||||
)
|
||||
for k, label, w in name_scheme["fields"]
|
||||
]
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_street",
|
||||
_("Invoice address street"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: get_invoice_address_or_empty(order).street,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_zipcode",
|
||||
_("Invoice address ZIP code"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: get_invoice_address_or_empty(order).zipcode,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_city",
|
||||
_("Invoice address city"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: get_invoice_address_or_empty(order).city,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_country",
|
||||
_("Invoice address country"),
|
||||
Question.TYPE_COUNTRYCODE,
|
||||
None,
|
||||
lambda order: str(get_invoice_address_or_empty(order).country),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"email",
|
||||
_("Order email"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: normalize_email(order.email),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"order_code",
|
||||
_("Order code"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: order.code,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"event_order_code",
|
||||
_("Event and order code"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: order.full_code,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"order_total",
|
||||
_("Order total"),
|
||||
Question.TYPE_NUMBER,
|
||||
None,
|
||||
lambda order: str(order.total),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"product",
|
||||
_("Product and variation name"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: str(
|
||||
str(position.item.internal_name or position.item.name)
|
||||
+ ((" – " + str(position.variation.value)) if position.variation else "")
|
||||
),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"product_id",
|
||||
_("Product ID"),
|
||||
Question.TYPE_NUMBER,
|
||||
None,
|
||||
lambda position: str(position.item.pk),
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT,
|
||||
"event_slug",
|
||||
_("Event short form"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda event: str(event.slug),
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT,
|
||||
"event_name",
|
||||
_("Event name"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda event: str(event.name),
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT_OR_SUBEVENT,
|
||||
"event_date_from",
|
||||
_("Event start date"),
|
||||
Question.TYPE_DATETIME,
|
||||
None,
|
||||
lambda event_or_subevent: isoformat_or_none(event_or_subevent.date_from),
|
||||
),
|
||||
DataFieldInfo(
|
||||
EVENT_OR_SUBEVENT,
|
||||
"event_date_to",
|
||||
_("Event end date"),
|
||||
Question.TYPE_DATETIME,
|
||||
None,
|
||||
lambda event_or_subevent: isoformat_or_none(event_or_subevent.date_to),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"voucher_code",
|
||||
_("Voucher code"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: position.voucher.code if position.voucher_id else "",
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"ticket_id",
|
||||
_("Order code and position number"),
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: position.code,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"ticket_price",
|
||||
_("Ticket price"),
|
||||
Question.TYPE_NUMBER,
|
||||
None,
|
||||
lambda position: str(position.price),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"order_status",
|
||||
_("Order status"),
|
||||
Question.TYPE_CHOICE,
|
||||
Order.STATUS_CHOICE,
|
||||
lambda order: [order.status],
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"ticket_status",
|
||||
_("Ticket status"),
|
||||
Question.TYPE_CHOICE,
|
||||
Order.STATUS_CHOICE,
|
||||
lambda position: [Order.STATUS_CANCELED if position.canceled else position.order.status],
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"order_date",
|
||||
_("Order date and time"),
|
||||
Question.TYPE_DATETIME,
|
||||
None,
|
||||
lambda order: order.datetime.isoformat(),
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"payment_date",
|
||||
_("Payment date and time"),
|
||||
Question.TYPE_DATETIME,
|
||||
None,
|
||||
get_payment_date,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"order_locale",
|
||||
_("Order language code"),
|
||||
Question.TYPE_CHOICE,
|
||||
[(lc, lc) for lc in event.settings.locales],
|
||||
lambda order: [order.locale],
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"position_id",
|
||||
_("Order position ID"),
|
||||
Question.TYPE_NUMBER,
|
||||
None,
|
||||
lambda op: str(op.pk),
|
||||
),
|
||||
]
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"checkin_date_" + str(cl.pk),
|
||||
_("Check-in datetime on list {}").format(cl.name),
|
||||
Question.TYPE_DATETIME,
|
||||
None,
|
||||
partial(first_checkin_on_list, cl.pk),
|
||||
)
|
||||
for cl in event.checkin_lists.all()
|
||||
]
|
||||
+ [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"question_" + q.identifier,
|
||||
_("Question: {name}").format(name=str(q.question)),
|
||||
q.type,
|
||||
get_enum_opts(q),
|
||||
partial(lambda qq, position: get_answer(position, qq.identifier), q),
|
||||
)
|
||||
for q in event.questions.filter(~Q(type=Question.TYPE_FILE)).prefetch_related("options")
|
||||
]
|
||||
)
|
||||
if not any(field_name == "given_name" for field_name, label, weight in name_scheme["fields"]):
|
||||
src_fields += [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_name_given_name",
|
||||
_("Attendee") + ": " + _("Given name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: split_name_on_last_space(position.attendee_name, part=0),
|
||||
deprecated=True,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_name_given_name",
|
||||
_("Invoice address") + ": " + _("Given name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: split_name_on_last_space(get_invoice_address_or_empty(order).name, part=0),
|
||||
deprecated=True,
|
||||
),
|
||||
]
|
||||
|
||||
if not any(field_name == "family_name" for field_name, label, weight in name_scheme["fields"]):
|
||||
src_fields += [
|
||||
DataFieldInfo(
|
||||
ORDER_POSITION,
|
||||
"attendee_name_family_name",
|
||||
_("Attendee") + ": " + _("Family name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda position: split_name_on_last_space(position.attendee_name, part=1),
|
||||
deprecated=True,
|
||||
),
|
||||
DataFieldInfo(
|
||||
ORDER,
|
||||
"invoice_address_name_family_name",
|
||||
_("Invoice address") + ": " + _("Family name") + " (⚠️ auto-generated, not recommended)",
|
||||
Question.TYPE_STRING,
|
||||
None,
|
||||
lambda order: split_name_on_last_space(get_invoice_address_or_empty(order).name, part=1),
|
||||
deprecated=True,
|
||||
),
|
||||
]
|
||||
|
||||
if for_model:
|
||||
available_inputs = AVAILABLE_MODELS[for_model]
|
||||
return [
|
||||
f for f in src_fields if f.required_input in available_inputs
|
||||
]
|
||||
else:
|
||||
return src_fields
|
||||
|
||||
|
||||
def get_enum_opts(q):
|
||||
if q.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
|
||||
return [(opt.identifier, opt.answer) for opt in q.options.all()]
|
||||
else:
|
||||
return None
|
||||
123
src/pretix/base/datasync/utils.py
Normal file
123
src/pretix/base/datasync/utils.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import List, Tuple
|
||||
|
||||
from pretix.base.datasync.datasync import SyncConfigError
|
||||
from pretix.base.models.datasync import (
|
||||
MODE_APPEND_LIST, MODE_OVERWRITE, MODE_SET_IF_EMPTY, MODE_SET_IF_NEW,
|
||||
)
|
||||
|
||||
|
||||
def assign_properties(
|
||||
new_values: List[Tuple[str, str, str]], old_values: dict, is_new, list_sep
|
||||
):
|
||||
"""
|
||||
Generates a dictionary mapping property keys to new values, handling conditional overwrites and list updates
|
||||
according to an update mode specified per property.
|
||||
|
||||
Supported update modes are:
|
||||
- `MODE_OVERWRITE`: Replaces the existing value with the new value.
|
||||
- `MODE_SET_IF_NEW`: Only sets the property if `is_new` is True.
|
||||
- `MODE_SET_IF_EMPTY`: Only sets the property if the field is empty or missing in old_values.
|
||||
- `MODE_APPEND_LIST`: Appends the new value to the list from old_values (or the empty list if missing),
|
||||
using `list_sep` as a separator.
|
||||
|
||||
:param new_values: List of tuples, where each tuple contains (field_name, new_value, update_mode).
|
||||
:param old_values: Dictionary, current property values in the external system.
|
||||
:param is_new: Boolean, whether the object will be newly created in the external system.
|
||||
:param list_sep: If string, used as a separator for MODE_APPEND_LIST. If None, native lists are used.
|
||||
:raises SyncConfigError: If an invalid update mode is specified.
|
||||
:returns: A dictionary containing the properties that need to be updated in the external system.
|
||||
"""
|
||||
|
||||
out = {}
|
||||
|
||||
for field_name, new_value, update_mode in new_values:
|
||||
if update_mode == MODE_OVERWRITE:
|
||||
out[field_name] = new_value
|
||||
continue
|
||||
elif update_mode == MODE_SET_IF_NEW and not is_new:
|
||||
continue
|
||||
if not new_value:
|
||||
continue
|
||||
|
||||
current_value = old_values.get(field_name, out.get(field_name, ""))
|
||||
if update_mode in (MODE_SET_IF_EMPTY, MODE_SET_IF_NEW):
|
||||
if not current_value:
|
||||
out[field_name] = new_value
|
||||
elif update_mode == MODE_APPEND_LIST:
|
||||
_add_to_list(out, field_name, current_value, new_value, list_sep)
|
||||
else:
|
||||
raise SyncConfigError(["Invalid update mode " + update_mode])
|
||||
return out
|
||||
|
||||
|
||||
def _add_to_list(out, field_name, current_value, new_item, list_sep):
|
||||
new_item = str(new_item)
|
||||
if list_sep is not None:
|
||||
new_item = new_item.replace(list_sep, "")
|
||||
current_value = current_value.split(list_sep) if current_value else []
|
||||
elif not isinstance(current_value, (list, tuple)):
|
||||
current_value = [str(current_value)]
|
||||
if new_item not in current_value:
|
||||
new_list = current_value + [new_item]
|
||||
if list_sep is not None:
|
||||
new_list = list_sep.join(new_list)
|
||||
out[field_name] = new_list
|
||||
|
||||
|
||||
def translate_property_mappings(property_mappings, checkin_list_map):
|
||||
"""
|
||||
To properly handle copied events, users of data fields as provided by get_data_fields need to register to the
|
||||
event_copy_data signal and translate all stored references to those fields using this method.
|
||||
|
||||
For example, if you store your mappings in a custom Django model with a ForeignKey to Event:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@receiver(signal=event_copy_data, dispatch_uid="my_sync_event_copy_data")
|
||||
def event_copy_data_receiver(sender, other, checkin_list_map, **kwargs):
|
||||
object_mappings = other.my_object_mappings.all()
|
||||
object_mapping_map = {}
|
||||
for om in object_mappings:
|
||||
om = copy.copy(om)
|
||||
object_mapping_map[om.pk] = om
|
||||
om.pk = None
|
||||
om.event = sender
|
||||
om.property_mappings = translate_property_mappings(om.property_mappings, checkin_list_map)
|
||||
om.save()
|
||||
|
||||
"""
|
||||
mappings = []
|
||||
|
||||
for mapping in property_mappings:
|
||||
pretix_field = mapping["pretix_field"]
|
||||
if pretix_field.startswith("checkin_date_"):
|
||||
old_id = int(pretix_field[len("checkin_date_"):])
|
||||
if old_id not in checkin_list_map:
|
||||
# old_id might not be in checkin_list_map, because copying of an event series only copies check-in
|
||||
# lists covering the whole series, not individual dates.
|
||||
pretix_field = "_invalid_" + pretix_field
|
||||
else:
|
||||
pretix_field = "checkin_date_%d" % checkin_list_map[old_id].pk
|
||||
mappings.append({**mapping, "pretix_field": pretix_field})
|
||||
return mappings
|
||||
@@ -668,6 +668,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
_('External customer ID'),
|
||||
_('Check-in lists'),
|
||||
_('Payment providers'),
|
||||
_('Position order link')
|
||||
]
|
||||
|
||||
# get meta_data labels from first cached event
|
||||
@@ -812,6 +813,14 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
if p and p != 'free'
|
||||
]))
|
||||
|
||||
row.append(
|
||||
build_absolute_uri(order.event, 'presale:event.order.position', kwargs={
|
||||
'order': order.code,
|
||||
'secret': op.web_secret,
|
||||
'position': op.positionid
|
||||
})
|
||||
)
|
||||
|
||||
if has_subevents:
|
||||
if op.subevent:
|
||||
row += op.subevent.meta_data.values()
|
||||
|
||||
@@ -48,7 +48,7 @@ from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.platypus import (
|
||||
BaseDocTemplate, Flowable, Frame, KeepTogether, NextPageTemplate,
|
||||
PageTemplate, Paragraph, Spacer, Table, TableStyle,
|
||||
PageTemplate, Spacer, Table, TableStyle,
|
||||
)
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
@@ -56,7 +56,9 @@ from pretix.base.models import Event, Invoice, Order, OrderPayment
|
||||
from pretix.base.services.currencies import SOURCE_NAMES
|
||||
from pretix.base.signals import register_invoice_renderers
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.helpers.reportlab import ThumbnailingImageReader, reshaper
|
||||
from pretix.helpers.reportlab import (
|
||||
FontFallbackParagraph, ThumbnailingImageReader, reshaper,
|
||||
)
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -235,16 +237,17 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
||||
italic='OpenSansIt', boldItalic='OpenSansBI')
|
||||
|
||||
for family, styles in get_fonts(event=self.event, pdf_support_required=True).items():
|
||||
pdfmetrics.registerFont(TTFont(family, finders.find(styles['regular']['truetype'])))
|
||||
if family == self.event.settings.invoice_renderer_font:
|
||||
pdfmetrics.registerFont(TTFont(family, finders.find(styles['regular']['truetype'])))
|
||||
self.font_regular = family
|
||||
if 'italic' in styles:
|
||||
pdfmetrics.registerFont(TTFont(family + ' I', finders.find(styles['italic']['truetype'])))
|
||||
if 'bold' in styles:
|
||||
pdfmetrics.registerFont(TTFont(family + ' B', finders.find(styles['bold']['truetype'])))
|
||||
self.font_bold = family + ' B'
|
||||
if 'bolditalic' in styles:
|
||||
pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype'])))
|
||||
if 'italic' in styles:
|
||||
pdfmetrics.registerFont(TTFont(family + ' I', finders.find(styles['italic']['truetype'])))
|
||||
if 'bold' in styles:
|
||||
pdfmetrics.registerFont(TTFont(family + ' B', finders.find(styles['bold']['truetype'])))
|
||||
if 'bolditalic' in styles:
|
||||
pdfmetrics.registerFont(TTFont(family + ' B I', finders.find(styles['bolditalic']['truetype'])))
|
||||
|
||||
def _normalize(self, text):
|
||||
# reportlab does not support unicode combination characters
|
||||
@@ -393,8 +396,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
invoice_to_top = 52 * mm
|
||||
|
||||
def _draw_invoice_to(self, canvas):
|
||||
p = Paragraph(self._clean_text(self.invoice.address_invoice_to),
|
||||
style=self.stylesheet['Normal'])
|
||||
p = FontFallbackParagraph(self._clean_text(self.invoice.address_invoice_to),
|
||||
style=self.stylesheet['Normal'])
|
||||
p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height)
|
||||
p_size = p.wrap(self.invoice_to_width, self.invoice_to_height)
|
||||
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - p_size[1] - self.invoice_to_top)
|
||||
@@ -405,7 +408,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
invoice_from_top = 17 * mm
|
||||
|
||||
def _draw_invoice_from(self, canvas):
|
||||
p = Paragraph(
|
||||
p = FontFallbackParagraph(
|
||||
self._clean_text(self.invoice.full_invoice_from),
|
||||
style=self.stylesheet['InvoiceFrom']
|
||||
)
|
||||
@@ -523,12 +526,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
def shorten(txt):
|
||||
txt = str(txt)
|
||||
txt = bleach.clean(txt, tags=set()).strip()
|
||||
p = Paragraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
||||
p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
||||
p_size = p.wrap(self.event_width, self.event_height)
|
||||
|
||||
while p_size[1] > 2 * self.stylesheet['Normal'].leading:
|
||||
txt = ' '.join(txt.replace('…', '').split()[:-1]) + '…'
|
||||
p = Paragraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
||||
p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
||||
p_size = p.wrap(self.event_width, self.event_height)
|
||||
return txt
|
||||
|
||||
@@ -554,7 +557,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
else:
|
||||
p_str = shorten(self.invoice.event.name)
|
||||
|
||||
p = Paragraph(self._normalize(p_str.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
||||
p = FontFallbackParagraph(self._normalize(p_str.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
||||
p.wrapOn(canvas, self.event_width, self.event_height)
|
||||
p_size = p.wrap(self.event_width, self.event_height)
|
||||
p.drawOn(canvas, self.event_left, self.pagesize[1] - self.event_top - p_size[1])
|
||||
@@ -608,7 +611,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
def _get_intro(self):
|
||||
story = []
|
||||
if self.invoice.custom_field:
|
||||
story.append(Paragraph(
|
||||
story.append(FontFallbackParagraph(
|
||||
'{}: {}'.format(
|
||||
self._clean_text(str(self.invoice.event.settings.invoice_address_custom_field)),
|
||||
self._clean_text(self.invoice.custom_field),
|
||||
@@ -617,7 +620,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
))
|
||||
|
||||
if self.invoice.internal_reference:
|
||||
story.append(Paragraph(
|
||||
story.append(FontFallbackParagraph(
|
||||
self._normalize(pgettext('invoice', 'Customer reference: {reference}').format(
|
||||
reference=self._clean_text(self.invoice.internal_reference),
|
||||
)),
|
||||
@@ -625,14 +628,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
))
|
||||
|
||||
if self.invoice.invoice_to_vat_id:
|
||||
story.append(Paragraph(
|
||||
story.append(FontFallbackParagraph(
|
||||
self._normalize(pgettext('invoice', 'Customer VAT ID')) + ': ' +
|
||||
self._clean_text(self.invoice.invoice_to_vat_id),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
|
||||
if self.invoice.invoice_to_beneficiary:
|
||||
story.append(Paragraph(
|
||||
story.append(FontFallbackParagraph(
|
||||
self._normalize(pgettext('invoice', 'Beneficiary')) + ':<br />' +
|
||||
self._clean_text(self.invoice.invoice_to_beneficiary),
|
||||
self.stylesheet['Normal']
|
||||
@@ -644,7 +647,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
if story:
|
||||
story.append(Spacer(1, 5 * mm))
|
||||
|
||||
story.append(Paragraph(
|
||||
story.append(FontFallbackParagraph(
|
||||
self._clean_text(self.invoice.introductory_text, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
@@ -657,7 +660,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
|
||||
story = [
|
||||
NextPageTemplate('FirstPage'),
|
||||
Paragraph(
|
||||
FontFallbackParagraph(
|
||||
self._normalize(
|
||||
pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU'
|
||||
else pgettext('invoice', 'Invoice')
|
||||
@@ -683,17 +686,17 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
]
|
||||
if has_taxes:
|
||||
tdata = [(
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['BoldRightNoSplit']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Net')), self.stylesheet['BoldRightNoSplit']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Gross')), self.stylesheet['BoldRightNoSplit']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['BoldRightNoSplit']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net')), self.stylesheet['BoldRightNoSplit']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross')), self.stylesheet['BoldRightNoSplit']),
|
||||
)]
|
||||
else:
|
||||
tdata = [(
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Amount')), self.stylesheet['BoldRightNoSplit']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Amount')), self.stylesheet['BoldRightNoSplit']),
|
||||
)]
|
||||
|
||||
def _group_key(line):
|
||||
@@ -715,14 +718,20 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
)
|
||||
description = description + "\n" + single_price_line
|
||||
tdata.append((
|
||||
Paragraph(
|
||||
FontFallbackParagraph(
|
||||
self._clean_text(description, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
str(len(lines)),
|
||||
localize(tax_rate) + " %",
|
||||
Paragraph(money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']),
|
||||
Paragraph(money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']),
|
||||
FontFallbackParagraph(
|
||||
money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
||||
self.stylesheet['NormalRight']
|
||||
),
|
||||
FontFallbackParagraph(
|
||||
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
||||
self.stylesheet['NormalRight']
|
||||
),
|
||||
))
|
||||
else:
|
||||
if len(lines) > 1:
|
||||
@@ -731,12 +740,15 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
)
|
||||
description = description + "\n" + single_price_line
|
||||
tdata.append((
|
||||
Paragraph(
|
||||
FontFallbackParagraph(
|
||||
self._clean_text(description, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
str(len(lines)),
|
||||
Paragraph(money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '), self.stylesheet['NormalRight']),
|
||||
FontFallbackParagraph(
|
||||
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
||||
self.stylesheet['NormalRight']
|
||||
),
|
||||
))
|
||||
taxvalue_map[tax_rate, tax_name] += (gross_value - net_value) * len(lines)
|
||||
grossvalue_map[tax_rate, tax_name] += gross_value * len(lines)
|
||||
@@ -744,13 +756,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
|
||||
if has_taxes:
|
||||
tdata.append([
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', '', '',
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', '', '',
|
||||
money_filter(total, self.invoice.event.currency)
|
||||
])
|
||||
colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)]
|
||||
else:
|
||||
tdata.append([
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '',
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '',
|
||||
money_filter(total, self.invoice.event.currency)
|
||||
])
|
||||
colwidths = [a * doc.width for a in (.65, .20, .15)]
|
||||
@@ -760,12 +772,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
pending_sum = self.invoice.order.pending_sum
|
||||
if pending_sum != total:
|
||||
tdata.append(
|
||||
[Paragraph(self._normalize(pgettext('invoice', 'Received payments')), self.stylesheet['Normal'])] +
|
||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Received payments')), self.stylesheet['Normal'])] +
|
||||
(['', '', ''] if has_taxes else ['']) +
|
||||
[money_filter(pending_sum - total, self.invoice.event.currency)]
|
||||
)
|
||||
tdata.append(
|
||||
[Paragraph(self._normalize(pgettext('invoice', 'Outstanding payments')), self.stylesheet['Bold'])] +
|
||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Outstanding payments')), self.stylesheet['Bold'])] +
|
||||
(['', '', ''] if has_taxes else ['']) +
|
||||
[money_filter(pending_sum, self.invoice.event.currency)]
|
||||
)
|
||||
@@ -782,12 +794,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
s=Sum('amount')
|
||||
)['s'] or Decimal('0.00')
|
||||
tdata.append(
|
||||
[Paragraph(self._normalize(pgettext('invoice', 'Paid by gift card')), self.stylesheet['Normal'])] +
|
||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Paid by gift card')), self.stylesheet['Normal'])] +
|
||||
(['', '', ''] if has_taxes else ['']) +
|
||||
[money_filter(giftcard_sum, self.invoice.event.currency)]
|
||||
)
|
||||
tdata.append(
|
||||
[Paragraph(self._normalize(pgettext('invoice', 'Remaining amount')), self.stylesheet['Bold'])] +
|
||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Remaining amount')), self.stylesheet['Bold'])] +
|
||||
(['', '', ''] if has_taxes else ['']) +
|
||||
[money_filter(total - giftcard_sum, self.invoice.event.currency)]
|
||||
)
|
||||
@@ -810,7 +822,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
story.append(Spacer(1, 10 * mm))
|
||||
|
||||
if self.invoice.payment_provider_text:
|
||||
story.append(Paragraph(
|
||||
story.append(FontFallbackParagraph(
|
||||
self._normalize(self.invoice.payment_provider_text),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
@@ -819,7 +831,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
story.append(Spacer(1, 3 * mm))
|
||||
|
||||
if self.invoice.additional_text:
|
||||
story.append(Paragraph(
|
||||
story.append(FontFallbackParagraph(
|
||||
self._clean_text(self.invoice.additional_text, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
@@ -835,10 +847,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
('FONTNAME', (0, 0), (-1, -1), self.font_regular),
|
||||
]
|
||||
thead = [
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['Fineprint']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Net value')), self.stylesheet['FineprintRight']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Gross value')), self.stylesheet['FineprintRight']),
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Tax')), self.stylesheet['FineprintRight']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['Fineprint']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net value')), self.stylesheet['FineprintRight']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross value')), self.stylesheet['FineprintRight']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax')), self.stylesheet['FineprintRight']),
|
||||
''
|
||||
]
|
||||
tdata = [thead]
|
||||
@@ -849,7 +861,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
continue
|
||||
tax = taxvalue_map[idx]
|
||||
tdata.append([
|
||||
Paragraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
|
||||
FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
|
||||
money_filter(gross - tax, self.invoice.event.currency),
|
||||
money_filter(gross, self.invoice.event.currency),
|
||||
money_filter(tax, self.invoice.event.currency),
|
||||
@@ -868,7 +880,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
table.setStyle(TableStyle(tstyledata))
|
||||
story.append(Spacer(5 * mm, 5 * mm))
|
||||
story.append(KeepTogether([
|
||||
Paragraph(self._normalize(pgettext('invoice', 'Included taxes')), self.stylesheet['FineprintHeading']),
|
||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Included taxes')), self.stylesheet['FineprintHeading']),
|
||||
table
|
||||
]))
|
||||
|
||||
@@ -885,7 +897,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
net = gross - tax
|
||||
|
||||
tdata.append([
|
||||
Paragraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
|
||||
FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
|
||||
fmt(net), fmt(gross), fmt(tax), ''
|
||||
])
|
||||
|
||||
@@ -894,7 +906,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
|
||||
story.append(KeepTogether([
|
||||
Spacer(1, height=2 * mm),
|
||||
Paragraph(
|
||||
FontFallbackParagraph(
|
||||
self._normalize(pgettext(
|
||||
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
|
||||
'{date}, this corresponds to:'
|
||||
@@ -909,7 +921,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
elif self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate:
|
||||
foreign_total = round_decimal(total * self.invoice.foreign_currency_rate)
|
||||
story.append(Spacer(1, 5 * mm))
|
||||
story.append(Paragraph(self._normalize(
|
||||
story.append(FontFallbackParagraph(self._normalize(
|
||||
pgettext(
|
||||
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
|
||||
'{date}, the invoice total corresponds to {total}.'
|
||||
@@ -962,7 +974,7 @@ class Modern1Renderer(ClassicInvoiceRenderer):
|
||||
self._clean_text(l)
|
||||
for l in self.invoice.address_invoice_from.strip().split('\n')
|
||||
]
|
||||
p = Paragraph(self._normalize(' · '.join(c)), style=self.stylesheet['Sender'])
|
||||
p = FontFallbackParagraph(self._normalize(' · '.join(c)), style=self.stylesheet['Sender'])
|
||||
p.wrapOn(canvas, self.invoice_to_width, 15.7 * mm)
|
||||
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - self.invoice_to_top + 2 * mm)
|
||||
super()._draw_invoice_from(canvas)
|
||||
@@ -1021,7 +1033,7 @@ class Modern1Renderer(ClassicInvoiceRenderer):
|
||||
_draw(pgettext('invoice', 'Order code'), self.invoice.order.full_code, value_size, self.left_margin, 45 * mm, **kwargs)
|
||||
]
|
||||
|
||||
p = Paragraph(
|
||||
p = FontFallbackParagraph(
|
||||
self._normalize(date_format(self.invoice.date, "DATE_FORMAT")),
|
||||
style=ParagraphStyle(name=f'Normal{value_size}', fontName=self.font_regular, fontSize=value_size, leading=value_size * 1.2)
|
||||
)
|
||||
@@ -1079,7 +1091,7 @@ class Modern1SimplifiedRenderer(Modern1Renderer):
|
||||
i = []
|
||||
|
||||
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
|
||||
i.append(Paragraph(
|
||||
i.append(FontFallbackParagraph(
|
||||
pgettext('invoice', 'Event date: {date_range}').format(
|
||||
date_range=self.invoice.event.get_date_range_display(),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Generated by Django 4.2.21 on 2025-06-27 13:32
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0283_taxrule_default_taxrule_backfill'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrderSyncResult',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('sync_provider', models.CharField(max_length=128)),
|
||||
('mapping_id', models.IntegerField()),
|
||||
('external_object_type', models.CharField(max_length=128)),
|
||||
('external_id_field', models.CharField(max_length=128)),
|
||||
('id_value', models.CharField(max_length=128)),
|
||||
('external_link_href', models.CharField(max_length=255, null=True)),
|
||||
('external_link_display_name', models.CharField(max_length=255, null=True)),
|
||||
('transmitted', models.DateTimeField(auto_now_add=True)),
|
||||
('sync_info', models.JSONField()),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sync_results', to='pretixbase.order')),
|
||||
('order_position', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sync_results', to='pretixbase.orderposition')),
|
||||
],
|
||||
options={
|
||||
'indexes': [models.Index(fields=['order', 'sync_provider'], name='pretixbase__order_i_3e3c84_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrderSyncQueue',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('sync_provider', models.CharField(max_length=128)),
|
||||
('triggered_by', models.CharField(max_length=128)),
|
||||
('triggered', models.DateTimeField(auto_now_add=True)),
|
||||
('failed_attempts', models.PositiveIntegerField(default=0)),
|
||||
('not_before', models.DateTimeField(db_index=True)),
|
||||
('need_manual_retry', models.CharField(null=True, max_length=20)),
|
||||
('in_flight', models.BooleanField(default=False)),
|
||||
('in_flight_since', models.DateTimeField(blank=True, null=True)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='queued_sync_jobs', to='pretixbase.event')),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='queued_sync_jobs', to='pretixbase.order')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('triggered',),
|
||||
'unique_together': {('order', 'sync_provider', 'in_flight')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -350,6 +350,7 @@ class Checkin(models.Model):
|
||||
REASON_BLOCKED = 'blocked'
|
||||
REASON_UNAPPROVED = 'unapproved'
|
||||
REASON_INVALID_TIME = 'invalid_time'
|
||||
REASON_ANNULLED = 'annulled'
|
||||
REASONS = (
|
||||
(REASON_CANCELED, _('Order canceled')),
|
||||
(REASON_INVALID, _('Unknown ticket')),
|
||||
@@ -364,6 +365,7 @@ class Checkin(models.Model):
|
||||
(REASON_BLOCKED, _('Ticket blocked')),
|
||||
(REASON_UNAPPROVED, _('Order not approved')),
|
||||
(REASON_INVALID_TIME, _('Ticket not valid at this time')),
|
||||
(REASON_ANNULLED, _('Check-in annulled')),
|
||||
)
|
||||
|
||||
successful = models.BooleanField(
|
||||
|
||||
149
src/pretix/base/models/datasync.py
Normal file
149
src/pretix/base/models/datasync.py
Normal file
@@ -0,0 +1,149 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import logging
|
||||
from functools import cached_property
|
||||
|
||||
from django.db import IntegrityError, models
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from pretix.base.models import Event, Order, OrderPosition
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
MODE_OVERWRITE = "overwrite"
|
||||
MODE_SET_IF_NEW = "if_new"
|
||||
MODE_SET_IF_EMPTY = "if_empty"
|
||||
MODE_APPEND_LIST = "append"
|
||||
|
||||
|
||||
class OrderSyncQueue(models.Model):
|
||||
order = models.ForeignKey(
|
||||
Order, on_delete=models.CASCADE, related_name="queued_sync_jobs"
|
||||
)
|
||||
event = models.ForeignKey(
|
||||
Event, on_delete=models.CASCADE, related_name="queued_sync_jobs"
|
||||
)
|
||||
sync_provider = models.CharField(blank=False, null=False, max_length=128)
|
||||
triggered_by = models.CharField(blank=False, null=False, max_length=128)
|
||||
triggered = models.DateTimeField(blank=False, null=False, auto_now_add=True)
|
||||
failed_attempts = models.PositiveIntegerField(default=0)
|
||||
not_before = models.DateTimeField(blank=False, null=False, db_index=True)
|
||||
need_manual_retry = models.CharField(blank=True, null=True, max_length=20, choices=[
|
||||
('exceeded', _('Temporary error, auto-retry limit exceeded')),
|
||||
('permanent', _('Provider reported a permanent error')),
|
||||
('config', _('Misconfiguration, please check provider settings')),
|
||||
('internal', _('System error, needs manual intervention')),
|
||||
('timeout', _('System error, needs manual intervention')),
|
||||
])
|
||||
in_flight = models.BooleanField(default=False)
|
||||
in_flight_since = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("order", "sync_provider", "in_flight"),)
|
||||
ordering = ("triggered",)
|
||||
|
||||
@cached_property
|
||||
def _provider_class_info(self):
|
||||
from pretix.base.datasync.datasync import datasync_providers
|
||||
return datasync_providers.get(identifier=self.sync_provider)
|
||||
|
||||
@property
|
||||
def provider_class(self):
|
||||
return self._provider_class_info[0]
|
||||
|
||||
@property
|
||||
def provider_display_name(self):
|
||||
return self.provider_class.display_name
|
||||
|
||||
@property
|
||||
def is_provider_active(self):
|
||||
return self._provider_class_info[1]
|
||||
|
||||
@property
|
||||
def max_retry_attempts(self):
|
||||
return self.provider_class.max_attempts
|
||||
|
||||
def set_sync_error(self, failure_mode, messages, full_message):
|
||||
logger.exception(
|
||||
f"Could not sync order {self.order.code} to {type(self).__name__} ({failure_mode})"
|
||||
)
|
||||
self.order.log_action(f"pretix.event.order.data_sync.failed.{failure_mode}", {
|
||||
"provider": self.sync_provider,
|
||||
"error": messages,
|
||||
"full_message": full_message,
|
||||
})
|
||||
self.need_manual_retry = failure_mode
|
||||
self.clear_in_flight()
|
||||
|
||||
def clear_in_flight(self):
|
||||
self.in_flight = False
|
||||
self.in_flight_since = None
|
||||
try:
|
||||
self.save()
|
||||
except IntegrityError:
|
||||
# if setting in_flight=False fails due to UNIQUE constraint, just delete the current instance
|
||||
self.delete()
|
||||
|
||||
|
||||
class OrderSyncResult(models.Model):
|
||||
order = models.ForeignKey(
|
||||
Order, on_delete=models.CASCADE, related_name="sync_results"
|
||||
)
|
||||
sync_provider = models.CharField(blank=False, null=False, max_length=128)
|
||||
order_position = models.ForeignKey(
|
||||
OrderPosition, on_delete=models.CASCADE, related_name="sync_results", blank=True, null=True,
|
||||
)
|
||||
mapping_id = models.IntegerField(blank=False, null=False)
|
||||
external_object_type = models.CharField(blank=False, null=False, max_length=128)
|
||||
external_id_field = models.CharField(blank=False, null=False, max_length=128)
|
||||
id_value = models.CharField(blank=False, null=False, max_length=128)
|
||||
external_link_href = models.CharField(blank=True, null=True, max_length=255)
|
||||
external_link_display_name = models.CharField(blank=True, null=True, max_length=255)
|
||||
transmitted = models.DateTimeField(blank=False, null=False, auto_now_add=True)
|
||||
sync_info = models.JSONField()
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
models.Index(fields=("order", "sync_provider")),
|
||||
]
|
||||
|
||||
def external_link_html(self):
|
||||
if not self.external_link_display_name:
|
||||
return None
|
||||
|
||||
from pretix.base.datasync.datasync import datasync_providers
|
||||
prov, meta = datasync_providers.get(identifier=self.sync_provider)
|
||||
if prov:
|
||||
return prov.get_external_link_html(self.order.event, self.external_link_href, self.external_link_display_name)
|
||||
|
||||
def to_result_dict(self):
|
||||
return {
|
||||
"position": self.order_position_id,
|
||||
"object_type": self.external_object_type,
|
||||
"external_id_field": self.external_id_field,
|
||||
"id_value": self.id_value,
|
||||
"external_link_href": self.external_link_href,
|
||||
"external_link_display_name": self.external_link_display_name,
|
||||
**self.sync_info,
|
||||
}
|
||||
@@ -1821,7 +1821,7 @@ class OrderPayment(models.Model):
|
||||
|
||||
def fail(self, info=None, user=None, auth=None, log_data=None, send_mail=True):
|
||||
"""
|
||||
Marks the order as failed and sets info to ``info``, but only if the order is in ``created`` or ``pending``
|
||||
Marks the order as failed and sets info to ``info``, but only if the order is in ``created``, ``pending`` or ``canceled``
|
||||
state. This is equivalent to setting ``state`` to ``OrderPayment.PAYMENT_STATE_FAILED`` and logging a failure,
|
||||
but it adds strong database locking since we do not want to report a failure for an order that has just
|
||||
been marked as paid.
|
||||
@@ -1829,7 +1829,11 @@ class OrderPayment(models.Model):
|
||||
"""
|
||||
with transaction.atomic():
|
||||
locked_instance = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=self.pk)
|
||||
if locked_instance.state not in (OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING):
|
||||
if locked_instance.state in (
|
||||
OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||
OrderPayment.PAYMENT_STATE_FAILED,
|
||||
OrderPayment.PAYMENT_STATE_REFUNDED
|
||||
):
|
||||
# Race condition detected, this payment is already confirmed
|
||||
logger.info('Failed payment {} but ignored due to likely race condition.'.format(
|
||||
self.full_id,
|
||||
@@ -3310,6 +3314,24 @@ class InvoiceAddress(models.Model):
|
||||
kwargs['update_fields'] = {'name_cached', 'name_parts'}.union(kwargs['update_fields'])
|
||||
super().save(**kwargs)
|
||||
|
||||
def clear(self, except_name=False):
|
||||
self.is_business = False
|
||||
if not except_name:
|
||||
self.name_cached = ""
|
||||
self.name_parts = {}
|
||||
self.company = ""
|
||||
self.street = ""
|
||||
self.zipcode = ""
|
||||
self.city = ""
|
||||
self.country_old = ""
|
||||
self.country = ""
|
||||
self.state = ""
|
||||
self.vat_id = ""
|
||||
self.vat_id_validated = False
|
||||
self.custom_field = None
|
||||
self.internal_reference = ""
|
||||
self.beneficiary = ""
|
||||
|
||||
def describe(self):
|
||||
parts = [
|
||||
self.company,
|
||||
|
||||
85
src/pretix/base/services/datasync.py
Normal file
85
src/pretix/base/services/datasync.py
Normal file
@@ -0,0 +1,85 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from itertools import groupby
|
||||
|
||||
from django.db.models import F, Window
|
||||
from django.db.models.functions import RowNumber
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
from pretix.base.datasync.datasync import datasync_providers
|
||||
from pretix.base.models.datasync import OrderSyncQueue
|
||||
from pretix.base.signals import periodic_task
|
||||
from pretix.celery_app import app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@receiver(periodic_task, dispatch_uid="data_sync_periodic_sync_all")
|
||||
def periodic_sync_all(sender, **kwargs):
|
||||
sync_all.apply_async()
|
||||
|
||||
|
||||
@receiver(periodic_task, dispatch_uid="data_sync_periodic_reset_in_flight")
|
||||
def periodic_reset_in_flight(sender, **kwargs):
|
||||
for sq in OrderSyncQueue.objects.filter(
|
||||
in_flight=True,
|
||||
in_flight_since__lt=now() - timedelta(minutes=20),
|
||||
):
|
||||
sq.set_sync_error('timeout', [], 'Timeout')
|
||||
|
||||
|
||||
@app.task()
|
||||
def sync_all():
|
||||
with scopes_disabled():
|
||||
queue = (
|
||||
OrderSyncQueue.objects
|
||||
.filter(
|
||||
in_flight=False,
|
||||
not_before__lt=now(),
|
||||
need_manual_retry__isnull=True,
|
||||
)
|
||||
.order_by(Window(
|
||||
expression=RowNumber(),
|
||||
partition_by=[F("event_id")],
|
||||
order_by="not_before",
|
||||
))
|
||||
.prefetch_related("event")
|
||||
[:1000]
|
||||
)
|
||||
grouped = groupby(sorted(queue, key=lambda q: (q.sync_provider, q.event.pk)), lambda q: (q.sync_provider, q.event))
|
||||
for (target, event), queued_orders in grouped:
|
||||
target_cls, meta = datasync_providers.get(identifier=target, active_in=event)
|
||||
|
||||
if not target_cls:
|
||||
# sync plugin not found (plugin deactivated or uninstalled) -> drop outstanding jobs
|
||||
num_deleted, _ = OrderSyncQueue.objects.filter(pk__in=[sq.pk for sq in queued_orders]).delete()
|
||||
logger.info("Deleted %d queue entries from %r because plugin %s inactive", num_deleted, event, target)
|
||||
continue
|
||||
|
||||
with scope(organizer=event.organizer):
|
||||
with target_cls(event=event) as p:
|
||||
p.sync_queued_orders(queued_orders)
|
||||
@@ -257,8 +257,14 @@ class Registry:
|
||||
When a new entry is registered, all accessor functions are called with the new entry as parameter.
|
||||
Their return value is stored as the metadata value for that key.
|
||||
"""
|
||||
self.registered_entries = dict()
|
||||
self.keys = keys
|
||||
self.clear()
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Removes all entries from the registry.
|
||||
"""
|
||||
self.registered_entries = dict()
|
||||
self.by_key = {key: {} for key in self.keys.keys()}
|
||||
|
||||
def register(self, *objs):
|
||||
@@ -333,6 +339,23 @@ class EventPluginRegistry(Registry):
|
||||
def __init__(self, keys):
|
||||
super().__init__({"plugin": lambda o: get_defining_app(o), **keys})
|
||||
|
||||
def filter(self, active_in=None, **kwargs):
|
||||
result = super().filter(**kwargs)
|
||||
if active_in is not None:
|
||||
result = (
|
||||
(entry, meta)
|
||||
for entry, meta in result
|
||||
if is_app_active(active_in, meta['plugin'])
|
||||
)
|
||||
return result
|
||||
|
||||
def get(self, active_in=None, **kwargs):
|
||||
item, meta = super().get(**kwargs)
|
||||
if meta and active_in is not None:
|
||||
if not is_app_active(active_in, meta['plugin']):
|
||||
return None, None
|
||||
return item, meta
|
||||
|
||||
|
||||
event_live_issues = EventPluginSignal()
|
||||
"""
|
||||
@@ -645,6 +668,16 @@ For backwards compatibility reasons, this signal is only sent when a **successfu
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
checkin_annulled = EventPluginSignal()
|
||||
"""
|
||||
Arguments: ``checkin``
|
||||
|
||||
This signal is sent out every time a check-in is annulled (i.e. changed to unsuccessful after it
|
||||
already was successful).
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
logentry_display = EventPluginSignal()
|
||||
"""
|
||||
Arguments: ``logentry``
|
||||
|
||||
@@ -60,11 +60,17 @@
|
||||
</td>
|
||||
<td>
|
||||
{{ event.name }}
|
||||
{% if not event.has_subevents and event.settings.show_dates_on_frontpage %}
|
||||
<br>
|
||||
{{ event.get_date_range_display }}
|
||||
{% if event.settings.show_times %}
|
||||
{{ event.date_from|date:"TIME_FORMAT" }}
|
||||
{% if not event.has_subevents %}
|
||||
{% if event.settings.show_dates_on_frontpage %}
|
||||
<br>
|
||||
{{ event.get_date_range_display }}
|
||||
{% if event.settings.show_times %}
|
||||
{{ event.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if event.location %}
|
||||
<br>
|
||||
{{ event.location|oneline }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
@@ -42,3 +42,4 @@ class PretixControlConfig(AppConfig):
|
||||
def ready(self):
|
||||
from .views import dashboards # noqa
|
||||
from . import logdisplay # noqa
|
||||
from .views import datasync # noqa
|
||||
|
||||
123
src/pretix/control/forms/mapping.py
Normal file
123
src/pretix/control/forms/mapping.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django import forms
|
||||
from django.forms import formset_factory
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.models import Question
|
||||
from pretix.base.models.datasync import (
|
||||
MODE_APPEND_LIST, MODE_OVERWRITE, MODE_SET_IF_EMPTY, MODE_SET_IF_NEW,
|
||||
)
|
||||
|
||||
|
||||
class PropertyMappingForm(forms.Form):
|
||||
pretix_field = forms.CharField()
|
||||
external_field = forms.CharField()
|
||||
value_map = forms.CharField(required=False)
|
||||
overwrite = forms.ChoiceField(
|
||||
choices=[
|
||||
(MODE_OVERWRITE, _("Overwrite")),
|
||||
(MODE_SET_IF_NEW, _("Fill if new")),
|
||||
(MODE_SET_IF_EMPTY, _("Fill if empty")),
|
||||
(MODE_APPEND_LIST, _("Add to list")),
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(self, pretix_fields, external_fields_id, available_modes, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["pretix_field"] = forms.ChoiceField(
|
||||
label=_("pretix field"),
|
||||
choices=pretix_fields_choices(pretix_fields, kwargs.get("initial", {}).get("pretix_field")),
|
||||
required=False,
|
||||
)
|
||||
if external_fields_id:
|
||||
self.fields["external_field"] = forms.ChoiceField(
|
||||
widget=forms.Select(
|
||||
attrs={
|
||||
"data-model-select2": "json_script",
|
||||
"data-select2-src": "#" + external_fields_id,
|
||||
},
|
||||
),
|
||||
)
|
||||
self.fields["external_field"].choices = [
|
||||
(self["external_field"].value(), self["external_field"].value()),
|
||||
]
|
||||
self.fields["overwrite"].choices = [
|
||||
(key, label) for (key, label) in self.fields["overwrite"].choices if key in available_modes
|
||||
]
|
||||
|
||||
|
||||
class PropertyMappingFormSet(formset_factory(
|
||||
PropertyMappingForm,
|
||||
can_order=True,
|
||||
can_delete=True,
|
||||
extra=0,
|
||||
)):
|
||||
template_name = "pretixcontrol/datasync/property_mappings_formset.html"
|
||||
|
||||
def __init__(self, pretix_fields, external_fields, available_modes, prefix, *args, **kwargs):
|
||||
super().__init__(
|
||||
form_kwargs={
|
||||
"pretix_fields": pretix_fields,
|
||||
"external_fields_id": prefix + "external-fields" if external_fields else None,
|
||||
"available_modes": available_modes,
|
||||
},
|
||||
prefix=prefix,
|
||||
*args, **kwargs)
|
||||
self.external_fields = external_fields
|
||||
|
||||
def get_context(self):
|
||||
ctx = super().get_context()
|
||||
ctx["external_fields"] = self.external_fields
|
||||
ctx["external_fields_id"] = self.prefix + "external-fields"
|
||||
return ctx
|
||||
|
||||
def to_property_mappings_list(self):
|
||||
"""
|
||||
Returns a property mapping configuration as a JSON-serializable list of dictionaries.
|
||||
|
||||
Each entry specifies how to transfer data from one pretix field to one field in the external system:
|
||||
|
||||
- `pretix_field`: Name of a pretix data source field as declared in `pretix.base.datasync.sourcefields.get_data_fields`.
|
||||
- `external_field`: Name of the target field in the external system. Implementation-defined by the sync provider.
|
||||
- `value_map`: Dictionary mapping pretix value to external value. Only used for enumeration-type fields.
|
||||
- `overwrite`: Mode of operation if the object already exists in the target system.
|
||||
|
||||
- `MODE_OVERWRITE` (`"overwrite"`) to always overwrite existing value.
|
||||
- `MODE_SET_IF_NEW` (`"if_new"`) to only set the value if object does not exist in target system yet.
|
||||
- `MODE_SET_IF_EMPTY` (`"if_empty"`) to only set the value if object does not exist in target system,
|
||||
or the field is currently empty in target system.
|
||||
- `MODE_APPEND_LIST` (`"append"`) if the field is an array or a multi-select: add the value to the list.
|
||||
"""
|
||||
mappings = [f.cleaned_data for f in self.ordered_forms]
|
||||
return mappings
|
||||
|
||||
|
||||
QUESTION_TYPE_LABELS = dict(Question.TYPE_CHOICES)
|
||||
|
||||
|
||||
def pretix_fields_choices(pretix_fields, initial_choice):
|
||||
return [
|
||||
(f.key, f.label + " [" + QUESTION_TYPE_LABELS[f.type] + "]")
|
||||
for f in pretix_fields
|
||||
if not f.deprecated or f.key == initial_choice
|
||||
]
|
||||
@@ -303,7 +303,7 @@ class VoucherBulkForm(VoucherForm):
|
||||
}),
|
||||
required=False,
|
||||
help_text=_('You can either supply a list of email addresses with one email address per line, or the contents '
|
||||
'of a CSV file with a title column and one or more of the columns "email", "number", "name", '
|
||||
'of a CSV file with a title row and one or more of the columns "email", "number", "name", '
|
||||
'or "tag".')
|
||||
)
|
||||
Recipient = namedtuple('Recipient', 'email number name tag')
|
||||
|
||||
@@ -43,9 +43,11 @@ from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape, format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.datasync.datasync import datasync_providers
|
||||
from pretix.base.logentrytypes import (
|
||||
DiscountLogEntryType, EventLogEntryType, ItemCategoryLogEntryType,
|
||||
ItemLogEntryType, LogEntryType, OrderLogEntryType, QuestionLogEntryType,
|
||||
@@ -319,6 +321,14 @@ class OrderChangedSplitFrom(OrderLogEntryType):
|
||||
_('Denied scan of position #{posid} at {datetime} for list "{list}", type "{type}", error code "{errorcode}".'),
|
||||
_('Denied scan of position #{posid} for list "{list}", type "{type}", error code "{errorcode}".'),
|
||||
),
|
||||
'pretix.event.checkin.annulled': (
|
||||
_('Annulled scan of position #{posid} at {datetime} for list "{list}", type "{type}".'),
|
||||
_('Annulled scan of position #{posid} for list "{list}", type "{type}".'),
|
||||
),
|
||||
'pretix.event.checkin.annulment.ignored': (
|
||||
_('Ignored annulment of position #{posid} at {datetime} for list "{list}", type "{type}".'),
|
||||
_('Ignored annulment of position #{posid} for list "{list}", type "{type}".'),
|
||||
),
|
||||
'pretix.control.views.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
|
||||
'pretix.event.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
|
||||
})
|
||||
@@ -421,6 +431,51 @@ class OrderPrintLogEntryType(OrderLogEntryType):
|
||||
)
|
||||
|
||||
|
||||
class OrderDataSyncLogEntryType(OrderLogEntryType):
|
||||
def display(self, logentry, data):
|
||||
try:
|
||||
from pretix.base.datasync.datasync import datasync_providers
|
||||
provider_class, meta = datasync_providers.get(identifier=data['provider'])
|
||||
data['provider_display_name'] = provider_class.display_name
|
||||
except (KeyError, AttributeError):
|
||||
data['provider_display_name'] = data.get('provider')
|
||||
return super().display(logentry, data)
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
"pretix.event.order.data_sync.success": _("Data successfully transferred to {provider_display_name}."),
|
||||
})
|
||||
class OrderDataSyncSuccessLogEntryType(OrderDataSyncLogEntryType):
|
||||
def display(self, logentry, data):
|
||||
links = []
|
||||
if data.get('provider') and data.get('objects'):
|
||||
prov, meta = datasync_providers.get(identifier=data['provider'])
|
||||
if prov:
|
||||
for objs in data['objects'].values():
|
||||
links.append(", ".join(
|
||||
prov.get_external_link_html(logentry.event, obj['external_link_href'], obj['external_link_display_name'])
|
||||
for obj in objs
|
||||
if obj and 'external_link_href' in obj and 'external_link_display_name' in obj
|
||||
))
|
||||
|
||||
return mark_safe(escape(super().display(logentry, data)) + "".join("<p>" + link + "</p>" for link in links))
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
"pretix.event.order.data_sync.failed.config": _("Transferring data to {provider_display_name} failed due to invalid configuration:"),
|
||||
"pretix.event.order.data_sync.failed.exceeded": _("Maximum number of retries exceeded while transferring data to {provider_display_name}:"),
|
||||
"pretix.event.order.data_sync.failed.permanent": _("Error while transferring data to {provider_display_name}:"),
|
||||
"pretix.event.order.data_sync.failed.internal": _("Internal error while transferring data to {provider_display_name}."),
|
||||
"pretix.event.order.data_sync.failed.timeout": _("Internal error while transferring data to {provider_display_name}."),
|
||||
})
|
||||
class OrderDataSyncErrorLogEntryType(OrderDataSyncLogEntryType):
|
||||
def display(self, logentry, data):
|
||||
errmes = data["error"]
|
||||
if not isinstance(errmes, list):
|
||||
errmes = [errmes]
|
||||
return mark_safe(escape(super().display(logentry, data)) + "".join("<p>" + escape(msg) + "</p>" for msg in errmes))
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
|
||||
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
|
||||
|
||||
@@ -451,6 +451,11 @@ def get_global_navigation(request):
|
||||
'url': reverse('control:global.sysreport'),
|
||||
'active': (url.url_name == 'global.sysreport'),
|
||||
},
|
||||
{
|
||||
'label': _('Data sync problems'),
|
||||
'url': reverse('control:global.datasync.failedjobs'),
|
||||
'active': (url.url_name == 'global.datasync.failedjobs'),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
@@ -655,6 +660,18 @@ def get_organizer_navigation(request):
|
||||
'icon': 'download',
|
||||
})
|
||||
|
||||
if 'can_change_organizer_settings' in request.orgapermset:
|
||||
merge_in(nav, [{
|
||||
'parent': reverse('control:organizer.export', kwargs={
|
||||
'organizer': request.organizer.slug,
|
||||
}),
|
||||
'label': _('Data sync problems'),
|
||||
'url': reverse('control:organizer.datasync.failedjobs', kwargs={
|
||||
'organizer': request.organizer.slug,
|
||||
}),
|
||||
'active': (url.url_name == 'organizer.datasync.failedjobs'),
|
||||
}])
|
||||
|
||||
merge_in(nav, sorted(
|
||||
sum((list(a[1]) for a in nav_organizer.send(request.organizer, request=request, organizer=request.organizer)),
|
||||
[]),
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
</li>
|
||||
{% elif request.user.is_staff and staff_session %}
|
||||
<li>
|
||||
<a href="{% url 'control:user.sudo.stop' %}" class="danger">
|
||||
<a href="{% url 'control:user.sudo.stop' %}" class="danger admin-only">
|
||||
<i class="fa fa-id-card"></i> {% trans "End admin session" %}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% load bootstrap3 %}
|
||||
{% load escapejson %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Data transfer to external systems" %}
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="list-group">
|
||||
{% for identifier, display_name, pending, objects in providers %}
|
||||
<li class="list-group-item">
|
||||
<form action="{% url "control:event.order.sync_job" organizer=event.organizer.slug event=event.slug code=order.code provider=identifier %}" method="post" class="form-inline pull-right">
|
||||
{% csrf_token %}
|
||||
{% if pending %}
|
||||
{% if pending.not_before > now or pending.need_manual_retry %}
|
||||
<button type="submit" name="run_job_now" value="{{ pending.pk }}" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Retry now" %}</button>
|
||||
{% endif %}
|
||||
<button type="submit" name="cancel_job" value="{{ pending.pk }}" class="btn btn-danger"><i class="fa fa-times"></i> {% trans "Cancel" %}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn-default"><i class="fa fa-refresh"></i> {% trans "Sync now" %}</button>
|
||||
<input type="hidden" name="queue_sync" value="true">
|
||||
{% endif %}
|
||||
</form>
|
||||
<p><b>{{ display_name }}</b></p>
|
||||
{% if pending %}
|
||||
<p>
|
||||
{% if pending.need_manual_retry %}
|
||||
<i class="fa fa-warning"></i>
|
||||
{% trans "Error" %}: {{ pending.get_need_manual_retry_display }}
|
||||
{% elif pending.failed_attempts %}
|
||||
<i class="fa fa-warning"></i>
|
||||
{% blocktrans trimmed with num=pending.failed_attempts max=pending.max_retry_attempts %}
|
||||
Error. Retry {{ num }} of {{ max }}.
|
||||
{% endblocktrans %}
|
||||
{% if pending.not_before %}
|
||||
{% blocktrans trimmed with datetime=pending.not_before|date:"SHORT_DATETIME_FORMAT" %}
|
||||
Waiting until {{ datetime }}
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% elif pending.not_before > now %}
|
||||
{% blocktrans trimmed with datetime=pending.not_before|date:"SHORT_DATETIME_FORMAT" %}
|
||||
Waiting until {{ datetime }}
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
<i class="fa fa-hourglass"></i> {% trans "Pending" %}
|
||||
{% endif %}
|
||||
<span class="text-muted">({% blocktrans trimmed with datetime=pending.triggered|date:"SHORT_DATETIME_FORMAT" %}triggered at {{ datetime }}
|
||||
{% endblocktrans %})</span>
|
||||
<!-- {{ pending.triggered_by }} / {{ pending.triggered }} -->
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<ul>
|
||||
{% for obj in objects %}
|
||||
<li>
|
||||
{% if obj.external_link_html %}
|
||||
{{ obj.external_link_html }}
|
||||
{% else %}
|
||||
{{ obj.external_object_type }}
|
||||
{% trans "identified by" %} {{ obj.external_id_field }}
|
||||
<em>{{ obj.id_value }}</em>
|
||||
{% endif %}
|
||||
<time class="text-muted" datetime="{{ obj.transmitted.isoformat }}">{{ obj.transmitted|date:"SHORT_DATETIME_FORMAT" }}</time>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li>{% trans "No data transmitted." %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,86 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Sync problems" %}</h2>
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
On this page, we provide a list of orders where data synchronisation to an external system has failed.
|
||||
You can start another attempt to sync them manually.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% if queue_items %}
|
||||
<label aria-label="{% trans "select all rows for batch-operation" %}"
|
||||
class="batch-select-label"><input type="checkbox" data-toggle-table/></label>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>{% trans "Order" %}</th>
|
||||
<th>{% trans "Sync provider" %}</th>
|
||||
<th>{% trans "Date" %}</th>
|
||||
<th>{% trans "Failure mode" %}</th>
|
||||
{% if staff_session %}
|
||||
<th>in_flight</th><th>retry</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in queue_items %}
|
||||
<tr>
|
||||
<td><input type="checkbox" name="idlist" value="{{ item.pk }}"></td>
|
||||
<td>
|
||||
{% if staff_session %}{{ item.order.event.organizer.slug }} -{% endif %}
|
||||
<a href="{% url "control:event.order" event=item.order.event.slug organizer=item.order.event.organizer.slug code=item.order.code %}">
|
||||
{{ item.order.full_code }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ item.provider_display_name }}</td>
|
||||
<td>
|
||||
{{ item.triggered }}
|
||||
{% if staff_session %}({{ item.triggered_by }}){% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if item.need_manual_retry %}
|
||||
{{ item.get_need_manual_retry_display }}
|
||||
{% else %}
|
||||
{% blocktrans trimmed with datetime=item.not_before|date:"SHORT_DATETIME_FORMAT" %}
|
||||
Temporary error, will retry after {{ datetime }}
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% if staff_session %}({{ item.need_manual_retry }}){% endif %}
|
||||
</td>
|
||||
{% if staff_session %}
|
||||
<td>{{ item.in_flight }} ({{ item.in_flight_since }})</td><td>{{ item.failed_attempts }} / {{ item.max_retry_attempts }} ({{ item.not_before }})</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">{% trans "No problems." %}</td>
|
||||
{% if staff_session %}
|
||||
<td></td><td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% if queue_items %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<button type="submit" name="action" value="retry" class="btn btn-primary"><i class="fa fa-refresh"></i> {% trans "Retry selected" %}</button>
|
||||
<button type="submit" name="action" value="cancel" class="btn btn-danger"><i class="fa fa-times"></i> {% trans "Cancel selected" %}</button>
|
||||
</td>
|
||||
{% if staff_session %}
|
||||
<td></td><td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
</table>
|
||||
</form>
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,81 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load escapejson %}
|
||||
{% load formset_tags %}
|
||||
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
<div data-formset-body>
|
||||
{% for f in formset %}
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="row formset-row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{{ f.id }}
|
||||
{% bootstrap_field f.DELETE form_group_class="" layout="inline" %}
|
||||
{% bootstrap_field f.ORDER form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field f.pretix_field layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field f.external_field layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{% bootstrap_field f.overwrite layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
{{ f.value_map.as_hidden }}
|
||||
<div class="col-md-2 text-right flip">
|
||||
<i class="fa fa-warning hidden" data-toggle="tooltip" title=""></i>
|
||||
|
||||
<button type="button" class="btn btn-default hidden" data-edit-value-map data-toggle="modal"
|
||||
data-target="#editValueMapModal" title="{% trans "Edit value mapping" %}">
|
||||
<i class="fa fa-edit"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
<i class="fa fa-arrow-down"></i></button>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div class="row formset-row" data-formset-form>
|
||||
<div class="sr-only">
|
||||
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field formset.empty_form.pretix_field layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% bootstrap_field formset.empty_form.external_field layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
{% bootstrap_field formset.empty_form.overwrite layout='inline' form_group_class="" %}
|
||||
</div>
|
||||
{{ f.value_map.as_hidden }}
|
||||
<div class="col-md-2 text-right flip">
|
||||
<i class="fa fa-warning hidden" data-toggle="tooltip" title=""></i>
|
||||
<button type="button" class="btn btn-default hidden" data-edit-value-map data-toggle="modal"
|
||||
data-target="#editValueMapModal" title="{% trans "Edit value mapping" %}">
|
||||
<i class="fa fa-edit"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-up-button>
|
||||
<i class="fa fa-arrow-up"></i></button>
|
||||
<button type="button" class="btn btn-default" data-formset-move-down-button>
|
||||
<i class="fa fa-arrow-down"></i></button>
|
||||
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add property" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
{% if external_fields %}
|
||||
{{ external_fields|json_script:external_fields_id }}
|
||||
{% endif %}
|
||||
@@ -79,6 +79,15 @@
|
||||
class="btn btn-primary">{% trans "Show affected orders" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if has_sync_problems %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
Orders in this event could not be <strong>synced to an external system</strong> as configured.
|
||||
{% endblocktrans %}
|
||||
<a href="{% url "control:event.datasync.failedjobs" event=request.event.slug organizer=request.event.organizer.slug %}"
|
||||
class="btn btn-primary">{% trans "Show sync problems" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% eventsignal request.event "pretix.control.signals.event_dashboard_top" request=request %}
|
||||
|
||||
{% if request.event.has_subevents %}
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<div class="col-lg-6 col-sm-12 col-xs-12">
|
||||
{{ log.display }}
|
||||
{% if staff_session %}
|
||||
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandlogs data-id="{{ log.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div class="col-lg-6 col-sm-12 col-xs-12">
|
||||
{{ log.display }}
|
||||
{% if staff_session %}
|
||||
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandlogs data-id="{{ log.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
{% trans "Every event needs to be created as part of an organizer account. Currently, you do not have access to any organizer accounts." %}
|
||||
</div>
|
||||
{% if staff_session %}
|
||||
<a href="{% url "control:organizers.add" %}" class="btn btn-default">
|
||||
<a href="{% url "control:organizers.add" %}" class="btn btn-default admin-only">
|
||||
{% trans "Create a new organizer" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<p>
|
||||
{{ log.display }}
|
||||
{% if staff_session %}
|
||||
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandlogs data-id="{{ log.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
|
||||
@@ -800,7 +800,7 @@
|
||||
{{ p.html_info|safe }}
|
||||
{% if staff_session %}
|
||||
<p>
|
||||
<a href="" class="btn btn-default btn-xs" data-expandpayment data-id="{{ p.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandpayment data-id="{{ p.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
@@ -812,7 +812,7 @@
|
||||
<tr>
|
||||
<td colspan="1"></td>
|
||||
<td colspan="5">
|
||||
<a href="" class="btn btn-default btn-xs" data-expandpayment data-id="{{ p.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandpayment data-id="{{ p.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
@@ -916,7 +916,7 @@
|
||||
{% endif %}
|
||||
{% if staff_session %}
|
||||
<p>
|
||||
<a href="" class="btn btn-default btn-xs" data-expandrefund
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandrefund
|
||||
data-id="{{ r.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<div class="col-lg-6 col-sm-12 col-xs-12">
|
||||
{{ log.display }}
|
||||
{% if staff_session %}
|
||||
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandlogs data-id="{{ log.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{% blocktrans with name=organizer.name %}Organizer settings{% endblocktrans %}
|
||||
{% if request.user.is_staff and staff_session %}
|
||||
<a href="{% url "control:organizer.delete" organizer=organizer.slug %}"
|
||||
class="btn btn-danger hidden-print">
|
||||
class="btn btn-danger hidden-print admin-only">
|
||||
<span class="fa fa-trash"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if staff_session and t.info %}
|
||||
<pre><code>{{ t.info|pprint }}</code></pre>
|
||||
<pre class="admin-only"><code>{{ t.info|pprint }}</code></pre>
|
||||
{% endif %}
|
||||
{% if t.acceptor and t.acceptor != request.organizer %}
|
||||
<span class="text-muted">
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
{% if staff_session %}
|
||||
<p>
|
||||
<a href="{% url "control:organizers.add" %}" class="btn btn-default">
|
||||
<a href="{% url "control:organizers.add" %}" class="btn btn-default admin-only">
|
||||
<span class="fa fa-plus"></span>
|
||||
{% trans "Create a new organizer" %}
|
||||
</a>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<div class="col-lg-6 col-sm-12 col-xs-12">
|
||||
{{ log.display }}
|
||||
{% if staff_session %}
|
||||
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandlogs data-id="{{ log.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
<tr>
|
||||
<td colspan="1"></td>
|
||||
<td colspan="6">
|
||||
<a href="" class="btn btn-default btn-xs" data-expandpayment data-id="{{ p.pk }}">
|
||||
<a href="" class="btn btn-default btn-xs admin-only" data-expandpayment data-id="{{ p.pk }}">
|
||||
<span class="fa-eye fa fa-fw"></span>
|
||||
{% trans "Inspect" %}
|
||||
</a>
|
||||
|
||||
@@ -37,9 +37,9 @@ from django.urls import include, re_path
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
from pretix.control.views import (
|
||||
auth, checkin, dashboards, discounts, event, geo, global_settings, item,
|
||||
main, modelimport, oauth, orders, organizer, pdf, search, shredder,
|
||||
subevents, typeahead, user, users, vouchers, waitinglist,
|
||||
auth, checkin, dashboards, datasync, discounts, event, geo,
|
||||
global_settings, item, main, modelimport, oauth, orders, organizer, pdf,
|
||||
search, shredder, subevents, typeahead, user, users, vouchers, waitinglist,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@@ -58,6 +58,7 @@ urlpatterns = [
|
||||
re_path(r'^global/license/$', global_settings.LicenseCheckView.as_view(), name='global.license'),
|
||||
re_path(r'^global/sysreport/$', global_settings.SysReportView.as_view(), name='global.sysreport'),
|
||||
re_path(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'),
|
||||
re_path(r'^global/datasync/failedjobs/$', datasync.GlobalFailedSyncJobsView.as_view(), name='global.datasync.failedjobs'),
|
||||
re_path(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'),
|
||||
re_path(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'),
|
||||
re_path(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'),
|
||||
@@ -248,6 +249,7 @@ urlpatterns = [
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/export/(?P<pk>[^/]+)/delete$', organizer.DeleteScheduledExportView.as_view(),
|
||||
name='organizer.export.scheduled.delete'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/ticket_select2$', typeahead.ticket_select2, name='organizer.ticket_select2'),
|
||||
re_path(r'^organizer/(?P<organizer>[^/]+)/datasync/failedjobs/$', datasync.OrganizerFailedSyncJobsView.as_view(), name='organizer.datasync.failedjobs'),
|
||||
re_path(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
|
||||
re_path(r'^events/$', main.EventList.as_view(), name='events'),
|
||||
re_path(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
|
||||
@@ -428,6 +430,8 @@ urlpatterns = [
|
||||
re_path(r'^orders/(?P<code>[0-9A-Z]+)/cancellationrequests/(?P<req>\d+)/delete$',
|
||||
orders.OrderCancellationRequestDelete.as_view(),
|
||||
name='event.order.cancellationrequests.delete'),
|
||||
re_path(r'^orders/(?P<code>[0-9A-Z]+)/sync_job/(?P<provider>[^/]+)/$', datasync.ControlSyncJob.as_view(),
|
||||
name='event.order.sync_job'),
|
||||
re_path(r'^orders/(?P<code>[0-9A-Z]+)/transactions/$', orders.OrderTransactions.as_view(), name='event.order.transactions'),
|
||||
re_path(r'^orders/(?P<code>[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'),
|
||||
re_path(r'^invoice/(?P<invoice>[^/]+)$', orders.InvoiceDownload.as_view(),
|
||||
@@ -474,6 +478,7 @@ urlpatterns = [
|
||||
name='event.orders.checkinlists.edit'),
|
||||
re_path(r'^checkinlists/(?P<list>\d+)/delete$', checkin.CheckinListDelete.as_view(),
|
||||
name='event.orders.checkinlists.delete'),
|
||||
re_path(r'^datasync/failedjobs/$', datasync.EventFailedSyncJobsView.as_view(), name='event.datasync.failedjobs'),
|
||||
])),
|
||||
re_path(r'^event/(?P<organizer>[^/]+)/$', RedirectView.as_view(pattern_name='control:organizer'), name='event.organizerredirect'),
|
||||
]
|
||||
|
||||
@@ -383,6 +383,10 @@ def event_index(request, organizer, event):
|
||||
ctx['has_cancellation_requests'] = can_view_orders and CancellationRequest.objects.filter(
|
||||
order__event=request.event
|
||||
).exists()
|
||||
ctx['has_sync_problems'] = can_change_event_settings and request.event.queued_sync_jobs.filter(
|
||||
Q(need_manual_retry__isnull=False)
|
||||
| Q(failed_attempts__gt=0)
|
||||
).exists()
|
||||
|
||||
ctx['timeline'] = [
|
||||
{
|
||||
|
||||
150
src/pretix/control/views/datasync.py
Normal file
150
src/pretix/control/views/datasync.py
Normal file
@@ -0,0 +1,150 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from itertools import groupby
|
||||
|
||||
from django.contrib import messages
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.http import HttpResponseNotAllowed
|
||||
from django.shortcuts import redirect
|
||||
from django.template.loader import get_template
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import ListView
|
||||
|
||||
from pretix.base.datasync.datasync import datasync_providers
|
||||
from pretix.base.models import Event, Order
|
||||
from pretix.base.models.datasync import OrderSyncQueue
|
||||
from pretix.control.permissions import (
|
||||
AdministratorPermissionRequiredMixin, EventPermissionRequiredMixin,
|
||||
OrganizerPermissionRequiredMixin,
|
||||
)
|
||||
from pretix.control.signals import order_info
|
||||
from pretix.control.views.orders import OrderView
|
||||
|
||||
|
||||
@receiver(order_info, dispatch_uid="datasync_control_order_info")
|
||||
def on_control_order_info(sender: Event, request, order: Order, **kwargs):
|
||||
providers = [provider for provider, meta in datasync_providers.filter(active_in=sender)]
|
||||
if not providers:
|
||||
return ""
|
||||
|
||||
queued = {p.sync_provider: p for p in order.queued_sync_jobs.all()}
|
||||
objects = {
|
||||
provider: list(objects)
|
||||
for (provider, objects)
|
||||
in groupby(order.sync_results.order_by('sync_provider').all(), key=lambda o: o.sync_provider)
|
||||
}
|
||||
providers = [(provider.identifier, provider.display_name, queued.get(provider.identifier), objects.get(provider.identifier)) for provider in providers]
|
||||
|
||||
template = get_template("pretixcontrol/datasync/control_order_info.html")
|
||||
ctx = {
|
||||
"order": order,
|
||||
"request": request,
|
||||
"event": sender,
|
||||
"providers": providers,
|
||||
"now": now(),
|
||||
}
|
||||
return template.render(ctx, request=request)
|
||||
|
||||
|
||||
class ControlSyncJob(OrderView):
|
||||
permission = 'can_change_orders'
|
||||
|
||||
def post(self, request, provider, *args, **kwargs):
|
||||
prov, meta = datasync_providers.get(active_in=self.request.event, identifier=provider)
|
||||
|
||||
if self.request.POST.get("queue_sync") == "true":
|
||||
prov.enqueue_order(self.order, 'user')
|
||||
messages.success(self.request, _('The sync job has been enqueued and will run in the next minutes.'))
|
||||
elif self.request.POST.get("cancel_job"):
|
||||
job = self.order.queued_sync_jobs.get(pk=self.request.POST.get("cancel_job"))
|
||||
if job.in_flight:
|
||||
messages.warning(self.request, _('The sync job is already in progress.'))
|
||||
else:
|
||||
job.delete()
|
||||
messages.success(self.request, _('The sync job has been canceled.'))
|
||||
elif self.request.POST.get("run_job_now"):
|
||||
job = self.order.queued_sync_jobs.get(pk=self.request.POST.get("run_job_now"))
|
||||
job.not_before = now()
|
||||
job.need_manual_retry = None
|
||||
job.save()
|
||||
messages.success(self.request, _('The sync job has been set to run as soon as possible.'))
|
||||
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
return HttpResponseNotAllowed(['POST'])
|
||||
|
||||
|
||||
class FailedSyncJobsView(ListView):
|
||||
template_name = 'pretixcontrol/datasync/failed_jobs.html'
|
||||
model = OrderSyncQueue
|
||||
context_object_name = 'queue_items'
|
||||
paginate_by = 100
|
||||
ordering = ('triggered',)
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(
|
||||
Q(need_manual_retry__isnull=False)
|
||||
| Q(failed_attempts__gt=0)
|
||||
).select_related(
|
||||
'order'
|
||||
)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
items = self.get_queryset().filter(pk__in=request.POST.getlist('idlist'))
|
||||
|
||||
if self.request.POST.get("action") == "retry":
|
||||
for item in items:
|
||||
item.not_before = now()
|
||||
item.need_manual_retry = None
|
||||
item.save()
|
||||
messages.success(self.request, _('The selected jobs have been set to run as soon as possible.'))
|
||||
elif self.request.POST.get("action") == "cancel":
|
||||
items.delete()
|
||||
messages.success(self.request, _('The selected jobs have been canceled.'))
|
||||
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
|
||||
class GlobalFailedSyncJobsView(AdministratorPermissionRequiredMixin, FailedSyncJobsView):
|
||||
pass
|
||||
|
||||
|
||||
class OrganizerFailedSyncJobsView(OrganizerPermissionRequiredMixin, FailedSyncJobsView):
|
||||
permission = "can_change_organizer_settings"
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(
|
||||
event__organizer=self.request.organizer
|
||||
)
|
||||
|
||||
|
||||
class EventFailedSyncJobsView(EventPermissionRequiredMixin, FailedSyncJobsView):
|
||||
permission = "can_change_event_settings"
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(
|
||||
event=self.request.event
|
||||
)
|
||||
@@ -22,6 +22,7 @@
|
||||
from babel.core import Locale
|
||||
from django.core.cache import cache
|
||||
from django.utils import translation
|
||||
from django.utils.translation import gettext_noop
|
||||
from django_countries import Countries, collator
|
||||
from django_countries.fields import CountryField
|
||||
from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE
|
||||
@@ -118,3 +119,14 @@ def get_phone_prefixes_sorted_and_localized():
|
||||
_cached_phone_prefixes[cache_key] = val
|
||||
cache.set(cache_key, val, 3600 * 24 * 30)
|
||||
return val
|
||||
|
||||
|
||||
custom_translations = [
|
||||
# Hotfix to allow pretix to provide custom translations until
|
||||
# https://github.com/SmileyChris/django-countries/pull/471
|
||||
# is merged
|
||||
gettext_noop("Belarus"),
|
||||
gettext_noop("French Guiana"),
|
||||
gettext_noop("North Macedonia"),
|
||||
gettext_noop("Macao"),
|
||||
]
|
||||
|
||||
@@ -19,11 +19,20 @@
|
||||
# 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 logging
|
||||
|
||||
from arabic_reshaper import ArabicReshaper
|
||||
from django.conf import settings
|
||||
from django.utils.functional import SimpleLazyObject
|
||||
from PIL import Image
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib.utils import ImageReader
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThumbnailingImageReader(ImageReader):
|
||||
@@ -59,3 +68,35 @@ reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
|
||||
'delete_harakat': True,
|
||||
'support_ligatures': False,
|
||||
}))
|
||||
|
||||
|
||||
class FontFallbackParagraph(Paragraph):
|
||||
def __init__(self, text, style=None, *args, **kwargs):
|
||||
if style is None:
|
||||
style = ParagraphStyle(name='paragraphImplicitDefaultStyle')
|
||||
|
||||
if not self._font_supports_text(text, style.fontName):
|
||||
newFont = self._find_font(text, style.fontName)
|
||||
if newFont:
|
||||
logger.debug(f"replacing {style.fontName} with {newFont} for {text!r}")
|
||||
style = style.clone(name=style.name + '_' + newFont, fontName=newFont)
|
||||
|
||||
super().__init__(text, style, *args, **kwargs)
|
||||
|
||||
def _font_supports_text(self, text, font_name):
|
||||
if not text:
|
||||
return True
|
||||
font = pdfmetrics.getFont(font_name)
|
||||
return all(
|
||||
ord(c) in font.face.charToGlyph or not c.isprintable()
|
||||
for c in text
|
||||
)
|
||||
|
||||
def _find_font(self, text, original_font):
|
||||
for family, styles in get_fonts(pdf_support_required=True).items():
|
||||
if self._font_supports_text(text, family):
|
||||
if (original_font.endswith("It") or original_font.endswith(" I")) and "italic" in styles:
|
||||
return family + " I"
|
||||
if (original_font.endswith("Bd") or original_font.endswith(" B")) and "bold" in styles:
|
||||
return family + " B"
|
||||
return family
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -557,48 +557,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -630,40 +630,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2021-09-15 11:22+0000\n"
|
||||
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
|
||||
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -587,52 +587,52 @@ msgstr "QR الدخول"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "لا يمكن تحميل ملف PDF الخلفية للأسباب التالية:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "مجموعة من العناصر"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "عنصر نص"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "عنصر نص"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "منطقة باركود"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "منطقة صورة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "مدعوم من pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "عنصر"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "تصميم التذكرة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "فشلت عملية الحفظ."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr "حصل خطأ أثناء رفع ملف PDF الخاص بك، يرجى المحاولة مرة أخرى."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "هل تريد أن تغادر المحرر دون حفظ التعديلات؟"
|
||||
|
||||
@@ -668,40 +668,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "البحث في الاستفسارات"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "الكل"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "لا شيء"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "المختارة فقط"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "قم باستخدم اسم مختلف داخليا"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "اضغط لاغلاق الصفحة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "لم تقم بحفظ التعديلات!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -557,48 +557,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -630,40 +630,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
|
||||
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
|
||||
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -560,48 +560,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Disseny del tiquet"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -633,40 +633,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2025-05-16 17:00+0000\n"
|
||||
"Last-Translator: David <davemachala@gmail.com>\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -578,52 +578,52 @@ msgstr "Check-in QR kód"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "Pozadí PDF nemohl být načten:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Skupina objektů"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Textový objekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Textový objekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Oblast s QR kódem"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Oblast obrazu"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Poháněno společností pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Objekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Design vstupenky"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Uložení se nepodařilo."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr "Při nahrávání souboru PDF došlo k problému, zkuste to prosím znovu."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "Opravdu chcete opustit editor bez uložení změn?"
|
||||
|
||||
@@ -660,40 +660,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "Hledaný výraz"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Všechny"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Žádný"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "Pouze vybrané"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Interně používat jiný název"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Kliknutím zavřete"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Máte neuložené změny!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -558,48 +558,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -631,40 +631,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2024-07-10 15:00+0000\n"
|
||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -600,54 +600,54 @@ msgstr "Check-in QR"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "Baggrunds-pdf'en kunne ikke hentes af følgende grund:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Gruppe af objekter"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Tekstobjekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Tekstobjekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "QR-kode-område"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
#, fuzzy
|
||||
#| msgid "Barcode area"
|
||||
msgid "Image area"
|
||||
msgstr "QR-kode-område"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Drevet af pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Objekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Billetdesign"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Gem fejlede."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr "Fejl under upload af pdf. Prøv venligt igen."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Er du sikker på at du vil forlade editoren uden at gemme dine ændringer?"
|
||||
@@ -680,40 +680,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Ingen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Klik for at lukke"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Du har ændringer, der ikke er gemt!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2025-05-30 11:08+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -573,49 +573,49 @@ msgstr "Check-in-QR-Code"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "Die Hintergrund-PDF-Datei konnte nicht geladen werden:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Gruppe von Objekten"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Text-Objekt (veraltet)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr "Textbox"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "QR-Code-Bereich"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Bildbereich"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Event-Ticketshop von pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Objekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Ticket-Design"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Speichern fehlgeschlagen."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Es gab ein Problem beim Hochladen der PDF-Datei, bitte erneut versuchen."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Möchten Sie den Editor wirklich schließen ohne Ihre Änderungen zu speichern?"
|
||||
@@ -653,40 +653,40 @@ msgstr ""
|
||||
"Diese Farbe hat keinen ausreichenden Kontrast zu weiß. Die Barrierefreiheit "
|
||||
"der Seite ist eingeschränkt."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "Suchbegriff"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Keine"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "Nur ausgewählte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Geben Sie eine Seitenzahl zwischen 1 und %(max)s ein."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr "Ungültige Seitenzahl."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Intern einen anderen Namen verwenden"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Sie haben ungespeicherte Änderungen!"
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ Apps
|
||||
APM
|
||||
as
|
||||
Aufbuchungen
|
||||
aufeinanderfolgenden
|
||||
Aufteilungsliste
|
||||
aufzubuchen
|
||||
auschecken
|
||||
@@ -44,6 +45,7 @@ Bcc
|
||||
BCC
|
||||
Beispielevent
|
||||
Beispielproduktvariantenbeschreibung
|
||||
Belarus
|
||||
Benachrichtigungs
|
||||
Benachrichtigungsart
|
||||
Benachrichtigungsarten
|
||||
@@ -146,6 +148,8 @@ GiroCode
|
||||
giropay
|
||||
GPL
|
||||
Grants
|
||||
Gruppierungswert
|
||||
Gruppierungsspalte
|
||||
Guide
|
||||
Gutscheineinlöser
|
||||
herunterscrollen
|
||||
@@ -194,6 +198,7 @@ Logindaten
|
||||
Lösch
|
||||
loszulegen
|
||||
Ltd
|
||||
Macau
|
||||
max
|
||||
MariaDB
|
||||
MapQuest
|
||||
@@ -318,6 +323,7 @@ Stornobedingungen
|
||||
Stornobeleg
|
||||
Stornodatum
|
||||
Stornogebühr
|
||||
Stornogebühren
|
||||
Stornos
|
||||
Strg
|
||||
Stripe
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2025-05-30 11:07+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
@@ -573,49 +573,49 @@ msgstr "Check-in-QR-Code"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "Die Hintergrund-PDF-Datei konnte nicht geladen werden:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Gruppe von Objekten"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Text-Objekt (veraltet)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr "Textbox"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "QR-Code-Bereich"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Bildbereich"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Event-Ticketshop von pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Objekt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Ticket-Design"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Speichern fehlgeschlagen."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Es gab ein Problem beim Hochladen der PDF-Datei, bitte erneut versuchen."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Möchtest du den Editor wirklich schließen ohne Ihre Änderungen zu speichern?"
|
||||
@@ -653,40 +653,40 @@ msgstr ""
|
||||
"Diese Farbe hat keinen ausreichenden Kontrast zu weiß. Die Barrierefreiheit "
|
||||
"der Seite ist eingeschränkt."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "Suchbegriff"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Keine"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "Nur ausgewählte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Gib eine Seitenzahl zwischen 1 und %(max)s ein."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr "Ungültige Seitenzahl."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Intern einen anderen Namen verwenden"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Du hast ungespeicherte Änderungen!"
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ Apps
|
||||
APM
|
||||
as
|
||||
Aufbuchungen
|
||||
aufeinanderfolgenden
|
||||
Aufteilungsliste
|
||||
aufzubuchen
|
||||
auschecken
|
||||
@@ -44,6 +45,7 @@ Bcc
|
||||
BCC
|
||||
Beispielevent
|
||||
Beispielproduktvariantenbeschreibung
|
||||
Belarus
|
||||
Benachrichtigungs
|
||||
Benachrichtigungsart
|
||||
Benachrichtigungsarten
|
||||
@@ -146,6 +148,8 @@ GiroCode
|
||||
giropay
|
||||
GPL
|
||||
Grants
|
||||
Gruppierungswert
|
||||
Gruppierungsspalte
|
||||
Guide
|
||||
Gutscheineinlöser
|
||||
herunterscrollen
|
||||
@@ -194,6 +198,7 @@ Logindaten
|
||||
Lösch
|
||||
loszulegen
|
||||
Ltd
|
||||
Macau
|
||||
max
|
||||
MariaDB
|
||||
MapQuest
|
||||
@@ -318,6 +323,7 @@ Stornobedingungen
|
||||
Stornobeleg
|
||||
Stornodatum
|
||||
Stornogebühr
|
||||
Stornogebühren
|
||||
Stornos
|
||||
Strg
|
||||
Stripe
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-05 07:29+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -557,48 +557,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -630,40 +630,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2024-12-22 00:00+0000\n"
|
||||
"Last-Translator: Dimitris Tsimpidis <tsimpidisd@gmail.com>\n"
|
||||
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -606,54 +606,54 @@ msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
"Το αρχείο φόντου PDF δεν ήταν δυνατό να φορτωθεί για τον ακόλουθο λόγο:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Ομάδα αντικειμένων"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Αντικείμενο κειμένου"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Αντικείμενο κειμένου"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Περιοχή Barcode"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
#, fuzzy
|
||||
#| msgid "Barcode area"
|
||||
msgid "Image area"
|
||||
msgstr "Περιοχή Barcode"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Υποστηρίζεται από το Pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Αντικείμενο"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Σχεδιασμός εισιτηρίων"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Η αποθήκευση απέτυχε."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr "Σφάλμα κατά τη μεταφόρτωση του αρχείου PDF, δοκιμάστε ξανά."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Θέλετε πραγματικά να αφήσετε τον επεξεργαστή χωρίς να αποθηκεύσετε τις "
|
||||
@@ -694,40 +694,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Όλα"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Κανένας"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Χρησιμοποιήστε διαφορετικό όνομα εσωτερικά"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Κάντε κλικ για να κλείσετε"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -557,48 +557,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -630,40 +630,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2025-05-30 11:15+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -571,50 +571,50 @@ msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
"El archivo PDF de fondo no ha podido ser cargado debido al siguiente motivo:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Grupo de objetos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Objeto texto (obsoleto)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr "Campo de texto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Área para código de barras"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Área de imagen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Proveído por pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Objeto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Diseño del entrada"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "El guardado falló."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Ha habido un error mientras se cargaba el archivo PDF, por favor, intente de "
|
||||
"nuevo."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "¿Realmente desea salir del editor sin haber guardado sus cambios?"
|
||||
|
||||
@@ -651,40 +651,40 @@ msgstr ""
|
||||
"El color no tiene suficiente contraste con el blanco. La accesibilidad de su "
|
||||
"sitio se verá afectada."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "Consulta de búsqueda"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Ninguno"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "Solamente seleccionados"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Introduce un número de página entre 1 y %(max)s."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr "Número de página inválido."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Usar un nombre diferente internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "¡Tienes cambios sin guardar!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1156
src/pretix/locale/es_419/LC_MESSAGES/djangojs.po
Normal file
1156
src/pretix/locale/es_419/LC_MESSAGES/djangojs.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -557,48 +557,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -630,40 +630,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2024-09-06 08:47+0000\n"
|
||||
"Last-Translator: Albizuri <oier@puntu.eus>\n"
|
||||
"Language-Team: Basque <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -570,48 +570,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Sarrera diseinua"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -643,40 +643,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Guztiak"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2021-11-10 05:00+0000\n"
|
||||
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
||||
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -582,54 +582,54 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Tekstiobjekti"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Tekstiobjekti"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Viivakoodialue"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
#, fuzzy
|
||||
#| msgid "Barcode area"
|
||||
msgid "Image area"
|
||||
msgstr "Viivakoodialue"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Tallennus epäonnistui."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -661,40 +661,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Kaikki"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Käytä toista nimeä sisäisesti"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Sulje klikkaamalla"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Sinulla on tallentamattomia muutoksia!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -557,48 +557,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -630,40 +630,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2025-05-30 11:06+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -573,49 +573,49 @@ msgstr ""
|
||||
"Le fichier PDF généré en arrière-plan n'a pas pu être chargé pour la raison "
|
||||
"suivante :"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Groupe d'objets"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Objet textuel (obsolète)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr "Zone de texte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Zone de code-barres"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Zone d'image"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Propulsé par pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Objet"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Conception des billets"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "L'enregistrement a échoué."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Erreur lors du téléchargement de votre fichier PDF, veuillez réessayer."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Voulez-vous vraiment quitter l'éditeur sans sauvegarder vos modifications ?"
|
||||
@@ -653,40 +653,40 @@ msgstr ""
|
||||
"Votre choix de couleur n'est pas assez contrastée par rapport au blanc. "
|
||||
"L'accessibilité de votre site en sera affectée."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "Requête de recherche"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Tous"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Aucun"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "Seuls les sélectionnés"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr "Saisir le numéro de page entre 1 et %(max)s."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr "Numéro de page invalide."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Utiliser un nom différent en interne"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Cliquez pour fermer"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Vous avez des modifications non sauvegardées !"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2022-02-22 22:00+0000\n"
|
||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -584,53 +584,53 @@ msgstr "QR de validación"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "O arquivo PDF de fondo non se puido cargar polo motivo seguinte:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Grupo de obxectos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Obxecto de texto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Obxecto de texto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Área para código de barras"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Área de imaxe"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Desenvolto por Pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Obxecto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Deseño do tícket"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "O gardado fallou."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Houbo un erro mentres se cargaba o arquivo PDF. Por favor, inténteo de novo."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "Realmente desexa saír do editor sen gardar os cambios?"
|
||||
|
||||
@@ -668,40 +668,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "Consultar unha procura"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Ningún"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "Soamente seleccionados"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Usar un nome diferente internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Tes cambios sen gardar!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2021-09-24 13:54+0000\n"
|
||||
"Last-Translator: ofirtro <ofir.tro@gmail.com>\n"
|
||||
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -565,48 +565,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -638,40 +638,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2025-03-16 10:17+0000\n"
|
||||
"Last-Translator: Robert Rigo <kontakt@bicikli.hr>\n"
|
||||
"Language-Team: Croatian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -559,48 +559,48 @@ msgstr ""
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
@@ -632,40 +632,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2024-11-28 06:00+0000\n"
|
||||
"Last-Translator: Patrick Chilton <chpatrick@gmail.com>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -599,54 +599,54 @@ msgstr "Check in QR"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "A PDF háttér fájl nem tölthető be a következők miatt:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "tárgy csoport"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Szöveg"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Szöveg"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Vonalkód terület"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
#, fuzzy
|
||||
#| msgid "Barcode area"
|
||||
msgid "Image area"
|
||||
msgstr "Vonalkód terület"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "pretix által működtetett"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "objektum"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Jegy design"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Mentés sikertelen."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr "Hiba a PDF fájl feltöltése közben, próbálja újra."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "Biztosan ki akar lépni a szerkesztőből a változtatások mentése nélkül?"
|
||||
|
||||
@@ -683,40 +683,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Összes"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Semmi"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Használj másik nevet"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Bezárásért kattints"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Mentetlen változtatások!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-06-26 09:11+0000\n"
|
||||
"POT-Creation-Date: 2025-08-04 12:44+0000\n"
|
||||
"PO-Revision-Date: 2023-09-20 14:01+0000\n"
|
||||
"Last-Translator: Mahdia Aliyy <mahdlyy.k@gmail.com>\n"
|
||||
"Language-Team: Indonesian <https://translate.pretix.eu/projects/pretix/"
|
||||
@@ -584,52 +584,52 @@ msgstr "QR masuk"
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "File latar belakang PDF tidak dapat dimuat karena alasan berikut:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
msgstr "Sekelompok objek"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:908
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Objek teks"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:910
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Objek teks"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:912
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Area kode batang"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:914
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Daerah gambar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:916
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Didukung oleh pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:918
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
msgstr "Obyek"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:922
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:923
|
||||
msgid "Ticket design"
|
||||
msgstr "Desain tiket"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1258
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1292
|
||||
msgid "Saving failed."
|
||||
msgstr "Gagal menyimpan."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1327
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1378
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1412
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr "Terjadi kesalahan saat mengunggah file PDF Anda, coba lagi."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1361
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1395
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
"Apakah Anda benar-benar ingin keluar dari editor tanpa menyimpan perubahan?"
|
||||
@@ -667,40 +667,40 @@ msgid ""
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:416
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:437
|
||||
msgid "Search query"
|
||||
msgstr "Permintaan pencarian"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:434
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
msgid "All"
|
||||
msgstr "Semua"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:435
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:436
|
||||
msgid "None"
|
||||
msgstr "Tidak ada"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:439
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:440
|
||||
msgid "Selected only"
|
||||
msgstr "Hanya dipilih"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:811
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:812
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:814
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:815
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:972
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:973
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Gunakan nama yang berbeda secara internal"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1012
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1013
|
||||
msgid "Click to close"
|
||||
msgstr "Klik untuk menutup"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1093
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1094
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Anda memiliki perubahan yang belum disimpan!"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user