Compare commits

..
205 changed files with 74846 additions and 80533 deletions
+2 -10
View File
@@ -52,18 +52,10 @@ Example::
``currency``
The default currency as a three-letter code. Defaults to ``EUR``.
``cachedir``
The local path to a directory where temporary files will be stored.
Defaults to the ``cache`` directory below the ``datadir``.
``datadir``
The local path to a data directory that will be used for storing user uploads and similar
data. Defaults to the value of the environment variable ``DATA_DIR`` or ``data``.
``logdir``
The local path to a directory where log files will be stored.
Defaults to the ``logs`` directory below the ``datadir``.
``plugins_default``
A comma-separated list of plugins that are enabled by default for all new events.
Defaults to ``pretix.plugins.sendmail,pretix.plugins.statistics``.
@@ -353,7 +345,7 @@ to speed up various operations::
The location of redis, as a URL of the form ``redis://[:password]@localhost:6379/0``
or ``unix://[:password]@/path/to/socket.sock?db=0``
``sessions``
``session``
When this is set to ``True``, redis will be used as the session storage.
``sentinels``
@@ -529,4 +521,4 @@ pretix can optionally make use of a GeoIP database for some features. It needs a
.. _GeoAcumen: https://github.com/geoacumen/geoacumen-country
.. _GeoLite2: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
.. _GeoLite2: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
+1 -3
View File
@@ -94,9 +94,7 @@ If you want the user to return to your application after the payment is complete
"Plugins". Enable the plugin "Redirection from order page". Then, go to the new page "Settings", then "Redirection".
Enter the base URL of your web application. This will allow you to redirect to pages under this base URL later on.
For example, if you want users to be redirected to ``https://example.org/order/return?tx_id=1234``, you could now
either enter ``https://example.org/order/`` or ``https://example.org/``.
Please note that in the latter case the trailing slash is required, ``https://example.org`` is not allowed to prevent.
Only base URLs with a secure (``https://``) or local (``http://localhost``) origin are permitted.
either enter ``https://example.org`` or ``https://example.org/order/``.
The user will be redirected back to your page instead of pretix' order confirmation page after the payment,
**regardless of whether it was successful or not**. We will append an ``error=…`` query parameter with an error
-11
View File
@@ -19,7 +19,6 @@ external_identifier string External ID of
the API, but is read-only for customers created through a
SSO integration.
email string Customer email address
phone string Customer phone number
name string Name of this customer (or ``null``)
name_parts object of strings Decomposition of name (i.e. given name, family name)
is_active boolean Whether this account is active
@@ -40,10 +39,6 @@ password string Can only be set
Passwords can now be set through the API during customer creation.
.. versionchanged:: 2024.3
The attribute ``phone`` has been added.
Endpoints
---------
@@ -76,7 +71,6 @@ Endpoints
"identifier": "8WSAJCJ",
"external_identifier": null,
"email": "customer@example.org",
"phone": "+493012345678",
"name": "John Doe",
"name_parts": {
"_scheme": "full",
@@ -124,7 +118,6 @@ Endpoints
"identifier": "8WSAJCJ",
"external_identifier": null,
"email": "customer@example.org",
"phone": "+493012345678",
"name": "John Doe",
"name_parts": {
"_scheme": "full",
@@ -162,7 +155,6 @@ Endpoints
{
"email": "test@example.org",
"phone": "+493012345678",
"password": "verysecret",
"send_email": true
}
@@ -179,7 +171,6 @@ Endpoints
"identifier": "8WSAJCJ",
"external_identifier": null,
"email": "test@example.org",
"phone": "+493012345678",
...
}
@@ -224,7 +215,6 @@ Endpoints
"identifier": "8WSAJCJ",
"external_identifier": null,
"email": "test@example.org",
"phone": "+493012345678",
}
@@ -259,7 +249,6 @@ Endpoints
"identifier": "8WSAJCJ",
"external_identifier": null,
"email": null,
"phone": null,
}
-9
View File
@@ -179,11 +179,6 @@ country string Attendee countr
state string Attendee state (ISO 3166-2 code). Only supported in
AU, BR, CA, CN, MY, MX, and US, otherwise ``null``.
voucher integer Internal ID of the voucher used for this position (or ``null``)
voucher_budget_use money (string) Amount of money discounted by the voucher, corresponding
to how much of the ``budget`` of the voucher is consumed.
**Important:** Do not rely on this amount to be a useful
value if the position's price, product or voucher
are changed *after* the order was created. Can be ``null``.
tax_rate decimal (string) VAT rate applied for this position
tax_value money (string) VAT included in this position
tax_rule integer The ID of the used tax rule (or ``null``)
@@ -372,7 +367,6 @@ List of all orders
"country": "DE",
"state": null,
"voucher": null,
"voucher_budget_use": null,
"tax_rate": "0.00",
"tax_value": "0.00",
"tax_rule": null,
@@ -595,7 +589,6 @@ Fetching individual orders
"country": "DE",
"state": null,
"voucher": null,
"voucher_budget_use": null,
"tax_rate": "0.00",
"tax_rule": null,
"tax_value": "0.00",
@@ -1548,7 +1541,6 @@ List of all order positions
},
"attendee_email": null,
"voucher": null,
"voucher_budget_use": null,
"tax_rate": "0.00",
"tax_rule": null,
"tax_value": "0.00",
@@ -1662,7 +1654,6 @@ Fetching individual positions
},
"attendee_email": null,
"voucher": null,
"voucher_budget_use": null,
"tax_rate": "0.00",
"tax_rule": null,
"tax_value": "0.00",
-6
View File
@@ -34,7 +34,6 @@ internal_id string Can be used for
contact_name string Contact person (or ``null``)
contact_name_parts object of strings Decomposition of contact name (i.e. given name, family name)
contact_email string Contact person email address (or ``null``)
contact_cc_email string Copy email addresses, can be multiple separated by comma (or ``null``)
booth string Booth number (or ``null``). Maximum 100 characters.
locale string Locale for communication with the exhibitor.
access_code string Access code for the exhibitor to access their data or use the lead scanning app (read-only).
@@ -110,7 +109,6 @@ Endpoints
"title": "Dr"
},
"contact_email": "johnson@as.example.org",
"contact_cc_email": "miller@as.example.org,smith@as.example.org",
"booth": "A2",
"locale": "de",
"access_code": "VKHZ2FU84",
@@ -164,7 +162,6 @@ Endpoints
"title": "Dr"
},
"contact_email": "johnson@as.example.org",
"contact_cc_email": "miller@as.example.org,smith@as.example.org",
"booth": "A2",
"locale": "de",
"access_code": "VKHZ2FU84",
@@ -368,7 +365,6 @@ Endpoints
"title": "Dr"
},
"contact_email": "johnson@as.example.org",
"contact_cc_email": "miller@as.example.org,smith@as.example.org",
"booth": "A2",
"locale": "de",
"allow_lead_scanning": true,
@@ -398,7 +394,6 @@ Endpoints
"title": "Dr"
},
"contact_email": "johnson@as.example.org",
"contact_cc_email": "miller@as.example.org,smith@as.example.org",
"booth": "A2",
"locale": "de",
"access_code": "VKHZ2FU84",
@@ -459,7 +454,6 @@ Endpoints
"title": "Dr"
},
"contact_email": "johnson@as.example.org",
"contact_cc_email": "miller@as.example.org,smith@as.example.org",
"booth": "A2",
"locale": "de",
"access_code": "VKHZ2FU84",
+1 -1
View File
@@ -1,4 +1,4 @@
sphinx==7.2.*
sphinx==7.0.*
jinja2==3.1.*
sphinx-rtd-theme
sphinxcontrib-httpdomain
+1 -1
View File
@@ -1,5 +1,5 @@
-e ../
sphinx==7.2.*
sphinx==7.0.*
jinja2==3.1.*
sphinx-rtd-theme
sphinxcontrib-httpdomain
@@ -19,3 +19,4 @@ Then, head to the **Bundled products** tab of the "conference ticket" and add th
Once a customer tries to buy the € 450 conference ticket, a sub-product will be added and the price will automatically be split into the two components, leading to a correct computation of taxes.
You can find more use cases in these specialized guides:
+30 -28
View File
@@ -31,41 +31,41 @@ dependencies = [
"BeautifulSoup4==4.12.*",
"bleach==5.0.*",
"celery==5.3.*",
"chardet==5.2.*",
"chardet==5.1.*",
"cryptography>=3.4.2",
"css-inline==0.13.*",
"css-inline==0.8.*",
"defusedcsv>=1.1.0",
"dj-static",
"Django==4.2.*",
"django-bootstrap3==23.6.*",
"django-compressor==4.4",
"django-bootstrap3==23.1.*",
"django-compressor==4.3.*",
"django-countries==7.5.*",
"django-filter==24.1",
"django-filter==23.2",
"django-formset-js-improved==0.5.0.3",
"django-formtools==2.5.1",
"django-hierarkey==1.1.*",
"django-hijack==3.4.*",
"django-hijack==3.3.*",
"django-i18nfield==1.9.*,>=1.9.4",
"django-libsass==0.9",
"django-localflavor==4.0",
"django-markup",
"django-oauth-toolkit==2.3.*",
"django-otp==1.3.*",
"django-phonenumber-field==7.3.*",
"django-oauth-toolkit==2.2.*",
"django-otp==1.2.*",
"django-phonenumber-field==7.1.*",
"django-redis==5.4.*",
"django-scopes==2.0.*",
"django-statici18n==2.4.*",
"djangorestframework==3.14.*",
"dnspython==2.6.*",
"dnspython==2.3.*",
"drf_ujson2==1.7.*",
"geoip2==4.*",
"importlib_metadata==7.*", # Polyfill, we can probably drop this once we require Python 3.10+
"isoweek",
"jsonschema",
"kombu==5.3.*",
"libsass==0.23.*",
"libsass==0.22.*",
"lxml",
"markdown==3.6", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
"markdown==3.4.3", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
"mt-940==4.30.*",
"oauthlib==3.2.*",
@@ -73,26 +73,26 @@ dependencies = [
"packaging",
"paypalrestsdk==1.13.*",
"paypal-checkout-serversdk==1.0.*",
"PyJWT==2.8.*",
"PyJWT==2.7.*",
"phonenumberslite==8.13.*",
"Pillow==10.2.*",
"Pillow==9.5.*",
"pretix-plugin-build",
"protobuf==5.26.*",
"protobuf==4.23.*",
"psycopg2-binary",
"pycountry",
"pycparser==2.21",
"pycryptodome==3.20.*",
"pycryptodome==3.18.*",
"pypdf==3.9.*",
"python-bidi==0.4.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
"python-dateutil==2.8.*",
"pytz",
"pytz-deprecation-shim==0.1.*",
"pyuca",
"qrcode==7.4.*",
"redis==5.0.*",
"reportlab==4.1.*",
"redis==4.6.*",
"reportlab==4.0.*",
"requests==2.31.*",
"sentry-sdk==1.42.*",
"sentry-sdk==1.40.*",
"sepaxml==2.6.*",
"slimit",
"static3==0.7.*",
@@ -102,7 +102,7 @@ dependencies = [
"tqdm==4.*",
"vat_moss_forked==2020.3.20.0.11.0",
"vobject==0.9.*",
"webauthn==2.0.*",
"webauthn==0.4.*",
"zeep==4.2.*"
]
@@ -112,21 +112,23 @@ dev = [
"aiohttp==3.9.*",
"coverage",
"coveralls",
"fakeredis==2.21.*",
"flake8==7.0.*",
"fakeredis==2.18.*",
"flake8==6.0.*",
"freezegun",
"isort==5.13.*",
"isort==5.12.*",
"pep8-naming==0.13.*",
"potypo",
"pycodestyle==2.10.*",
"pyflakes==3.0.*",
"pytest-asyncio",
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.12.*",
"pytest-rerunfailures==14.*",
"pytest-mock==3.10.*",
"pytest-rerunfailures==13.*",
"pytest-sugar",
"pytest-xdist==3.5.*",
"pytest==8.1.*",
"pytest-xdist==3.3.*",
"pytest==7.3.*",
"responses",
]
+1 -1
View File
@@ -19,4 +19,4 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
__version__ = "2024.3.0"
__version__ = "2024.2.0.dev0"
-1
View File
@@ -111,7 +111,6 @@ LANGUAGES_RTL = {
LANGUAGES_INCUBATING = {
'fi', 'pt-br', 'gl',
}
LANGUAGES = ALL_LANGUAGES
LOCALE_PATHS = [
os.path.join(os.path.dirname(__file__), 'locale'),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.10 on 2024-02-12 11:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixapi", "0011_bigint"),
]
operations = [
migrations.AddField(
model_name="oauthapplication",
name="post_logout_redirect_uris",
field=models.TextField(default=""),
),
]
-5
View File
@@ -42,11 +42,6 @@ class OAuthApplication(AbstractApplication):
verbose_name=_("Redirection URIs"),
help_text=_("Allowed URIs list, space separated")
)
post_logout_redirect_uris = models.TextField(
blank=True, validators=[URIValidator],
help_text=_("Allowed Post Logout URIs list, space separated"),
default="",
)
client_id = models.CharField(
verbose_name=_("Client ID"),
max_length=100, unique=True, default=generate_client_id, db_index=True
-1
View File
@@ -687,7 +687,6 @@ class EventSettingsSerializer(SettingsSerializer):
'allow_modifications_after_checkin',
'show_quota_left',
'waiting_list_enabled',
'waiting_list_auto_disable',
'waiting_list_hours',
'waiting_list_auto',
'waiting_list_names_asked',
+3 -5
View File
@@ -486,11 +486,11 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
'company', 'street', 'zipcode', 'city', 'country', 'state', 'discount',
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled',
'valid_from', 'valid_until', 'blocked', 'voucher_budget_use')
'valid_from', 'valid_until', 'blocked')
read_only_fields = (
'id', 'order', 'positionid', 'item', 'variation', 'price', 'voucher', 'tax_rate', 'tax_value', 'secret',
'addon_to', 'subevent', 'checkins', 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data',
'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked', 'voucher_budget_use'
'seat', 'canceled', 'discount', 'valid_from', 'valid_until', 'blocked'
)
def __init__(self, *args, **kwargs):
@@ -1585,7 +1585,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
if order.total == Decimal('0.00') and validated_data.get('status') == Order.STATUS_PAID and not payment_provider:
payment_provider = 'free'
if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID and not validated_data.get('require_approval'):
if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID:
order.status = Order.STATUS_PAID
order.save()
order.payments.create(
@@ -1597,8 +1597,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
elif validated_data.get('status') == Order.STATUS_PAID:
if not payment_provider:
raise ValidationError('You cannot create a paid order without a payment provider.')
if validated_data.get('require_approval'):
raise ValidationError('You cannot create a paid order that requires approval.')
order.payments.create(
amount=order.total,
provider=payment_provider,
+2 -2
View File
@@ -79,8 +79,8 @@ class CustomerSerializer(I18nAwareModelSerializer):
class Meta:
model = Customer
fields = ('identifier', 'external_identifier', 'email', 'phone', 'name', 'name_parts', 'is_active',
'is_verified', 'last_login', 'date_joined', 'locale', 'last_modified', 'notes')
fields = ('identifier', 'external_identifier', 'email', 'name', 'name_parts', 'is_active', 'is_verified', 'last_login', 'date_joined',
'locale', 'last_modified', 'notes')
def update(self, instance, validated_data):
if instance and instance.provider_id:
-27
View File
@@ -35,7 +35,6 @@ from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django.utils.timezone import now
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
@@ -587,32 +586,6 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
'list': MiniCheckinListSerializer(list_by_event[revoked_matches[0].event_id]).data,
}, status=400)
else:
if media.linked_orderposition.order.event_id not in list_by_event:
# Medium exists but connected ticket is for the wrong event
if not simulate:
checkinlists[0].event.log_action('pretix.event.checkin.unknown', data={
'datetime': datetime,
'type': checkin_type,
'list': checkinlists[0].pk,
'barcode': raw_barcode,
'searched_lists': [cl.pk for cl in checkinlists]
}, user=user, auth=auth)
Checkin.objects.create(
position=None,
successful=False,
error_reason=Checkin.REASON_INVALID,
error_explanation=gettext('Medium connected to other event'),
**common_checkin_args,
)
return Response({
'detail': 'Not found.', # for backwards compatibility
'status': 'error',
'reason': Checkin.REASON_INVALID,
'reason_explanation': gettext('Medium connected to other event'),
'require_attention': False,
'checkin_texts': [],
'list': MiniCheckinListSerializer(checkinlists[0]).data,
}, status=404)
op_candidates = [media.linked_orderposition]
if list_by_event[media.linked_orderposition.order.event_id].addon_match:
op_candidates += list(media.linked_orderposition.addons.all())
+3 -4
View File
@@ -176,7 +176,7 @@ class ParametrizedItemWebhookEvent(ParametrizedWebhookEvent):
}
class ParametrizedOrderPositionCheckinWebhookEvent(ParametrizedOrderWebhookEvent):
class ParametrizedOrderPositionWebhookEvent(ParametrizedOrderWebhookEvent):
def build_payload(self, logentry: LogEntry):
d = super().build_payload(logentry)
@@ -185,7 +185,6 @@ class ParametrizedOrderPositionCheckinWebhookEvent(ParametrizedOrderWebhookEvent
d['orderposition_id'] = logentry.parsed_data.get('position')
d['orderposition_positionid'] = logentry.parsed_data.get('positionid')
d['checkin_list'] = logentry.parsed_data.get('list')
d['type'] = logentry.parsed_data.get('type')
d['first_checkin'] = logentry.parsed_data.get('first_checkin')
return d
@@ -297,11 +296,11 @@ def register_default_webhook_events(sender, **kwargs):
'pretix.event.order.denied',
_('Order denied'),
),
ParametrizedOrderPositionCheckinWebhookEvent(
ParametrizedOrderPositionWebhookEvent(
'pretix.event.checkin',
_('Ticket checked in'),
),
ParametrizedOrderPositionCheckinWebhookEvent(
ParametrizedOrderPositionWebhookEvent(
'pretix.event.checkin.reverted',
_('Ticket check-in reverted'),
),
+1 -1
View File
@@ -189,7 +189,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
tpl = get_template(self.template_name)
body_html = tpl.render(htmlctx)
inliner = css_inline.CSSInliner(keep_style_tags=False)
inliner = css_inline.CSSInliner(remove_style_tags=True)
body_html = inliner.inline(body_html)
return body_html
-1
View File
@@ -86,7 +86,6 @@ class InvoiceExporterMixin:
('', _('All payment providers')),
] + [
(k, v.verbose_name) for k, v in self.event.get_payment_providers().items()
if not v.is_meta
],
required=False,
help_text=_('Only include invoices for orders that have at least one payment attempt '
+2 -7
View File
@@ -257,8 +257,8 @@ class OrderListExporter(MultiSheetListExporter):
tax_rates = self._get_all_tax_rates(qs)
headers = [
_('Event slug'), _('Event name'), _('Order code'), _('Order total'), _('Status'), _('Email'),
_('Phone number'), _('Order date'), _('Order time'), _('Company'), _('Name'),
_('Event slug'), _('Order code'), _('Order total'), _('Status'), _('Email'), _('Phone number'),
_('Order date'), _('Order time'), _('Company'), _('Name'),
]
name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme] if not self.is_multievent else None
if name_scheme and len(name_scheme['fields']) > 1:
@@ -335,7 +335,6 @@ class OrderListExporter(MultiSheetListExporter):
row = [
self.event_object_cache[order.event_id].slug,
str(self.event_object_cache[order.event_id].name),
order.code,
order.total,
order.get_extended_status_display(),
@@ -437,7 +436,6 @@ class OrderListExporter(MultiSheetListExporter):
headers = [
_('Event slug'),
_('Event name'),
_('Order code'),
_('Status'),
_('Email'),
@@ -474,7 +472,6 @@ class OrderListExporter(MultiSheetListExporter):
tz = ZoneInfo(order.event.settings.timezone)
row = [
self.event_object_cache[order.event_id].slug,
str(self.event_object_cache[order.event_id].name),
order.code,
_("canceled") if op.canceled else order.get_extended_status_display(),
order.email,
@@ -553,7 +550,6 @@ class OrderListExporter(MultiSheetListExporter):
headers = [
_('Event slug'),
_('Event name'),
_('Order code'),
_('Position ID'),
_('Status'),
@@ -655,7 +651,6 @@ class OrderListExporter(MultiSheetListExporter):
tz = ZoneInfo(self.event_object_cache[order.event_id].settings.timezone)
row = [
self.event_object_cache[order.event_id].slug,
str(self.event_object_cache[order.event_id].name),
order.code,
op.positionid,
_("canceled") if op.canceled else order.get_extended_status_display(),
+6 -17
View File
@@ -609,38 +609,27 @@ class BaseQuestionsForm(forms.Form):
max_date = now().astimezone(event.timezone) + timedelta(days=item.validity_dynamic_start_choice_day_limit)
else:
max_date = None
min_date = now()
initial = None
if (item.require_membership or (pos.variation and pos.variation.require_membership)) and pos.used_membership:
if pos.used_membership.date_start >= now():
initial = min_date = pos.used_membership.date_start
max_date = min(max_date, pos.used_membership.date_end) if max_date else pos.used_membership.date_end
if item.validity_dynamic_duration_months or item.validity_dynamic_duration_days:
attrs = {}
if max_date:
attrs['data-max'] = max_date.date().isoformat()
if min_date:
attrs['data-min'] = min_date.date().isoformat()
self.fields['requested_valid_from'] = forms.DateField(
label=_('Start date'),
help_text='' if initial else _('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=bool(initial),
initial=pos.requested_valid_from or initial,
help_text=_('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=False,
widget=DatePickerWidget(attrs),
validators=([MaxDateValidator(max_date.date())] if max_date else []) + [MinDateValidator(min_date.date())]
validators=[MaxDateValidator(max_date.date())] if max_date else []
)
else:
self.fields['requested_valid_from'] = forms.SplitDateTimeField(
label=_('Start date'),
help_text='' if initial else _('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=bool(initial),
initial=pos.requested_valid_from or initial,
help_text=_('If you keep this empty, the ticket will be valid starting at the time of purchase.'),
required=False,
widget=SplitDateTimePickerWidget(
time_format=get_format_without_seconds('TIME_INPUT_FORMATS'),
min_date=min_date,
max_date=max_date
),
validators=([MaxDateTimeValidator(max_date)] if max_date else []) + [MinDateTimeValidator(min_date)]
validators=[MaxDateTimeValidator(max_date)] if max_date else []
)
add_fields = {}
+63
View File
@@ -0,0 +1,63 @@
#
# 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 bootstrap3.renderers import (
FieldRenderer as BaseFieldRenderer,
InlineFieldRenderer as BaseInlineFieldRenderer,
)
from django.forms import (
CheckboxInput, CheckboxSelectMultiple, ClearableFileInput, RadioSelect,
SelectDateWidget,
)
class FieldRenderer(BaseFieldRenderer):
# Local application of https://github.com/zostera/django-bootstrap3/pull/859
def post_widget_render(self, html):
if isinstance(self.widget, CheckboxSelectMultiple):
html = self.list_to_class(html, "checkbox")
elif isinstance(self.widget, RadioSelect):
html = self.list_to_class(html, "radio")
elif isinstance(self.widget, SelectDateWidget):
html = self.fix_date_select_input(html)
elif isinstance(self.widget, ClearableFileInput):
html = self.fix_clearable_file_input(html)
elif isinstance(self.widget, CheckboxInput):
html = self.put_inside_label(html)
return html
class InlineFieldRenderer(BaseInlineFieldRenderer):
# Local application of https://github.com/zostera/django-bootstrap3/pull/859
def post_widget_render(self, html):
if isinstance(self.widget, CheckboxSelectMultiple):
html = self.list_to_class(html, "checkbox")
elif isinstance(self.widget, RadioSelect):
html = self.list_to_class(html, "radio")
elif isinstance(self.widget, SelectDateWidget):
html = self.fix_date_select_input(html)
elif isinstance(self.widget, ClearableFileInput):
html = self.fix_clearable_file_input(html)
elif isinstance(self.widget, CheckboxInput):
html = self.put_inside_label(html)
return html
+3 -3
View File
@@ -33,7 +33,7 @@
# License for the specific language governing permissions and limitations under the License.
import os
from datetime import datetime
from datetime import date
from django import forms
from django.utils.formats import get_format
@@ -188,11 +188,11 @@ class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
time_attrs['autocomplete'] = 'off'
if min_date:
date_attrs['data-min'] = (
min_date if not isinstance(min_date, datetime) else min_date.astimezone(get_current_timezone()).date()
min_date if isinstance(min_date, date) else min_date.astimezone(get_current_timezone()).date()
).isoformat()
if max_date:
date_attrs['data-max'] = (
max_date if not isinstance(max_date, datetime) else max_date.astimezone(get_current_timezone()).date()
max_date if isinstance(max_date, date) else max_date.astimezone(get_current_timezone()).date()
).isoformat()
def date_placeholder():
+1 -1
View File
@@ -625,7 +625,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
)]
else:
tdata = [(
Paragraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
Paragraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['BoldRight']),
Paragraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
Paragraph(self._normalize(pgettext('invoice', 'Amount')), self.stylesheet['BoldRightNoSplit']),
)]
@@ -1,19 +0,0 @@
# Generated by Django 4.2.9 on 2024-01-30 11:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0256_itemvariation_unavail_modes"),
]
operations = [
migrations.AlterField(
model_name="item",
name="default_price",
field=models.DecimalField(decimal_places=2, default=0, max_digits=13),
preserve_default=False,
),
]
+25 -15
View File
@@ -37,7 +37,9 @@ import json
import operator
from datetime import timedelta
from functools import reduce
from urllib.parse import urlparse
import webauthn
from django.conf import settings
from django.contrib.auth.models import (
AbstractBaseUser, BaseUserManager, PermissionsMixin,
@@ -51,12 +53,11 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_otp.models import Device
from django_scopes import scopes_disabled
from webauthn.helpers.structs import PublicKeyCredentialDescriptor
from pretix.base.i18n import language
from pretix.helpers.urls import build_absolute_uri
from ...helpers.u2f import pub_key_from_der, websafe_decode
from ...helpers.u2f import pub_key_from_der, websafe_decode, websafe_encode
from .base import LoggingMixin
@@ -605,12 +606,7 @@ class U2FDevice(Device):
json_data = models.TextField()
@property
def webauthndevice(self):
d = json.loads(self.json_data)
return PublicKeyCredentialDescriptor(websafe_decode(d['keyHandle']))
@property
def webauthnpubkey(self):
def webauthnuser(self):
d = json.loads(self.json_data)
# We manually need to convert the pubkey from DER format (used in our
# former U2F implementation) to the format required by webauthn. This
@@ -622,7 +618,16 @@ class U2FDevice(Device):
pub_key.public_numbers().x, pub_key.public_numbers().y
)
)
return pub_key
return webauthn.WebAuthnUser(
d['keyHandle'],
self.user.email,
str(self.user),
settings.SITE_URL,
d['keyHandle'],
websafe_encode(pub_key),
1,
urlparse(settings.SITE_URL).netloc
)
class WebAuthnDevice(Device):
@@ -634,9 +639,14 @@ class WebAuthnDevice(Device):
sign_count = models.IntegerField(default=0)
@property
def webauthndevice(self):
return PublicKeyCredentialDescriptor(websafe_decode(self.credential_id))
@property
def webauthnpubkey(self):
return websafe_decode(self.pub_key)
def webauthnuser(self):
return webauthn.WebAuthnUser(
self.ukey,
self.user.email,
str(self.user),
settings.SITE_URL,
self.credential_id,
self.pub_key,
self.sign_count,
urlparse(settings.SITE_URL).netloc
)
-11
View File
@@ -229,14 +229,6 @@ class EventMixin:
else:
return self.presale_end
@property
def waiting_list_active(self):
if not self.settings.waiting_list_enabled:
return False
if self.settings.waiting_list_auto_disable:
return self.settings.waiting_list_auto_disable.datetime(self) > now()
return True
@property
def presale_has_ended(self):
"""
@@ -273,9 +265,6 @@ class EventMixin:
@property
def event_microdata(self):
if self.settings.event_microdata:
return self.settings.event_microdata
import json
eventdict = {
+1 -1
View File
@@ -430,7 +430,7 @@ class Item(LoggedModel):
help_text=_("If this product has multiple variations, you can set different prices for each of the "
"variations. If a variation does not have a special price or if you do not have variations, "
"this price will be used."),
max_digits=13, decimal_places=2,
max_digits=13, decimal_places=2, null=True
)
free_price = models.BooleanField(
default=False,
+3 -8
View File
@@ -49,8 +49,7 @@ class MembershipType(LoggedModel):
allow_parallel_usage = models.BooleanField(
verbose_name=_('Parallel usage is allowed'),
help_text=_('If this is selected, the membership can be used to purchase tickets for events happening at the same time. Note '
'that this will only check for an identical start time of the events, not for any overlap between events. An overlap '
'check will be performed if there is a product-level validity of the ticket.'),
'that this will only check for an identical start time of the events, not for any overlap between events.'),
default=False
)
max_usages = models.PositiveIntegerField(
@@ -163,12 +162,8 @@ class Membership(models.Model):
def attendee_name(self):
return build_name(self.attendee_name_parts, fallback_scheme=lambda: self.customer.organizer.settings.name_scheme)
def is_valid(self, ev=None, ticket_valid_from=None, valid_from_not_chosen=False):
if valid_from_not_chosen:
return not self.canceled and self.date_end >= now()
elif ticket_valid_from:
dt = ticket_valid_from
elif ev:
def is_valid(self, ev=None):
if ev:
dt = ev.date_from
else:
dt = now()
+3 -3
View File
@@ -451,9 +451,9 @@ class Order(LockModel, LoggedModel):
if results:
qs = qs.annotate(
is_overpaid=Case(
When(~Q(status__in=(Order.STATUS_CANCELED, Order.STATUS_EXPIRED)) & Q(pending_sum_t__lt=-1e-8),
When(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=-1e-8),
then=Value(1)),
When(Q(status__in=(Order.STATUS_CANCELED, Order.STATUS_EXPIRED)) & Q(pending_sum_rc__lt=-1e-8),
When(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=-1e-8),
then=Value(1)),
default=Value(0),
output_field=models.IntegerField()
@@ -468,7 +468,7 @@ class Order(LockModel, LoggedModel):
is_underpaid=Case(
When(Q(status=Order.STATUS_PAID) & Q(pending_sum_t__gt=1e-8),
then=Value(1)),
When(Q(status__in=(Order.STATUS_CANCELED, Order.STATUS_EXPIRED)) & Q(pending_sum_rc__gt=1e-8),
When(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__gt=1e-8),
then=Value(1)),
default=Value(0),
output_field=models.IntegerField()
+7 -18
View File
@@ -351,6 +351,9 @@ class Voucher(LoggedModel):
'variations.'))
if variation and not item.variations.filter(pk=variation.pk).exists():
raise ValidationError(_('This variation does not belong to this product.'))
if item.has_variations and not variation and data.get('block_quota'):
raise ValidationError(_('You can only block quota if you specify a specific product variation. '
'Otherwise it might be unclear which quotas to block.'))
if item.category and item.category.is_addon:
raise ValidationError(_('It is currently not possible to create vouchers for add-on products.'))
elif block_quota:
@@ -428,15 +431,7 @@ class Voucher(LoggedModel):
elif old_instance.variation:
quotas |= set(old_instance.variation.quotas.filter(subevent=old_instance.subevent))
elif old_instance.item:
if old_instance.item.has_variations:
quotas |= set(
Quota.objects.filter(pk__in=Quota.variations.through.objects.filter(
itemvariation__item=old_instance.item,
quota__subevent=old_instance.subevent,
).values('quota_id'))
)
else:
quotas |= set(old_instance.item.quotas.filter(subevent=old_instance.subevent))
quotas |= set(old_instance.item.quotas.filter(subevent=old_instance.subevent))
return quotas
@staticmethod
@@ -451,19 +446,13 @@ class Voucher(LoggedModel):
if quota:
new_quotas = {quota}
elif item and item.has_variations and not variation:
raise ValidationError(_('You can only block quota if you specify a specific product variation. '
'Otherwise it might be unclear which quotas to block.'))
elif item and variation:
new_quotas = set(variation.quotas.filter(subevent=data.get('subevent')))
elif item and not item.has_variations:
new_quotas = set(item.quotas.filter(subevent=data.get('subevent')))
elif item and item.has_variations:
new_quotas = set(
Quota.objects.filter(
pk__in=Quota.variations.through.objects.filter(
itemvariation__item=old_instance.item,
quota__subevent=data.get('subevent'),
).values('quota_id')
)
)
else:
raise ValidationError(_('You need to select a specific product or quota if this voucher should reserve '
'tickets.'))
+3 -1
View File
@@ -1311,7 +1311,9 @@ class GiftCardPayment(BasePaymentProvider):
@property
def public_name(self) -> str:
return str(self.settings.get("public_name", as_type=LazyI18nString) or _("Gift card"))
return str(self.settings.get("public_name", as_type=LazyI18nString)) or _(
"Gift card"
)
@property
def settings_form_fields(self):
+1 -1
View File
@@ -203,7 +203,7 @@ error_messages = {
'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.',
'min'
),
'addon_no_multi': gettext_lazy('You can select every add-on from the category %(cat)s for the product %(base)s at most once.'),
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
'addon_only': gettext_lazy('One of the products you selected can only be bought as an add-on to another product.'),
'bundled_only': gettext_lazy('One of the products you selected can only be bought part of a bundle.'),
'seat_required': gettext_lazy('You need to select a specific seat.'),
+7 -7
View File
@@ -104,10 +104,10 @@ def build_invoice(invoice: Invoice) -> Invoice:
expire_date=date_format(invoice.order.expires, "SHORT_DATE_FORMAT")
)
invoice.introductory_text = str(introductory).replace('\n', '<br />').replace('\r', '')
invoice.additional_text = str(additional).replace('\n', '<br />').replace('\r', '')
invoice.introductory_text = str(introductory).replace('\n', '<br />')
invoice.additional_text = str(additional).replace('\n', '<br />')
invoice.footer_text = str(footer)
invoice.payment_provider_text = str(payment).replace('\n', '<br />').replace('\r', '')
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
invoice.payment_provider_stamp = str(payment_stamp) if payment_stamp else None
try:
@@ -462,10 +462,10 @@ def build_preview_invoice_pdf(event):
footer = event.settings.get('invoice_footer_text', as_type=LazyI18nString)
payment = _("A payment provider specific text might appear here.")
invoice.introductory_text = str(introductory).replace('\n', '<br />').replace('\r', '')
invoice.additional_text = str(additional).replace('\n', '<br />').replace('\r', '')
invoice.introductory_text = str(introductory).replace('\n', '<br />')
invoice.additional_text = str(additional).replace('\n', '<br />')
invoice.footer_text = str(footer)
invoice.payment_provider_text = str(payment).replace('\n', '<br />').replace('\r', '')
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
invoice.payment_provider_stamp = _('paid')
invoice.invoice_to_name = _("John Doe")
invoice.invoice_to_street = _("214th Example Street")
@@ -488,7 +488,7 @@ def build_preview_invoice_pdf(event):
InvoiceLine.objects.create(
invoice=invoice, description=_("Sample product {}").format(i + 1),
gross_value=tax.gross, tax_value=tax.tax,
tax_rate=tax.rate, tax_name=tax.name
tax_rate=tax.rate
)
else:
for i in range(5):
-1
View File
@@ -383,7 +383,6 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
if event:
with scopes_disabled():
event = Event.objects.get(id=event)
organizer = event.organizer
backend = event.get_mail_backend()
cm = lambda: scope(organizer=event.organizer) # noqa
elif organizer:
+23 -73
View File
@@ -29,8 +29,8 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from pretix.base.models import (
AbstractPosition, CartPosition, Customer, Event, Item, Membership, Order,
OrderPosition, SubEvent,
AbstractPosition, Customer, Event, Item, Membership, Order, OrderPosition,
SubEvent,
)
from pretix.helpers import OF_SELF
@@ -82,8 +82,7 @@ def create_membership(customer: Customer, position: OrderPosition):
)
def validate_memberships_in_order(customer: Customer, positions: List[AbstractPosition], event: Event, lock=False, ignored_order: Order = None, testmode=False,
valid_from_not_chosen=False):
def validate_memberships_in_order(customer: Customer, positions: List[AbstractPosition], event: Event, lock=False, ignored_order: Order = None, testmode=False):
"""
Validate that a set of cart or order positions. This currently does not validate
@@ -93,8 +92,6 @@ def validate_memberships_in_order(customer: Customer, positions: List[AbstractPo
:param lock: Whether to place a SELECT FOR UPDATE lock on the selected memberships
:param ignored_order: An order that should be ignored for usage counting
:param testmode: If ``True``, only test mode memberships are allowed. If ``False``, test mode memberships are not allowed.
:param valid_from_not_chosen: Set to ``True`` to indicate that the customer is in an early step of the checkout flow
where the valid_from date is not selected yet. In this case, the valid_from date is not checked.
"""
tz = event.timezone
applicable_positions = [
@@ -135,11 +132,7 @@ def validate_memberships_in_order(customer: Customer, positions: List[AbstractPo
qs = qs.exclude(order_id=ignored_order.pk)
m._used_at_dates = [
(op.subevent or op.order.event).date_from
for op in qs if not op.valid_from or not op.valid_until
]
m._used_for_ranges = [
(op.valid_from, op.valid_until)
for op in qs if op.valid_from or op.valid_until
for op in qs
]
for p in applicable_positions:
@@ -154,44 +147,22 @@ def validate_memberships_in_order(customer: Customer, positions: List[AbstractPo
_('You selected membership that has been canceled.')
)
if m.testmode and not testmode:
if m.testmode != testmode:
raise ValidationError(
_('You can not use a test mode membership for tickets that are not in test mode.')
)
elif not m.testmode and testmode:
raise ValidationError(
_('You need to add a test mode membership to the customer account to use it in test mode.')
_('You can only use a test mode membership for test mode tickets.')
)
ev = p.subevent or event
if isinstance(p, (OrderPosition, CartPosition)):
# override_ variants are for usage of fake cart in OrderChangeManager
valid_from = getattr(p, 'override_valid_from', p.valid_from)
valid_until = getattr(p, 'override_valid_until', p.valid_until)
else: # future safety, not technically defined on AbstractPosition
valid_from = None
valid_until = None
if not m.is_valid(ev, valid_from, valid_from_not_chosen=p.item.validity_dynamic_start_choice and valid_from_not_chosen):
if valid_from:
raise ValidationError(
_('You selected a membership that is valid from {start} to {end}, but selected a ticket that '
'starts to be valid on {date}.').format(
start=date_format(m.date_start.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
end=date_format(m.date_end.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
date=date_format(valid_from.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
)
)
else:
raise ValidationError(
_('You selected a membership that is valid from {start} to {end}, but selected an event '
'taking place at {date}.').format(
start=date_format(m.date_start.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
end=date_format(m.date_end.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
date=date_format(ev.date_from.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
)
if not m.is_valid(ev):
raise ValidationError(
_('You selected a membership that is valid from {start} to {end}, but selected an event '
'taking place at {date}.').format(
start=date_format(m.date_start.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
end=date_format(m.date_end.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
date=date_format(ev.date_from.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
)
)
if p.variation and p.variation.require_membership:
types = p.variation.require_membership_types.all()
@@ -217,34 +188,13 @@ def validate_memberships_in_order(customer: Customer, positions: List[AbstractPo
m.usages += 1
if not m.membership_type.allow_parallel_usage:
if (valid_from or valid_until) and not (p.item.validity_dynamic_start_choice and valid_from_not_chosen):
for used_range in m._used_for_ranges:
if valid_from and valid_from > used_range[1]:
continue
if valid_until and valid_until < used_range[0]:
continue
raise ValidationError(
_('You are trying to use a membership of type "{type}" for a ticket valid from {valid_from} '
'until {valid_until}, however you already used the same membership for a different ticket '
'that overlaps with this time frame ({conflict_from} {conflict_until}).').format(
type=m.membership_type.name,
valid_from=date_format(valid_from.astimezone(tz), 'SHORT_DATETIME_FORMAT') if valid_from else _('start'),
valid_until=date_format(valid_until.astimezone(tz), 'SHORT_DATETIME_FORMAT') if valid_until else _('open end'),
conflict_from=date_format(used_range[0].astimezone(tz), 'SHORT_DATETIME_FORMAT') if used_range[0] else _('start'),
conflict_until=date_format(used_range[1].astimezone(tz), 'SHORT_DATETIME_FORMAT') if used_range[1] else _('open end'),
)
df = ev.date_from
if any(abs(df - d) < timedelta(minutes=1) for d in m._used_at_dates):
raise ValidationError(
_('You are trying to use a membership of type "{type}" for an event taking place at {date}, '
'however you already used the same membership for a different ticket at the same time.').format(
type=m.membership_type.name,
date=date_format(ev.date_from.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
)
m._used_for_ranges.append((p.valid_from, p.valid_until))
if not valid_from or not valid_until:
df = ev.date_from
if any(abs(df - d) < timedelta(minutes=1) for d in m._used_at_dates):
raise ValidationError(
_('You are trying to use a membership of type "{type}" for an event taking place at {date}, '
'however you already used the same membership for a different ticket at the same time.').format(
type=m.membership_type.name,
date=date_format(ev.date_from.astimezone(tz), 'SHORT_DATETIME_FORMAT'),
)
)
m._used_at_dates.append(ev.date_from)
)
m._used_at_dates.append(ev.date_from)
+1 -1
View File
@@ -136,7 +136,7 @@ def send_notification_mail(notification: Notification, user: User):
tpl_html = get_template('pretixbase/email/notification.html')
body_html = tpl_html.render(ctx)
inliner = css_inline.CSSInliner(keep_style_tags=False)
inliner = css_inline.CSSInliner(remove_style_tags=True)
body_html = inliner.inline(body_html)
tpl_plain = get_template('pretixbase/email/notification.txt')
+3 -11
View File
@@ -197,7 +197,7 @@ error_messages = {
'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.',
'min'
),
'addon_no_multi': gettext_lazy('You can select every add-on from the category %(cat)s for the product %(base)s at most once.'),
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
'addon_already_checked_in': gettext_lazy('You cannot remove the position %(addon)s since it has already been checked in.'),
}
@@ -220,7 +220,7 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
is_available = order._is_still_available(now(), count_waitinglist=False, check_voucher_usage=True,
check_memberships=True, lock=True, force=force)
if is_available is True:
if order.payment_refund_sum >= order.total and not order.require_approval:
if order.payment_refund_sum >= order.total:
order.status = Order.STATUS_PAID
else:
order.status = Order.STATUS_PENDING
@@ -2500,7 +2500,7 @@ class OrderChangeManager:
remaining_total = sum([p.price for p in self.order.positions.all()]) + sum([f.value for f in self.order.fees.all()])
offset_amount = min(max(0, self.completed_payment_sum - remaining_total), split_order.total)
if offset_amount >= split_order.total and not split_order.require_approval:
if offset_amount >= split_order.total:
split_order.status = Order.STATUS_PAID
else:
split_order.status = Order.STATUS_PENDING
@@ -2671,7 +2671,6 @@ class OrderChangeManager:
for p in self.order.positions.all():
cp = CartPosition(
event=self.event,
item=p.item,
variation=p.variation,
attendee_name_parts=p.attendee_name_parts,
@@ -2692,23 +2691,16 @@ class OrderChangeManager:
positions_to_fake_cart[op.position].seat = op.seat
elif isinstance(op, self.MembershipOperation):
positions_to_fake_cart[op.position].used_membership = op.membership
elif isinstance(op, self.ChangeValidFromOperation):
positions_to_fake_cart[op.position].override_valid_from = op.valid_from
elif isinstance(op, self.ChangeValidUntilOperation):
positions_to_fake_cart[op.position].override_valid_until = op.valid_until
elif isinstance(op, self.CancelOperation) and op.position in positions_to_fake_cart:
fake_cart.remove(positions_to_fake_cart[op.position])
elif isinstance(op, self.AddOperation):
cp = CartPosition(
event=self.event,
item=op.item,
variation=op.variation,
used_membership=op.membership,
subevent=op.subevent,
seat=op.seat,
)
cp.override_valid_from = op.valid_from
cp.override_valid_until = op.valid_until
fake_cart.append(cp)
try:
validate_memberships_in_order(self.order.customer, fake_cart, self.event, lock=True, ignored_order=self.order, testmode=self.order.testmode)
+6 -17
View File
@@ -90,8 +90,8 @@ class QuotaAvailability:
self._count_waitinglist = count_waitinglist
self._ignore_closed = ignore_closed
self._full_results = full_results
self._item_to_quotas = defaultdict(set)
self._var_to_quotas = defaultdict(set)
self._item_to_quotas = defaultdict(list)
self._var_to_quotas = defaultdict(list)
self._early_out = early_out
self._quota_objects = {}
self.results = {}
@@ -243,16 +243,13 @@ class QuotaAvailability:
quota_id__in=[q.pk for q in quotas]
).values('quota_id', 'item_id')
for m in q_items:
self._item_to_quotas[m['item_id']].add(self._quota_objects[m['quota_id']])
self._item_to_quotas[m['item_id']].append(self._quota_objects[m['quota_id']])
q_vars = Quota.variations.through.objects.filter(
quota_id__in=[q.pk for q in quotas]
).values('quota_id', 'itemvariation_id', 'itemvariation__item_id')
).values('quota_id', 'itemvariation_id')
for m in q_vars:
self._var_to_quotas[m['itemvariation_id']].add(self._quota_objects[m['quota_id']])
# We can't be 100% certain that a quota, when it is connected to a variation, is also always connected to
# the parent item, so we double-check here just to be sure.
self._item_to_quotas[m['itemvariation__item_id']].add(self._quota_objects[m['quota_id']])
self._var_to_quotas[m['itemvariation_id']].append(self._quota_objects[m['quota_id']])
self._compute_orders(quotas, q_items, q_vars, size_left)
@@ -381,10 +378,7 @@ class QuotaAvailability:
Q(
Q(
Q(variation_id__isnull=True) &
Q(item_id__in=(
{i['item_id'] for i in q_items if i['quota_id'] in quota_ids} |
{i['itemvariation__item_id'] for i in q_vars if i['quota_id'] in quota_ids}
))
Q(item_id__in={i['item_id'] for i in q_items if i['quota_id'] in quota_ids})
) | Q(
variation_id__in={i['itemvariation_id'] for i in q_vars if i['quota_id'] in quota_ids}
) | Q(
@@ -446,11 +440,6 @@ class QuotaAvailability:
self.results[q] = Quota.AVAILABILITY_RESERVED, 0
def _compute_waitinglist(self, quotas, q_items, q_vars, size_left):
quotas = [
q for q in quotas
if not q.event.settings.waiting_list_auto_disable or q.event.settings.waiting_list_auto_disable.datetime(q.subevent or q.event) > now()
]
events = {q.event_id for q in quotas}
subevents = {q.subevent_id for q in quotas}
quota_ids = {q.pk for q in quotas}
-3
View File
@@ -110,9 +110,6 @@ def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None)
continue
if wle.subevent and not wle.subevent.presale_is_running:
continue
if event.settings.waiting_list_auto_disable and event.settings.waiting_list_auto_disable.datetime(wle.subevent or event) <= now():
gone.add((wle.item, wle.variation, wle.subevent))
continue
if not wle.item.is_available():
gone.add((wle.item, wle.variation, wle.subevent))
continue
-13
View File
@@ -1397,19 +1397,6 @@ DEFAULTS = {
widget=forms.NumberInput(),
)
},
'waiting_list_auto_disable': {
'default': None,
'type': RelativeDateWrapper,
'form_class': RelativeDateTimeField,
'serializer_class': SerializerRelativeDateTimeField,
'form_kwargs': dict(
label=_("Disable waiting list"),
help_text=_("The waiting list will be fully disabled after this date. This means that nobody can add "
"themselves to the waiting list any more, but also that tickets will be available for sale "
"again if quota permits, even if there are still people on the waiting list. Vouchers that "
"have already been sent remain active."),
)
},
'waiting_list_names_asked': {
'default': 'False',
'type': bool,
@@ -0,0 +1,6 @@
{# this is the version from django 3.x, prior to https://github.com/django/django/commit/5942ab5eb165ee2e759174e297148a40dd855920 so that django-bootstrap3 can keep doing its magic #}
{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>{% for group, options, index in widget.optgroups %}{% if group %}
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for option in options %}
<li>{% include option.template_name with widget=option %}</li>{% endfor %}{% if group %}
</ul></li>{% endif %}{% endfor %}
</ul>{% endwith %}
+5 -14
View File
@@ -123,6 +123,8 @@ class ClearableBasenameFileInput(forms.ClearableFileInput):
@property
def name(self):
if hasattr(self.file, 'display_name'):
return self.file.display_name
return self.file.name
@property
@@ -217,17 +219,15 @@ class ExtValidationMixin:
def clean(self, *args, **kwargs):
data = super().clean(*args, **kwargs)
from ...base.models import CachedFile
if isinstance(data, (UploadedFile, CachedFile)):
filename = data.name if isinstance(data, UploadedFile) else data.filename
if isinstance(data, UploadedFile):
filename = data.name
ext = os.path.splitext(filename)[1]
ext = ext.lower()
if ext not in self.ext_whitelist:
raise forms.ValidationError(_("Filetype not allowed!"))
if ext in IMAGE_EXTS:
validate_uploaded_file_for_valid_image(data if isinstance(data, UploadedFile) else data.file)
validate_uploaded_file_for_valid_image(data)
return data
@@ -257,12 +257,6 @@ class CachedFileField(ExtFileField):
if isinstance(data, File):
if hasattr(data, '_uploaded_to'):
return data._uploaded_to
try:
self.clean(data)
except ValidationError:
return None
cf = CachedFile.objects.create(
expires=now() + datetime.timedelta(days=1),
date=now(),
@@ -274,9 +268,6 @@ class CachedFileField(ExtFileField):
cf.save()
data._uploaded_to = cf
return cf
if isinstance(data, CachedFile):
return data
return super().bound_data(data, initial)
def clean(self, *args, **kwargs):
-1
View File
@@ -539,7 +539,6 @@ class EventSettingsForm(EventSettingsValidationMixin, FormPlaceholderMixin, Sett
'region',
'show_quota_left',
'waiting_list_enabled',
'waiting_list_auto_disable',
'waiting_list_hours',
'waiting_list_auto',
'waiting_list_names_asked',
+5 -2
View File
@@ -309,8 +309,11 @@ class OrderFilterForm(FilterForm):
elif s in ('p', 'n', 'e', 'c', 'r'):
qs = qs.filter(status=s)
elif s == 'overpaid':
qs = Order.annotate_overpayments(qs, refunds=False, results=True, sums=False)
qs = qs.filter(is_overpaid=True)
qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
qs = qs.filter(
Q(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=0))
| Q(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=0))
)
elif s == 'rc':
qs = qs.filter(
cancellation_requests__isnull=False
+2 -1
View File
@@ -19,7 +19,6 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from bootstrap3.renderers import FieldRenderer, InlineFieldRenderer
from bootstrap3.text import text_value
from django.forms import CheckboxInput, CheckboxSelectMultiple, RadioSelect
from django.forms.utils import flatatt
@@ -28,6 +27,8 @@ from django.utils.safestring import mark_safe
from django.utils.translation import pgettext
from i18nfield.forms import I18nFormField
from pretix.base.forms.renderers import FieldRenderer, InlineFieldRenderer
def render_label(content, label_for=None, label_class=None, label_title='', label_id='', optional=False):
"""
@@ -361,7 +361,6 @@
{% bootstrap_field sform.waiting_list_enabled layout="control" %}
{% bootstrap_field sform.waiting_list_auto layout="control" %}
{% bootstrap_field sform.waiting_list_hours layout="control" %}
{% bootstrap_field sform.waiting_list_auto_disable layout="control" %}
{% bootstrap_field sform.waiting_list_names_asked_required layout="control" %}
{% bootstrap_field sform.waiting_list_phones_asked_required layout="control" %}
{% bootstrap_field sform.waiting_list_phones_explanation_text layout="control" %}
@@ -267,7 +267,6 @@
{% bootstrap_field form.show_quota_left layout="control" %}
{% for f in plugin_forms %}
{% if not f.is_layouts and not f.title %}
<hr />
{% if f.template and not "template" in f.fields %}
{% include f.template with form=f %}
{% else %}
@@ -70,10 +70,6 @@
{% endblocktrans %}
</em>
{% endif %}
{% if position.attendee_name %}
<span class="fa fa-user" aria-hidden="true"></span>
{{ position.attendee_name }}
{% endif %}
</h3>
</div>
<div class="panel-body">
@@ -124,13 +124,6 @@
</button>
</div>
</form>
{% elif order.status == "e" and order.payment_refund_sum != 0 %}
<div class="alert alert-warning">
{% blocktrans trimmed with amount=order.payment_refund_sum|money:request.event.currency %}
This order is expired even though it received payments of {{ amount }}. You can choose to refund
the money below or reactivate it by extending the payment deadline.
{% endblocktrans %}
</div>
{% endif %}
<div class="row">
@@ -183,7 +183,6 @@
</div>
{% endif %}
<div class="row control-group pdf-info">
<hr/>
<div class="col-sm-6">
<label>{% trans "Width (mm)" %}</label><br>
<input type="number" id="pdf-info-width" class="input-block-level form-control">
@@ -225,7 +224,6 @@
</div>
</div>
<div class="row control-group pdf-info">
<hr/>
<div class="col-sm-12">
<label>{% trans "Preferred language" %}</label><br>
<select class="form-control" id="pdf-info-locale">
@@ -236,62 +234,7 @@
</select>
</div>
</div>
<div class="row control-group poweredby">
<div class="col-sm-12">
<label>{% trans "Style" %}</label><br>
<select class="input-block-level form-control" id="toolbox-poweredby-style">
<option value="dark">{% trans "Dark" %}</option>
<option value="white">{% trans "Light" %}</option>
</select>
</div>
</div>
<div class="row control-group imagecontent">
<div class="col-sm-12">
<label>{% trans "Image content" %}</label><br>
<select class="input-block-level form-control" id="toolbox-imagecontent">
<option value="">{% trans "Empty" %}</option>
{% for varname, var in images.items %}
<option value="{{ varname }}">{{ var.label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row control-group text textcontent">
<div class="col-sm-12">
<label>{% trans "Content" %}</label><br>
<select class="input-block-level form-control" id="toolbox-content">
{% for varname, var in variables.items %}
{% if not var.hidden %}
<option data-sample="{{ var.editor_sample }}" {% if var.migrate_from %}data-old-value="{{ var.migrate_from }}"{% endif %} value="{{ varname }}">{{ var.label }}</option>
{% endif %}
{% endfor %}
{% for p in request.organizer.meta_properties.all %}
<option value="meta:{{ p.name }}">
{% trans "Event attribute:" %} {{ p.name }}
</option>
{% endfor %}
{% for p in request.event.item_meta_properties.all %}
<option value="itemmeta:{{ p.name }}">
{% trans "Item attribute:" %} {{ p.name }}
</option>
{% endfor %}
<option value="other_i18n">{% trans "Other… (multilingual)" %}</option>
<option value="other">{% trans "Other…" %}</option>
</select>
<textarea type="text" value="" class="input-block-level form-control"
id="toolbox-content-other"></textarea>
<div class="i18n-form-group" id="toolbox-content-other-i18n">
{% for l in request.event.settings.locales %}
<textarea id="toolbox-content-other-{{ l }}" rows="3" class="input-block-level form-control" title="{{ l }}" lang="{{ l }}"></textarea>
{% endfor %}
</div>
<p class="help-block" id="toolbox-content-other-help">
<a href="?placeholders=true" target="_blank">{% trans "Show available placeholders" %}</a>
</p>
</div>
</div>
<div class="row control-group position">
<hr/>
<div class="col-sm-6">
<label>{% trans "x (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
@@ -334,30 +277,6 @@
</div>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-6">
<label>{% trans "Width (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
id="toolbox-textwidth">
</div>
<div class="col-sm-6">
<label>{% trans "Rotation (°)" %}</label><br>
<input type="number" value="0" class="input-block-level form-control" step="0.1"
id="toolbox-textrotation">
</div>
</div>
<div class="row control-group text">
<hr/>
<div class="col-sm-12">
<label>{% trans "Font" %}</label><br>
<select class="input-block-level form-control" id="toolbox-fontfamily">
<option>Open Sans</option>
{% for family in fonts.keys %}
<option>{{ family }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-6">
<label>{% trans "Font size (pt)" %}</label><br>
@@ -416,6 +335,83 @@
</div>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-12">
<label>{% trans "Font" %}</label><br>
<select class="input-block-level form-control" id="toolbox-fontfamily">
<option>Open Sans</option>
{% for family in fonts.keys %}
<option>{{ family }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row control-group text">
<div class="col-sm-6">
<label>{% trans "Width (mm)" %}</label><br>
<input type="number" value="13" class="input-block-level form-control" step="0.01"
id="toolbox-textwidth">
</div>
<div class="col-sm-6">
<label>{% trans "Rotation (°)" %}</label><br>
<input type="number" value="0" class="input-block-level form-control" step="0.1"
id="toolbox-textrotation">
</div>
</div>
<div class="row control-group poweredby">
<div class="col-sm-12">
<label>{% trans "Style" %}</label><br>
<select class="input-block-level form-control" id="toolbox-poweredby-style">
<option value="dark">{% trans "Dark" %}</option>
<option value="white">{% trans "Light" %}</option>
</select>
</div>
</div>
<div class="row control-group imagecontent">
<div class="col-sm-12">
<label>{% trans "Image content" %}</label><br>
<select class="input-block-level form-control" id="toolbox-imagecontent">
<option value="">{% trans "Empty" %}</option>
{% for varname, var in images.items %}
<option value="{{ varname }}">{{ var.label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row control-group text textcontent">
<div class="col-sm-12">
<label>{% trans "Content" %}</label><br>
<select class="input-block-level form-control" id="toolbox-content">
{% for varname, var in variables.items %}
{% if not var.hidden %}
<option data-sample="{{ var.editor_sample }}" {% if var.migrate_from %}data-old-value="{{ var.migrate_from }}"{% endif %} value="{{ varname }}">{{ var.label }}</option>
{% endif %}
{% endfor %}
{% for p in request.organizer.meta_properties.all %}
<option value="meta:{{ p.name }}">
{% trans "Event attribute:" %} {{ p.name }}
</option>
{% endfor %}
{% for p in request.event.item_meta_properties.all %}
<option value="itemmeta:{{ p.name }}">
{% trans "Item attribute:" %} {{ p.name }}
</option>
{% endfor %}
<option value="other_i18n">{% trans "Other… (multilingual)" %}</option>
<option value="other">{% trans "Other…" %}</option>
</select>
<textarea type="text" value="" class="input-block-level form-control"
id="toolbox-content-other"></textarea>
<div class="i18n-form-group" id="toolbox-content-other-i18n">
{% for l in request.event.settings.locales %}
<textarea id="toolbox-content-other-{{ l }}" rows="3" class="input-block-level form-control" title="{{ l }}" lang="{{ l }}"></textarea>
{% endfor %}
</div>
<p class="help-block" id="toolbox-content-other-help">
<a href="?placeholders=true" target="_blank">{% trans "Show available placeholders" %}</a>
</p>
</div>
</div>
</div>
</div>
<div class="editor-toolbox-text panel panel-default">
@@ -10,10 +10,6 @@
<div class="alert alert-danger">
{% trans "The waiting list is disabled, so if the event is sold out, people cannot add themselves to this list. If you want to enable it, go to the event settings." %}
</div>
{% elif not request.event.waiting_list_active and not request.event.has_subevents %}
<div class="alert alert-danger">
{% trans "The waiting list is no longer active for this event. The waiting list no longer affects quotas and no longer notifies waiting users." %}
</div>
{% endif %}
<div class="row">
{% if 'can_change_orders' in request.eventpermset %}
+32 -53
View File
@@ -32,11 +32,11 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import base64
import json
import logging
import time
from urllib.parse import quote, urlparse
from urllib.parse import quote
import webauthn
from django.conf import settings
@@ -54,7 +54,6 @@ from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from django_otp import match_token
from webauthn.helpers import generate_challenge
from pretix.base.auth import get_auth_backends
from pretix.base.forms.auth import (
@@ -63,6 +62,7 @@ from pretix.base.forms.auth import (
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
from pretix.base.services.mail import SendMailException
from pretix.helpers.http import redirect_to_url
from pretix.helpers.webauthn import generate_challenge
logger = logging.getLogger(__name__)
@@ -389,10 +389,6 @@ def get_u2f_appid(request):
return settings.SITE_URL
def get_webauthn_rp_id(request):
return urlparse(settings.SITE_URL).hostname
class Login2FAView(TemplateView):
template_name = 'pretixcontrol/auth/login_2fa.html'
@@ -431,41 +427,25 @@ class Login2FAView(TemplateView):
devices = U2FDevice.objects.filter(user=self.user)
for d in devices:
credential_current_sign_count = d.sign_count if isinstance(d, WebAuthnDevice) else 0
try:
webauthn_assertion_response = webauthn.verify_authentication_response(
credential=resp,
expected_challenge=base64.b64decode(challenge),
expected_rp_id=get_webauthn_rp_id(self.request),
expected_origin=settings.SITE_URL,
credential_public_key=d.webauthnpubkey,
credential_current_sign_count=credential_current_sign_count,
)
sign_count = webauthn_assertion_response.new_sign_count
if sign_count < credential_current_sign_count:
raise Exception("Possible replay attack, sign count not higher")
except Exception:
wu = d.webauthnuser
if isinstance(d, U2FDevice):
# https://www.w3.org/TR/webauthn/#sctn-appid-extension says
# "When verifying the assertion, expect that the rpIdHash MAY be the hash of the AppID instead of the RP ID."
try:
webauthn_assertion_response = webauthn.verify_authentication_response(
credential=resp,
expected_challenge=base64.b64decode(challenge),
expected_rp_id=get_u2f_appid(self.request),
expected_origin=settings.SITE_URL,
credential_public_key=d.webauthnpubkey,
credential_current_sign_count=credential_current_sign_count,
)
if webauthn_assertion_response.new_sign_count < 1:
raise Exception("Possible replay attack, sign count set")
except Exception:
logger.exception('U2F login failed')
else:
valid = True
break
else:
logger.exception('Webauthn login failed')
# RP_ID needs to be appId for U2F devices, but we can't
# set it that way in U2FDevice.webauthnuser, since that
# breaks the frontend part.
wu.rp_id = settings.SITE_URL
webauthn_assertion_response = webauthn.WebAuthnAssertionResponse(
wu,
resp,
challenge,
settings.SITE_URL,
uv_required=False # User Verification
)
sign_count = webauthn_assertion_response.verify()
except Exception:
logger.exception('U2F login failed')
else:
if isinstance(d, WebAuthnDevice):
d.sign_count = sign_count
@@ -491,24 +471,23 @@ class Login2FAView(TemplateView):
ctx = super().get_context_data()
if 'webauthn_challenge' in self.request.session:
del self.request.session['webauthn_challenge']
challenge = generate_challenge()
self.request.session['webauthn_challenge'] = base64.b64encode(challenge).decode()
challenge = generate_challenge(32)
self.request.session['webauthn_challenge'] = challenge
devices = [
device.webauthndevice for device in WebAuthnDevice.objects.filter(confirmed=True, user=self.user)
device.webauthnuser for device in WebAuthnDevice.objects.filter(confirmed=True, user=self.user)
] + [
device.webauthndevice for device in U2FDevice.objects.filter(confirmed=True, user=self.user)
device.webauthnuser for device in U2FDevice.objects.filter(confirmed=True, user=self.user)
]
if devices:
auth_options = webauthn.generate_authentication_options(
rp_id=get_webauthn_rp_id(self.request),
challenge=challenge,
allow_credentials=devices,
webauthn_assertion_options = webauthn.WebAuthnAssertionOptions(
devices,
challenge
)
# Backwards compatibility to U2F
j = json.loads(webauthn.options_to_json(auth_options))
j["extensions"] = {"appid": get_u2f_appid(self.request)}
ctx['jsondata'] = json.dumps(j)
ad = webauthn_assertion_options.assertion_dict
ad['extensions'] = {
'appid': get_u2f_appid(self.request)
}
ctx['jsondata'] = json.dumps(ad)
return ctx
def get(self, request, *args, **kwargs):
-2
View File
@@ -674,7 +674,6 @@ def itemvar_select2(request, **kwargs):
{
'id': k,
'text': str(v),
'inactive': d,
}
for k, v, d in choices
],
@@ -716,7 +715,6 @@ def itemvars_select2(request, **kwargs):
{
'id': k,
'text': str(v),
'inactive': d,
}
for k, v, d in choices
],
+78 -78
View File
@@ -35,9 +35,10 @@
import base64
import json
import logging
import os
import time
from collections import defaultdict
from urllib.parse import quote
from urllib.parse import quote, urlparse
import webauthn
from django.conf import settings
@@ -56,7 +57,6 @@ from django.views.generic import FormView, ListView, TemplateView, UpdateView
from django_otp.plugins.otp_static.models import StaticDevice
from django_otp.plugins.otp_totp.models import TOTPDevice
from django_scopes import scopes_disabled
from webauthn.helpers import generate_challenge, generate_user_handle
from pretix.base.auth import get_auth_backends
from pretix.base.forms.auth import ReauthForm
@@ -70,9 +70,9 @@ from pretix.control.forms.users import StaffSessionForm
from pretix.control.permissions import (
AdministratorPermissionRequiredMixin, StaffMemberRequiredMixin,
)
from pretix.control.views.auth import get_u2f_appid, get_webauthn_rp_id
from pretix.control.views.auth import get_u2f_appid
from pretix.helpers.http import redirect_to_url
from pretix.helpers.u2f import websafe_encode
from pretix.helpers.webauthn import generate_challenge, generate_ukey
REAL_DEVICE_TYPES = (TOTPDevice, WebAuthnDevice, U2FDevice)
logger = logging.getLogger(__name__)
@@ -105,41 +105,25 @@ class ReauthView(TemplateView):
devices = U2FDevice.objects.filter(user=self.request.user)
for d in devices:
credential_current_sign_count = d.sign_count if isinstance(d, WebAuthnDevice) else 0
try:
webauthn_assertion_response = webauthn.verify_authentication_response(
credential=resp,
expected_challenge=base64.b64decode(challenge),
expected_rp_id=get_webauthn_rp_id(self.request),
expected_origin=settings.SITE_URL,
credential_public_key=d.webauthnpubkey,
credential_current_sign_count=credential_current_sign_count,
)
sign_count = webauthn_assertion_response.new_sign_count
if sign_count < credential_current_sign_count:
raise Exception("Possible replay attack, sign count not higher")
except Exception:
wu = d.webauthnuser
if isinstance(d, U2FDevice):
# https://www.w3.org/TR/webauthn/#sctn-appid-extension says
# "When verifying the assertion, expect that the rpIdHash MAY be the hash of the AppID instead of the RP ID."
try:
webauthn_assertion_response = webauthn.verify_authentication_response(
credential=resp,
expected_challenge=base64.b64decode(challenge),
expected_rp_id=get_u2f_appid(self.request),
expected_origin=settings.SITE_URL,
credential_public_key=d.webauthnpubkey,
credential_current_sign_count=credential_current_sign_count,
)
if webauthn_assertion_response.new_sign_count < 1:
raise Exception("Possible replay attack, sign count set")
except Exception:
logger.exception('U2F login failed')
else:
valid = True
break
else:
logger.exception('Webauthn login failed')
# RP_ID needs to be appId for U2F devices, but we can't
# set it that way in U2FDevice.webauthnuser, since that
# breaks the frontend part.
wu.rp_id = settings.SITE_URL
webauthn_assertion_response = webauthn.WebAuthnAssertionResponse(
wu,
resp,
challenge,
settings.SITE_URL,
uv_required=False # User Verification
)
sign_count = webauthn_assertion_response.verify()
except Exception:
logger.exception('U2F login failed')
else:
if isinstance(d, WebAuthnDevice):
d.sign_count = sign_count
@@ -178,24 +162,23 @@ class ReauthView(TemplateView):
ctx = super().get_context_data()
if 'webauthn_challenge' in self.request.session:
del self.request.session['webauthn_challenge']
challenge = generate_challenge()
self.request.session['webauthn_challenge'] = base64.b64encode(challenge).decode()
challenge = generate_challenge(32)
self.request.session['webauthn_challenge'] = challenge
devices = [
device.webauthndevice for device in WebAuthnDevice.objects.filter(confirmed=True, user=self.request.user)
device.webauthnuser for device in WebAuthnDevice.objects.filter(confirmed=True, user=self.request.user)
] + [
device.webauthndevice for device in U2FDevice.objects.filter(confirmed=True, user=self.request.user)
device.webauthnuser for device in U2FDevice.objects.filter(confirmed=True, user=self.request.user)
]
if devices:
auth_options = webauthn.generate_authentication_options(
rp_id=get_webauthn_rp_id(self.request),
challenge=challenge,
allow_credentials=devices,
webauthn_assertion_options = webauthn.WebAuthnAssertionOptions(
devices,
challenge
)
# Backwards compatibility to U2F
j = json.loads(webauthn.options_to_json(auth_options))
j["extensions"] = {"appid": get_u2f_appid(self.request)}
ctx['jsondata'] = json.dumps(j)
ad = webauthn_assertion_options.assertion_dict
ad['extensions'] = {
'appid': get_u2f_appid(self.request)
}
ctx['jsondata'] = json.dumps(ad)
ctx['form'] = self.form
return ctx
@@ -404,26 +387,23 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
if 'webauthn_challenge' in self.request.session:
del self.request.session['webauthn_challenge']
challenge = generate_challenge()
ukey = generate_user_handle()
challenge = generate_challenge(32)
ukey = generate_ukey()
self.request.session['webauthn_challenge'] = base64.b64encode(challenge).decode()
self.request.session['webauthn_register_ukey'] = base64.b64encode(ukey).decode()
self.request.session['webauthn_challenge'] = challenge
self.request.session['webauthn_register_ukey'] = ukey
devices = [
device.webauthndevice for device in WebAuthnDevice.objects.filter(confirmed=True, user=self.request.user)
] + [
device.webauthndevice for device in U2FDevice.objects.filter(confirmed=True, user=self.request.user)
]
make_credential_options = webauthn.generate_registration_options(
rp_id=get_webauthn_rp_id(self.request),
rp_name=get_webauthn_rp_id(self.request),
user_id=ukey,
user_name=self.request.user.email,
challenge=challenge,
exclude_credentials=devices,
make_credential_options = webauthn.WebAuthnMakeCredentialOptions(
challenge,
urlparse(settings.SITE_URL).netloc,
urlparse(settings.SITE_URL).netloc,
ukey,
self.request.user.email,
str(self.request.user),
settings.SITE_URL,
attestation="none"
)
ctx['jsondata'] = webauthn.options_to_json(make_credential_options)
ctx['jsondata'] = json.dumps(make_credential_options.registration_dict)
return ctx
@@ -432,13 +412,30 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
challenge = self.request.session['webauthn_challenge']
ukey = self.request.session['webauthn_register_ukey']
resp = json.loads(self.request.POST.get("token"))
trust_anchor_dir = os.path.normpath(os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'../../static/webauthn_trusted_attestation_roots' # currently does not exist
))
# We currently do not check attestation certificates, since there's no real risk
# and we do not have any policies specifying what devices can be used. (Also, we
# didn't get it to work.)
# Read more: https://fidoalliance.org/fido-technotes-the-truth-about-attestation/
trusted_attestation_cert_required = False
self_attestation_permitted = True
none_attestation_permitted = True
registration_verification = webauthn.verify_registration_response(
credential=resp,
expected_challenge=base64.b64decode(challenge),
expected_rp_id=get_webauthn_rp_id(self.request),
expected_origin=settings.SITE_URL,
webauthn_registration_response = webauthn.WebAuthnRegistrationResponse(
urlparse(settings.SITE_URL).netloc,
settings.SITE_URL,
resp,
challenge,
trust_anchor_dir,
trusted_attestation_cert_required,
self_attestation_permitted,
none_attestation_permitted,
uv_required=False
)
webauthn_credential = webauthn_registration_response.verify()
# Check that the credentialId is not yet registered to any other user.
# If registration is requested for a credential that is already registered
@@ -446,7 +443,7 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
# ceremony, or it MAY decide to accept the registration, e.g. while deleting
# the older registration.
credential_id_exists = WebAuthnDevice.objects.filter(
credential_id=registration_verification.credential_id
credential_id=webauthn_credential.credential_id
).first()
if credential_id_exists:
messages.error(request, _('This security device is already registered.'))
@@ -454,11 +451,14 @@ class User2FADeviceConfirmWebAuthnView(RecentAuthenticationRequiredMixin, Templa
'device': self.device.pk
}))
self.device.credential_id = websafe_encode(registration_verification.credential_id)
self.device.ukey = websafe_encode(ukey)
self.device.pub_key = websafe_encode(registration_verification.credential_public_key)
self.device.sign_count = registration_verification.sign_count
self.device.rp_id = get_webauthn_rp_id(request)
webauthn_credential.credential_id = str(webauthn_credential.credential_id, "utf-8")
webauthn_credential.public_key = str(webauthn_credential.public_key, "utf-8")
self.device.credential_id = webauthn_credential.credential_id
self.device.ukey = ukey
self.device.pub_key = webauthn_credential.public_key
self.device.sign_count = webauthn_credential.sign_count
self.device.rp_id = urlparse(settings.SITE_URL).netloc
self.device.icon_url = settings.SITE_URL
self.device.confirmed = True
self.device.save()
+2 -7
View File
@@ -32,14 +32,9 @@ def clean_filename(fname):
"Terms.pdf" "Terms.pdf.OybgvyAH.22c0583727d5bc.pdf"
This function reverses this operation (leaving names without doubled extension as-is):
This function reverses this operation:
"Terms.pdf.OybgvyAH.22c0583727d5bc.pdf" "Terms.pdf"
"Terms.pdf" "Terms.pdf"
"""
ext = '.' + fname.split('.')[-1]
parts = fname.rsplit(ext + ".", 1)
if len(parts) == 1:
return parts[0]
else:
return parts[0] + ext
return fname.rsplit(ext + ".", 1)[0] + ext
+4 -5
View File
@@ -44,12 +44,11 @@ def validate_uploaded_file_for_valid_image(f):
# have to read the data into memory.
if hasattr(f, 'temporary_file_path'):
file = f.temporary_file_path()
elif hasattr(f, 'read'):
if hasattr(f, 'seek') and callable(f.seek):
f.seek(0)
file = BytesIO(f.read())
else:
file = BytesIO(f['content'])
if hasattr(f, 'read'):
file = BytesIO(f.read())
else:
file = BytesIO(f['content'])
try:
try:
+1 -1
View File
@@ -62,7 +62,7 @@ def soft_equals(a, b):
def hard_equals(a, b):
"""Implements the '===' operator."""
if type(a) is not type(b):
if type(a) != type(b):
return False
return a == b
+5 -11
View File
@@ -22,9 +22,9 @@
import logging
from django import template
from django.core.files import File
from django.core.files.storage import default_storage
from pretix import settings
from pretix.helpers.thumb import get_thumbnail
register = template.Library()
@@ -33,16 +33,10 @@ logger = logging.getLogger(__name__)
@register.filter
def thumb(source, arg):
if isinstance(source, File):
source = source.name
try:
formats = list(set().union(
settings.PILLOW_FORMATS_IMAGE,
settings.PILLOW_FORMATS_QUESTIONS_FAVICON,
settings.PILLOW_FORMATS_QUESTIONS_IMAGE
))
return get_thumbnail(source, arg, formats=formats).thumb.url
return get_thumbnail(source, arg).thumb.url
except:
logger.exception(f'Failed to create thumbnail of {source}')
# HACK: source.url works for some types of files (e.g. FieldFile), and for all files retrieved from Hierarkey,
# default_storage.url works for all files in NanoCDNStorage. For others, this may return an invalid URL.
# But for a fallback, this can probably be accepted.
return source.url if hasattr(source, 'url') else default_storage.url(str(source))
return default_storage.url(source)
+9 -19
View File
@@ -21,7 +21,6 @@
#
import hashlib
import math
import os
from io import BytesIO
from django.conf import settings
@@ -165,16 +164,9 @@ def resize_image(image, size):
return image
def create_thumbnail(source, size, formats=None):
source_name = str(source)
# HACK: this ensures that the file is opened in binary mode, which is not guaranteed otherwise, esp. for
# files retrieved from hierarkey. For Django Files in FileSystemStorage, where source.name is the absolute
# filesystem path, this only works because _open() uses safe_join, which accepts absolute paths if they match the
# expected base dir. For NanoCDN Files, this works because source.name is set to the storage path.
source_rb = default_storage.open(source_name, mode='rb')
image = Image.open(BytesIO(source_rb.read()), formats=formats or settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
def create_thumbnail(sourcename, size, formats=None):
source = default_storage.open(sourcename)
image = Image.open(BytesIO(source.read()), formats=formats or settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
try:
image.load()
except:
@@ -183,14 +175,13 @@ def create_thumbnail(source, size, formats=None):
frames = [resize_image(frame, size) for frame in ImageSequence.Iterator(image)]
image_out = frames[0]
save_kwargs = {}
source_ext = os.path.splitext(source_name)[1].lower()
if source_ext == '.jpg' or source_ext == '.jpeg':
if source.name.lower().endswith('.jpg') or source.name.lower().endswith('.jpeg'):
# Yields better file sizes for photos
target_ext = 'jpeg'
quality = 95
elif source_ext == '.gif' or source_ext == '.png':
target_ext = source_name.lower()[-3:]
elif source.name.lower().endswith('.gif') or source.name.lower().endswith('.png'):
target_ext = source.name.lower()[-3:]
quality = None
image_out.info = image.info
save_kwargs = {
@@ -205,14 +196,14 @@ def create_thumbnail(source, size, formats=None):
checksum = hashlib.md5(image.tobytes()).hexdigest()
name = checksum + '.' + size.replace('^', 'c') + '.' + target_ext
buffer = BytesIO()
if image_out.mode == "P" and source_ext == '.png':
if image_out.mode == "P" and source.name.lower().endswith('.png'):
image_out = image_out.convert('RGBA')
if image_out.mode not in ("1", "L", "RGB", "RGBA"):
image_out = image_out.convert('RGB')
image_out.save(fp=buffer, format=target_ext.upper(), quality=quality, **save_kwargs)
imgfile = ContentFile(buffer.getvalue())
t = Thumbnail.objects.create(source=source_name, size=size)
t = Thumbnail.objects.create(source=sourcename, size=size)
t.thumb.save(name, imgfile)
return t
@@ -220,7 +211,6 @@ def create_thumbnail(source, size, formats=None):
def get_thumbnail(source, size, formats=None):
# Assumes files are immutable
try:
source_name = str(source)
return Thumbnail.objects.get(source=source_name, size=size)
return Thumbnail.objects.get(source=source, size=size)
except Thumbnail.DoesNotExist:
return create_thumbnail(source, size, formats=formats)
+45
View File
@@ -0,0 +1,45 @@
#
# 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 random
import string
def generate_challenge(challenge_len):
return ''.join([
random.SystemRandom().choice(string.ascii_letters + string.digits)
for i in range(challenge_len)
])
def generate_ukey():
"""
Its value's id member is required, and contains an identifier
for the account, specified by the Relying Party. This is not meant
to be displayed to the user, but is used by the Relying Party to
control the number of credentials - an authenticator will never
contain more than one credential for a given Relying Party under
the same id.
A unique identifier for the entity. For a relying party entity,
sets the RP ID. For a user account entity, this will be an
arbitrary string specified by the relying party.
"""
return generate_challenge(20)
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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"
@@ -609,23 +609,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -663,32 +663,32 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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/"
@@ -639,23 +639,23 @@ msgstr "لا شيء"
msgid "Selected only"
msgstr "المختارة فقط"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "قم باستخدم اسم مختلف داخليا"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "اضغط لاغلاق الصفحة"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "لم تقم بحفظ التعديلات!"
@@ -707,32 +707,32 @@ msgstr[3] "سيتم حجز العناصر لك في سلة التسوق لعدة
msgstr[4] "سيتم حجز العناصر لك في سلة التسوق لدقائق {num}."
msgstr[5] "سيتم حجز العناصر لك في سلة التسوق لمدة {num}."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "يحصل المنظم على %(currency) %(amount)"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "ستسترد %(currency)%(amount)"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr "الرجاء إدخال المبلغ الذي يمكن للمنظم الاحتفاظ به."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "الرجاء إدخال عدد لأحد أنواع التذاكر."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "مطلوب"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "المنطقة الزمنية:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "التوقيت المحلي:"
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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-"
@@ -610,23 +610,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -668,34 +668,34 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "El contingut de la cistella ja no el teniu reservat."
msgstr[1] "El contingut de la cistella ja no el teniu reservat."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Cistella expirada"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2023-09-15 06:00+0000\n"
"Last-Translator: Michael <michael.happl@gmx.at>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -633,23 +633,23 @@ msgstr "Žádný"
msgid "Selected only"
msgstr "Pouze vybrané"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Interně používat jiný název"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Kliknutím zavřete"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Máte neuložené změny!"
@@ -694,32 +694,32 @@ msgstr[1] ""
msgstr[2] ""
"Produkty v nákupním košíku jsou pro vás rezervovány na dalších {num} minut."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "Organizátor si ponechává %(currency)s %(amount)s"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "Dostanete %(currency)s %(amount)s zpět"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr "Zadejte částku, kterou si organizátor může ponechat."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Zadejte prosím množství pro jeden z typů vstupenek."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "povinný"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Časové pásmo:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "Místní čas:"
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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"
@@ -610,23 +610,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -664,32 +664,32 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2022-12-01 17:00+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -653,23 +653,23 @@ msgstr "Ingen"
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Klik for at lukke"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Du har ændringer, der ikke er gemt!"
@@ -715,40 +715,40 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Varerne i din kurv er reserveret for dig i et minut."
msgstr[1] "Varerne i din kurv er reserveret for dig i {num} minutter."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
#, fuzzy
#| msgctxt "widget"
#| msgid "from %(currency)s %(price)s"
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "fra %(currency)s %(price)s"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
#, fuzzy
#| msgctxt "widget"
#| msgid "from %(currency)s %(price)s"
msgid "You get %(currency)s %(amount)s back"
msgstr "fra %(currency)s %(price)s"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Kurv udløbet"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Tidszone:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "Din lokaltid:"
File diff suppressed because it is too large Load Diff
+23 -19
View File
@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"PO-Revision-Date: 2024-03-15 18:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2024-02-10 00:00+0000\n"
"Last-Translator: Phin Wolkwitz <wolkwitz@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
"de/>\n"
"Language: de\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.4.2\n"
"X-Generator: Weblate 5.3.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -474,12 +474,16 @@ msgid "Number of days with a previous entry"
msgstr "Anzahl an Tagen mit vorherigem Eintritt"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:133
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry since"
msgstr "Anzahl an Tagen mit vorherigem Eintritt seit"
msgstr "Anzahl an Tagen mit vorherigem Eintritt"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:137
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry before"
msgstr "Anzahl an Tagen mit vorherigem Eintritt vor"
msgstr "Anzahl an Tagen mit vorherigem Eintritt"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:141
msgid "Minutes since last entry (-1 on first entry)"
@@ -631,23 +635,23 @@ msgstr "Keine"
msgid "Selected only"
msgstr "Nur ausgewählte"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
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:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr "Ungültige Seitenzahl."
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Intern einen anderen Namen verwenden"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Klicken zum Schließen"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Sie haben ungespeicherte Änderungen!"
@@ -690,32 +694,32 @@ msgstr[0] ""
msgstr[1] ""
"Die Produkte in Ihrem Warenkorb sind noch {num} Minuten für Sie reserviert."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "Der Veranstalter behält %(currency)s %(amount)s ein"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "Sie erhalten %(currency)s %(amount)s zurück"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr "Bitte geben Sie den Betrag ein, den der Veranstalter einbehalten darf."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Bitte tragen Sie eine Menge für eines der Produkte ein."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "verpflichtend"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Zeitzone:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "Deine lokale Zeit:"
File diff suppressed because it is too large Load Diff
@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"PO-Revision-Date: 2024-03-15 18:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2024-02-10 00:00+0000\n"
"Last-Translator: Phin Wolkwitz <wolkwitz@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix-js/de_Informal/>\n"
"Language: de_Informal\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.4.2\n"
"X-Generator: Weblate 5.3.1\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -473,12 +473,16 @@ msgid "Number of days with a previous entry"
msgstr "Anzahl an Tagen mit vorherigem Eintritt"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:133
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry since"
msgstr "Anzahl an Tagen mit vorherigem Eintritt seit"
msgstr "Anzahl an Tagen mit vorherigem Eintritt"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:137
#, fuzzy
#| msgid "Number of days with a previous entry"
msgid "Number of days with a previous entry before"
msgstr "Anzahl an Tagen mit vorherigem Eintritt vor"
msgstr "Anzahl an Tagen mit vorherigem Eintritt"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:141
msgid "Minutes since last entry (-1 on first entry)"
@@ -630,23 +634,23 @@ msgstr "Keine"
msgid "Selected only"
msgstr "Nur ausgewählte"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
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:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr "Ungültige Seitenzahl."
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Intern einen anderen Namen verwenden"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Klicken zum Schließen"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Du hast ungespeicherte Änderungen!"
@@ -689,32 +693,32 @@ msgstr[0] ""
msgstr[1] ""
"Die Produkte in deinem Warenkorb sind noch {num} Minuten für dich reserviert."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "Der Veranstalter behält %(currency)s %(amount)s ein"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "Du erhältst %(currency)s %(amount)s zurück"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr "Bitte gib den Betrag ein, den der Veranstalter einbehalten darf."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Bitte trage eine Menge für eines der Produkte ein."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "verpflichtend"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Zeitzone:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "Deine lokale Zeit:"
+1648 -1725
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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"
@@ -609,23 +609,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -663,32 +663,32 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -669,23 +669,23 @@ msgstr "Κανένας"
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Χρησιμοποιήστε διαφορετικό όνομα εσωτερικά"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Κάντε κλικ για να κλείσετε"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -729,40 +729,40 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Τα είδη στο καλάθι θα παραμείνουν δεσμευμένα για ένα λεπτό."
msgstr[1] "Τα είδη στο καλάθι θα παραμείνουν δεσμευμένα για {num} λεπτά."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
#, fuzzy
#| msgctxt "widget"
#| msgid "from %(currency)s %(price)s"
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "απο %(currency)s %(price)s"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
#, fuzzy
#| msgctxt "widget"
#| msgid "from %(currency)s %(price)s"
msgid "You get %(currency)s %(amount)s back"
msgstr "απο %(currency)s %(price)s"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Εισαγάγετε μια ποσότητα για έναν από τους τύπους εισιτηρίων."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Το καλάθι έληξε"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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"
@@ -609,23 +609,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -663,32 +663,32 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2023-11-07 14:00+0000\n"
"Last-Translator: Zona Vip <contacto@zonavip.mx>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -639,23 +639,23 @@ msgstr "Ninguno"
msgid "Selected only"
msgstr "Solamente seleccionados"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Usar un nombre diferente internamente"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Click para cerrar"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "¡Tienes cambios sin guardar!"
@@ -697,32 +697,32 @@ msgstr[0] ""
msgstr[1] ""
"Los elementos en su carrito de compras se han reservado por {num} minutos."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "El organizador se queda %(currency)s %(price)s"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "Obtienes %(currency)s %(price)s de vuelta"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr "Por favor, ingrese el monto que el organizador puede quedarse."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Por favor, introduzca un valor para cada tipo de entrada."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "campo requerido"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Zona horaria:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "Su hora local:"
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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-"
@@ -634,23 +634,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Käytä toista nimeä sisäisesti"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Sulje klikkaamalla"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Sinulla on tallentamattomia muutoksia!"
@@ -692,34 +692,34 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Ostoskorissasi olevat tuotteet eivät ole enää varattu sinulle."
msgstr[1] "Ostoskorissasi olevat tuotteet eivät ole enää varattu sinulle."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Ostoskori on vanhentunut"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Aikavyöhyke:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2023-09-11 10:00+0000\n"
"Last-Translator: Ronan LE MEILLAT <ronan.le_meillat@highcanfly.club>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -638,23 +638,23 @@ msgstr "Aucun"
msgid "Selected only"
msgstr "Seuls les sélectionnés"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Utiliser un nom différent en interne"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Cliquez pour fermer"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Vous avez des modifications non sauvegardées !"
@@ -694,33 +694,33 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Les articles de votre panier sont réservés pour une minute."
msgstr[1] "Les articles de votre panier sont réservés pendant {num} minutes."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "L'organisateur retient %(currency)s %(amount)s"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "Vous recevez en retour %(currency)s %(amount)s"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
"Veuillez indiquer le montant que l'organisateur est autorisé à retenir."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "SVP entrez une quantité pour un de vos types de billets."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "obligatoire"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Fuseau horaire :"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "Votre heure locale:"
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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-"
@@ -641,23 +641,23 @@ msgstr "Ningún"
msgid "Selected only"
msgstr "Soamente seleccionados"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Usar un nome diferente internamente"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Click para cerrar"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Tes cambios sen gardar!"
@@ -698,32 +698,32 @@ msgstr[0] "Os artigos da túa cesta están reservados para ti durante un minuto.
msgstr[1] ""
"Os artigos da túa cesta están reservados para ti durante {num} minutos."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "O organizador queda %(currency)s %(price)s"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "Obtés %(currency)s %(price)s de volta"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr "Por favor, ingrese a cantidade que pode conservar o organizador."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Por favor, introduza un valor para cada tipo de entrada."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "campo requirido"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Zona horaria:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "A súa hora local:"
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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/"
@@ -617,23 +617,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -675,32 +675,32 @@ msgstr[1] ""
msgstr[2] ""
msgstr[3] ""
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2024-01-31 04:00+0000\n"
"Last-Translator: Pavle Ergović <pavleergovic@gmail.com>\n"
"Language-Team: Croatian <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -611,23 +611,23 @@ msgstr ""
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr ""
@@ -665,32 +665,32 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+0000\n"
"PO-Revision-Date: 2020-01-24 08:00+0000\n"
"Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n"
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -657,23 +657,23 @@ msgstr "Semmi"
msgid "Selected only"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Használj másik nevet"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Bezárásért kattints"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Mentetlen változtatások!"
@@ -717,40 +717,40 @@ msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "A kosár tartalma egy percig foglalva van számodra."
msgstr[1] "A kosár tartalma {num} percig foglalva van számodra."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
#, fuzzy
#| msgctxt "widget"
#| msgid "from %(currency)s %(price)s"
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "%(currency) %(price)-tól"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
#, fuzzy
#| msgctxt "widget"
#| msgid "from %(currency)s %(price)s"
msgid "You get %(currency)s %(amount)s back"
msgstr "%(currency) %(price)-tól"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Adjon meg egy mennyiséget az egyik jegytípusból."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "A kosár lejárt"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr ""
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-26 13:37+0000\n"
"POT-Creation-Date: 2024-02-09 14:51+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/"
@@ -640,23 +640,23 @@ msgstr "Tidak ada"
msgid "Selected only"
msgstr "Hanya dipilih"
#: pretix/static/pretixcontrol/js/ui/main.js:792
#: pretix/static/pretixcontrol/js/ui/main.js:784
msgid "Enter page number between 1 and %(max)s."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:795
#: pretix/static/pretixcontrol/js/ui/main.js:787
msgid "Invalid page number."
msgstr ""
#: pretix/static/pretixcontrol/js/ui/main.js:953
#: pretix/static/pretixcontrol/js/ui/main.js:945
msgid "Use a different name internally"
msgstr "Gunakan nama yang berbeda secara internal"
#: pretix/static/pretixcontrol/js/ui/main.js:993
#: pretix/static/pretixcontrol/js/ui/main.js:985
msgid "Click to close"
msgstr "Klik untuk menutup"
#: pretix/static/pretixcontrol/js/ui/main.js:1068
#: pretix/static/pretixcontrol/js/ui/main.js:1060
msgid "You have unsaved changes!"
msgstr "Anda memiliki perubahan yang belum disimpan!"
@@ -694,32 +694,32 @@ msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Item di keranjang Anda direservasi untuk Anda selama {num} menit."
#: pretix/static/pretixpresale/js/ui/main.js:201
#: pretix/static/pretixpresale/js/ui/main.js:171
msgid "The organizer keeps %(currency)s %(amount)s"
msgstr "Penyelenggara menyimpan %(currency)s %(amount)s"
#: pretix/static/pretixpresale/js/ui/main.js:209
#: pretix/static/pretixpresale/js/ui/main.js:179
msgid "You get %(currency)s %(amount)s back"
msgstr "Anda mendapatkan %(currency)s %(amount)s kembali"
#: pretix/static/pretixpresale/js/ui/main.js:225
#: pretix/static/pretixpresale/js/ui/main.js:195
msgid "Please enter the amount the organizer can keep."
msgstr "Silakan masukkan jumlah yang dapat disimpan oleh penyelenggara."
#: pretix/static/pretixpresale/js/ui/main.js:442
#: pretix/static/pretixpresale/js/ui/main.js:412
msgid "Please enter a quantity for one of the ticket types."
msgstr "Masukkan jumlah untuk salah satu jenis tiket."
#: pretix/static/pretixpresale/js/ui/main.js:478
#: pretix/static/pretixpresale/js/ui/main.js:448
msgid "required"
msgstr "diperlukan"
#: pretix/static/pretixpresale/js/ui/main.js:581
#: pretix/static/pretixpresale/js/ui/main.js:600
#: pretix/static/pretixpresale/js/ui/main.js:551
#: pretix/static/pretixpresale/js/ui/main.js:570
msgid "Time zone:"
msgstr "Zona waktu:"
#: pretix/static/pretixpresale/js/ui/main.js:591
#: pretix/static/pretixpresale/js/ui/main.js:561
msgid "Your local time:"
msgstr "Waktu setempat Anda:"

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