mirror of
https://github.com/pretix/pretix.git
synced 2026-06-18 02:26:17 +00:00
Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1011ef7cc9 | |||
| 8829050eb9 | |||
| 5027f6dd59 | |||
| 787db18d72 | |||
| aadce7be00 | |||
| 26f296bc11 | |||
| 6ae80cdd4b | |||
| cb3956c994 | |||
| b9f350bf3a | |||
| ab447bb85f | |||
| bf33a42ae8 | |||
| 081f975ff9 | |||
| eab7d81a51 | |||
| b2dce51a24 | |||
| 5bd660a913 | |||
| 8e9cdd7548 | |||
| d6592cbb93 | |||
| 0e3ccae5d4 | |||
| 034b46d218 | |||
| a3f120198d | |||
| fa5f3bb15a | |||
| 5120b312b6 | |||
| 09064844b2 | |||
| 1a60b3a712 | |||
| 6216f0d7df | |||
| 380b55e699 | |||
| 6e67bb5045 | |||
| 1463ee9227 | |||
| 3b49e77722 | |||
| ceed07af94 | |||
| 802c03f8f3 | |||
| 9962d8a3be | |||
| 028a41f3e4 | |||
| 6d8a9854f9 | |||
| 861e14bb16 | |||
| 7a080c0820 | |||
| 2dbdb91066 | |||
| b8efb8f61d | |||
| 5f0cc4cc59 | |||
| d3bb1f3190 | |||
| 69a215feff | |||
| 435dd5ebaf | |||
| 015d74f7ae | |||
| 5c9a069d77 | |||
| 5866cf94ee | |||
| fa15ba4435 | |||
| e982f04d59 | |||
| ced00266dc | |||
| b534c125db | |||
| 769e1312d4 | |||
| 3d53c03906 | |||
| 59d1d2cb16 | |||
| 7e45837295 | |||
| fd9ed15065 | |||
| 2df3d9206b | |||
| fbd8bbbeaa | |||
| 1c305e4b30 | |||
| ea114b4f64 | |||
| 0342613635 | |||
| 743c4b796b | |||
| 8a7f54795e | |||
| cb464ad597 | |||
| 119cc50897 | |||
| 61f9cf13b4 | |||
| f24429a7c5 | |||
| 29ed07ccce | |||
| dd0cd7ab0b | |||
| d7df906995 | |||
| 839f4b4657 | |||
| 74f7e1f61c | |||
| 47919afab0 | |||
| 819daa99f7 | |||
| 8512e79d68 | |||
| 52672ae25b | |||
| ad752dc617 | |||
| 43c6c33bd8 | |||
| 88c9f8c047 | |||
| 2d2663f15f | |||
| ae6014708b | |||
| d1686df07c | |||
| 4d60d7bfbc | |||
| c0b93fedc5 | |||
| 2eaa6c3069 | |||
| db982c9ef4 | |||
| f9f6ee94ae | |||
| 99c257d392 | |||
| e2cb83ce28 | |||
| d7b7d3cc5f | |||
| 721ac8a500 | |||
| 5796cfe03f | |||
| 63f1c4f793 | |||
| 47f409171d | |||
| 27fcdff17f | |||
| a38a96f186 | |||
| 700ea77e39 | |||
| 06104ff483 | |||
| fb5697a27b | |||
| 9a9ad6d6d1 | |||
| a05845790e | |||
| a0830dd033 | |||
| dba2529f6b | |||
| 9c0ea8f179 | |||
| 1f0501a647 | |||
| d2e6446238 | |||
| d519fcfe0d | |||
| c7226303be | |||
| 9406e941bc | |||
| 919e598f8a | |||
| 672692d578 | |||
| 9429dc7e91 | |||
| 5c8dbd99dd | |||
| cfbb8310f0 | |||
| d37d9a861c | |||
| 43a9cf29b2 | |||
| 047ad438a7 | |||
| ec8d921fcf | |||
| 39e6ef4365 | |||
| 4d8b032591 | |||
| e8193e408b | |||
| 6723d8c07c | |||
| c30134f36c | |||
| 0617fc04ec | |||
| e90b54280a | |||
| 39ea2889ba | |||
| 76230bd37b | |||
| 86b8c5e90f | |||
| 7a2027c61b | |||
| 55de5ef45b | |||
| 39e6954828 | |||
| 7849d98672 | |||
| c325164059 | |||
| 8fc19c62dd | |||
| 20feaebbbd | |||
| 4587a9e630 | |||
| 9fe0e6eb67 | |||
| bab7e54f35 | |||
| 352efa40e7 | |||
| 50da7d4261 | |||
| 53cc59d41d | |||
| 9879e99c59 | |||
| dc49d5bcf7 | |||
| d4460045b4 |
@@ -60,6 +60,9 @@ The following values for ``action_types`` are valid with pretix core:
|
||||
* ``pretix.event.added``
|
||||
* ``pretix.event.changed``
|
||||
* ``pretix.event.deleted``
|
||||
* ``pretix.giftcards.created``
|
||||
* ``pretix.giftcards.modified``
|
||||
* ``pretix.giftcards.transaction.*``
|
||||
* ``pretix.voucher.added``
|
||||
* ``pretix.voucher.changed``
|
||||
* ``pretix.voucher.deleted``
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
sphinx==9.1.*
|
||||
sphinx-rtd-theme~=3.1.0
|
||||
sphinxcontrib-httpdomain~=1.8.1
|
||||
sphinxcontrib-httpdomain~=2.0.0
|
||||
sphinxcontrib-images~=1.0.1
|
||||
sphinxcontrib-jquery~=4.1
|
||||
sphinxcontrib-spelling~=8.0.2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-e ../
|
||||
sphinx==9.1.*
|
||||
sphinx-rtd-theme~=3.1.0
|
||||
sphinxcontrib-httpdomain~=1.8.1
|
||||
sphinxcontrib-httpdomain~=2.0.0
|
||||
sphinxcontrib-images~=1.0.1
|
||||
sphinxcontrib-jquery~=4.1
|
||||
sphinxcontrib-spelling~=8.0.2
|
||||
|
||||
+6
-6
@@ -33,7 +33,7 @@ dependencies = [
|
||||
"celery==5.6.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=44.0.0",
|
||||
"css-inline==0.19.*",
|
||||
"css-inline==0.20.*",
|
||||
"defusedcsv>=1.1.0",
|
||||
"dnspython==2.*",
|
||||
"Django[argon2]==4.2.*,>=4.2.26",
|
||||
@@ -65,7 +65,7 @@ dependencies = [
|
||||
"kombu==5.6.*",
|
||||
"libsass==0.23.*",
|
||||
"lxml",
|
||||
"markdown==3.10.1", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||
"markdown==3.10.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||
"mt-940==4.30.*",
|
||||
"oauthlib==3.3.*",
|
||||
@@ -73,7 +73,7 @@ dependencies = [
|
||||
"packaging",
|
||||
"paypalrestsdk==1.13.*",
|
||||
"paypal-checkout-serversdk==1.0.*",
|
||||
"PyJWT==2.10.*",
|
||||
"PyJWT==2.11.*",
|
||||
"phonenumberslite==9.0.*",
|
||||
"Pillow==12.1.*",
|
||||
"pretix-plugin-build",
|
||||
@@ -92,7 +92,7 @@ dependencies = [
|
||||
"redis==7.1.*",
|
||||
"reportlab==4.4.*",
|
||||
"requests==2.32.*",
|
||||
"sentry-sdk==2.51.*",
|
||||
"sentry-sdk==2.53.*",
|
||||
"sepaxml==2.7.*",
|
||||
"stripe==7.9.*",
|
||||
"text-unidecode==1.*",
|
||||
@@ -110,10 +110,10 @@ dev = [
|
||||
"aiohttp==3.13.*",
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"fakeredis==2.33.*",
|
||||
"fakeredis==2.34.*",
|
||||
"flake8==7.3.*",
|
||||
"freezegun",
|
||||
"isort==7.0.*",
|
||||
"isort==8.0.*",
|
||||
"pep8-naming==0.15.*",
|
||||
"potypo",
|
||||
"pytest-asyncio>=0.24",
|
||||
|
||||
@@ -19,4 +19,4 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2026.2.0.dev0"
|
||||
__version__ = "2026.3.0.dev0"
|
||||
|
||||
@@ -188,11 +188,15 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
clist = self.get_object()
|
||||
if serializer.validated_data.get('nonce'):
|
||||
if kwargs.get('position'):
|
||||
prev = kwargs['position'].all_checkins.filter(nonce=serializer.validated_data['nonce']).first()
|
||||
prev = kwargs['position'].all_checkins.filter(
|
||||
nonce=serializer.validated_data['nonce'],
|
||||
successful=False
|
||||
).first()
|
||||
else:
|
||||
prev = clist.checkins.filter(
|
||||
nonce=serializer.validated_data['nonce'],
|
||||
raw_barcode=serializer.validated_data['raw_barcode'],
|
||||
successful=False
|
||||
).first()
|
||||
if prev:
|
||||
# Ignore because nonce is already handled
|
||||
|
||||
@@ -249,12 +249,24 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
||||
def perform_create(self, serializer):
|
||||
value = serializer.validated_data.pop('value')
|
||||
inst = serializer.save(issuer=self.request.organizer)
|
||||
inst.transactions.create(value=value, acceptor=self.request.organizer)
|
||||
inst.log_action(
|
||||
'pretix.giftcards.transaction.manual',
|
||||
action='pretix.giftcards.created',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=merge_dicts(self.request.data, {'id': inst.pk})
|
||||
)
|
||||
inst.transactions.create(value=value, acceptor=self.request.organizer)
|
||||
inst.log_action(
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=merge_dicts(
|
||||
self.request.data,
|
||||
{
|
||||
'id': inst.pk,
|
||||
'acceptor_id': self.request.organizer.id,
|
||||
'acceptor_slug': self.request.organizer.slug
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
@@ -269,7 +281,7 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
||||
inst = serializer.save(secret=serializer.instance.secret, currency=serializer.instance.currency,
|
||||
testmode=serializer.instance.testmode)
|
||||
inst.log_action(
|
||||
'pretix.giftcards.modified',
|
||||
action='pretix.giftcards.modified',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data=self.request.data,
|
||||
@@ -282,10 +294,14 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
||||
diff = value - old_value
|
||||
inst.transactions.create(value=diff, acceptor=self.request.organizer)
|
||||
inst.log_action(
|
||||
'pretix.giftcards.transaction.manual',
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'value': diff}
|
||||
data={
|
||||
'value': diff,
|
||||
'acceptor_id': self.request.organizer.id,
|
||||
'acceptor_slug': self.request.organizer.slug
|
||||
}
|
||||
)
|
||||
|
||||
return inst
|
||||
@@ -309,10 +325,15 @@ class GiftCardViewSet(viewsets.ModelViewSet):
|
||||
}, status=status.HTTP_409_CONFLICT)
|
||||
gc.transactions.create(value=value, text=text, info=info, acceptor=self.request.organizer)
|
||||
gc.log_action(
|
||||
'pretix.giftcards.transaction.manual',
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={'value': value, 'text': text}
|
||||
data={
|
||||
'value': value,
|
||||
'text': text,
|
||||
'acceptor_id': self.request.organizer.id,
|
||||
'acceptor_slug': self.request.organizer.slug
|
||||
}
|
||||
)
|
||||
return Response(GiftCardSerializer(gc, context=self.get_serializer_context()).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -174,6 +174,38 @@ class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedGiftcardWebhookEvent(ParametrizedWebhookEvent):
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
giftcard = logentry.content_object
|
||||
if not giftcard:
|
||||
return None
|
||||
|
||||
return {
|
||||
'notification_id': logentry.pk,
|
||||
'issuer_id': logentry.organizer_id,
|
||||
'issuer_slug': logentry.organizer.slug,
|
||||
'giftcard': giftcard.pk,
|
||||
'action': logentry.action_type,
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedGiftcardTransactionWebhookEvent(ParametrizedWebhookEvent):
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
giftcard = logentry.content_object
|
||||
if not giftcard:
|
||||
return None
|
||||
|
||||
return {
|
||||
'notification_id': logentry.pk,
|
||||
'issuer_id': logentry.organizer_id,
|
||||
'issuer_slug': logentry.organizer.slug,
|
||||
'acceptor_id': logentry.parsed_data.get('acceptor_id'),
|
||||
'acceptor_slug': logentry.parsed_data.get('acceptor_slug'),
|
||||
'giftcard': giftcard.pk,
|
||||
'action': logentry.action_type,
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedVoucherWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
@@ -433,6 +465,18 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
'pretix.customer.anonymized',
|
||||
_('Customer account anonymized'),
|
||||
),
|
||||
ParametrizedGiftcardWebhookEvent(
|
||||
'pretix.giftcards.created',
|
||||
_('Gift card added'),
|
||||
),
|
||||
ParametrizedGiftcardWebhookEvent(
|
||||
'pretix.giftcards.modified',
|
||||
_('Gift card modified'),
|
||||
),
|
||||
ParametrizedGiftcardTransactionWebhookEvent(
|
||||
'pretix.giftcards.transaction.*',
|
||||
_('Gift card used in transaction'),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -216,7 +216,10 @@ class OutboundSyncProvider:
|
||||
|
||||
try:
|
||||
mapped_objects = self.sync_order(sq.order)
|
||||
if not all(all(not res or res.sync_info.get("action", "") == "nothing_to_do" for res in res_list) for res_list in mapped_objects.values()):
|
||||
actions_taken = [res and res.sync_info.get("action", "") for res_list in mapped_objects.values() for res in res_list]
|
||||
should_write_logentry = any(action not in (None, "nothing_to_do") for action in actions_taken)
|
||||
logger.info('Synced order %s to %s, actions: %r, log: %r', sq.order.code, sq.sync_provider, actions_taken, should_write_logentry)
|
||||
if should_write_logentry:
|
||||
sq.order.log_action("pretix.event.order.data_sync.success", {
|
||||
"provider": self.identifier,
|
||||
"objects": {
|
||||
@@ -237,7 +240,7 @@ class OutboundSyncProvider:
|
||||
sq.set_sync_error("exceeded", e.messages, e.full_message)
|
||||
else:
|
||||
logger.info(
|
||||
f"Could not sync order {sq.order.code} to {type(self).__name__} "
|
||||
f"Could not sync order {sq.order.code} to {sq.sync_provider} "
|
||||
f"(transient error, attempt #{sq.failed_attempts}, next {sq.not_before})",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ from pretix.base.templatetags.rich_text import (
|
||||
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
|
||||
markdown_compile_email, truelink_callback,
|
||||
)
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.format import FormattedString, SafeFormatter, format_map
|
||||
|
||||
from pretix.base.services.placeholders import ( # noqa
|
||||
get_available_placeholders, PlaceholderContext
|
||||
@@ -141,6 +141,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
||||
return markdown_compile_email(plaintext, context=context)
|
||||
|
||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
|
||||
apply_format_map = not isinstance(plain_body, FormattedString)
|
||||
body_md = self.compile_markdown(plain_body, context)
|
||||
if context:
|
||||
linker = bleach.Linker(
|
||||
@@ -149,12 +150,13 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
||||
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
|
||||
parse_email=True
|
||||
)
|
||||
body_md = format_map(
|
||||
body_md,
|
||||
context=context,
|
||||
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
||||
linkifier=linker
|
||||
)
|
||||
if apply_format_map:
|
||||
body_md = format_map(
|
||||
body_md,
|
||||
context=context,
|
||||
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
||||
linkifier=linker
|
||||
)
|
||||
htmlctx = {
|
||||
'site': settings.PRETIX_INSTANCE_NAME,
|
||||
'site_url': settings.SITE_URL,
|
||||
|
||||
@@ -651,6 +651,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
pgettext('address', 'State'),
|
||||
_('Voucher'),
|
||||
_('Voucher budget usage'),
|
||||
_('Voucher tag'),
|
||||
_('Pseudonymization ID'),
|
||||
_('Ticket secret'),
|
||||
_('Seat ID'),
|
||||
@@ -769,6 +770,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
op.state_for_address or '',
|
||||
op.voucher.code if op.voucher else '',
|
||||
op.voucher_budget_use if op.voucher_budget_use else '',
|
||||
op.voucher.tag if op.voucher else '',
|
||||
op.pseudonymization_id,
|
||||
op.secret,
|
||||
]
|
||||
|
||||
@@ -890,18 +890,18 @@ class BaseQuestionsForm(forms.Form):
|
||||
if not help_text:
|
||||
if q.valid_date_min and q.valid_date_max:
|
||||
help_text = format_lazy(
|
||||
'Please enter a date between {min} and {max}.',
|
||||
_('Please enter a date between {min} and {max}.'),
|
||||
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
|
||||
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
|
||||
)
|
||||
elif q.valid_date_min:
|
||||
help_text = format_lazy(
|
||||
'Please enter a date no earlier than {min}.',
|
||||
_('Please enter a date no earlier than {min}.'),
|
||||
min=date_format(q.valid_date_min, "SHORT_DATE_FORMAT"),
|
||||
)
|
||||
elif q.valid_date_max:
|
||||
help_text = format_lazy(
|
||||
'Please enter a date no later than {max}.',
|
||||
_('Please enter a date no later than {max}.'),
|
||||
max=date_format(q.valid_date_max, "SHORT_DATE_FORMAT"),
|
||||
)
|
||||
if initial and initial.answer:
|
||||
@@ -939,18 +939,18 @@ class BaseQuestionsForm(forms.Form):
|
||||
if not help_text:
|
||||
if q.valid_datetime_min and q.valid_datetime_max:
|
||||
help_text = format_lazy(
|
||||
'Please enter a date and time between {min} and {max}.',
|
||||
_('Please enter a date and time between {min} and {max}.'),
|
||||
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
|
||||
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
|
||||
)
|
||||
elif q.valid_datetime_min:
|
||||
help_text = format_lazy(
|
||||
'Please enter a date and time no earlier than {min}.',
|
||||
_('Please enter a date and time no earlier than {min}.'),
|
||||
min=date_format(q.valid_datetime_min, "SHORT_DATETIME_FORMAT"),
|
||||
)
|
||||
elif q.valid_datetime_max:
|
||||
help_text = format_lazy(
|
||||
'Please enter a date and time no later than {max}.',
|
||||
_('Please enter a date and time no later than {max}.'),
|
||||
max=date_format(q.valid_datetime_max, "SHORT_DATETIME_FORMAT"),
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#
|
||||
from typing import Optional
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_countries.fields import Country
|
||||
|
||||
from pretix.base.models import Invoice, InvoiceAddress
|
||||
@@ -106,6 +107,22 @@ class TransmissionType:
|
||||
def transmission_info_to_form_data(self, transmission_info: dict) -> dict:
|
||||
return transmission_info
|
||||
|
||||
def describe_info(self, transmission_info: dict, country: Country, is_business: bool):
|
||||
form_data = self.transmission_info_to_form_data(transmission_info)
|
||||
data = []
|
||||
visible_field_keys = self.invoice_address_form_fields_visible(country, is_business)
|
||||
for k, f in self.invoice_address_form_fields.items():
|
||||
if k not in visible_field_keys:
|
||||
continue
|
||||
v = form_data.get(k)
|
||||
if v is True:
|
||||
v = _("Yes")
|
||||
elif v is False:
|
||||
v = _("No")
|
||||
if v:
|
||||
data.append((f.label, v))
|
||||
return data
|
||||
|
||||
def pdf_watermark(self) -> Optional[str]:
|
||||
"""
|
||||
Return a watermark that should be rendered across the PDF file.
|
||||
|
||||
@@ -132,7 +132,7 @@ class AllowIgnoreQuotaColumn(BooleanColumnMixin, ImportColumn):
|
||||
|
||||
class PriceModeColumn(ImportColumn):
|
||||
identifier = 'price_mode'
|
||||
verbose_name = gettext_lazy('Price mode')
|
||||
verbose_name = gettext_lazy('Price effect')
|
||||
default_value = None
|
||||
initial = 'static:none'
|
||||
|
||||
@@ -147,7 +147,7 @@ class PriceModeColumn(ImportColumn):
|
||||
elif value in reverse:
|
||||
return reverse[value]
|
||||
else:
|
||||
raise ValidationError(_("Could not parse {value} as a price mode, use one of {options}.").format(
|
||||
raise ValidationError(_("Could not parse {value} as a price effect, use one of {options}.").format(
|
||||
value=value, options=', '.join(d.keys())
|
||||
))
|
||||
|
||||
@@ -162,7 +162,7 @@ class ValueColumn(DecimalColumnMixin, ImportColumn):
|
||||
def clean(self, value, previous_values):
|
||||
value = super().clean(value, previous_values)
|
||||
if value and previous_values.get("price_mode") == "none":
|
||||
raise ValidationError(_("It is pointless to set a value without a price mode."))
|
||||
raise ValidationError(_("It is pointless to set a value without a price effect."))
|
||||
return value
|
||||
|
||||
def assign(self, value, obj: Voucher, **kwargs):
|
||||
|
||||
@@ -130,6 +130,8 @@ class LoggingMixin:
|
||||
organizer_id = self.event.organizer_id
|
||||
elif hasattr(self, 'organizer_id'):
|
||||
organizer_id = self.organizer_id
|
||||
elif hasattr(self, 'issuer_id'):
|
||||
organizer_id = self.issuer_id
|
||||
|
||||
if user and not user.is_authenticated:
|
||||
user = None
|
||||
|
||||
@@ -40,6 +40,7 @@ from i18nfield.fields import I18nCharField
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
|
||||
from pretix.base.banlist import banned
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models.base import LoggedModel
|
||||
from pretix.base.models.fields import MultiStringField
|
||||
from pretix.base.models.giftcards import GiftCardTransaction
|
||||
@@ -164,6 +165,28 @@ class Customer(LoggedModel):
|
||||
self.attendee_profiles.all().delete()
|
||||
self.invoice_addresses.all().delete()
|
||||
|
||||
def send_security_notice(self, message, email=None):
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
try:
|
||||
with language(self.locale):
|
||||
mail(
|
||||
email or self.email,
|
||||
self.organizer.settings.mail_subject_customer_security_notice,
|
||||
self.organizer.settings.mail_text_customer_security_notice,
|
||||
{
|
||||
**self.get_email_context(),
|
||||
'message': str(message),
|
||||
'url': build_absolute_uri(self.organizer, 'presale:organizer.customer.index')
|
||||
},
|
||||
customer=self,
|
||||
organizer=self.organizer,
|
||||
locale=self.locale
|
||||
)
|
||||
except SendMailException:
|
||||
pass # Already logged
|
||||
|
||||
@scopes_disabled()
|
||||
def assign_identifier(self):
|
||||
charset = list('ABCDEFGHJKLMNPQRSTUVWXYZ23456789')
|
||||
|
||||
@@ -86,7 +86,7 @@ class OrderSyncQueue(models.Model):
|
||||
|
||||
def set_sync_error(self, failure_mode, messages, full_message):
|
||||
logger.exception(
|
||||
f"Could not sync order {self.order.code} to {type(self).__name__} ({failure_mode})"
|
||||
f"Could not sync order {self.order.code} to {self.sync_provider} ({failure_mode})"
|
||||
)
|
||||
self.order.log_action(f"pretix.event.order.data_sync.failed.{failure_mode}", {
|
||||
"provider": self.sync_provider,
|
||||
|
||||
@@ -87,7 +87,7 @@ from pretix.base.timemachine import time_machine_now
|
||||
|
||||
from ...helpers import OF_SELF
|
||||
from ...helpers.countries import CachedCountries, FastCountryField
|
||||
from ...helpers.format import format_map
|
||||
from ...helpers.format import FormattedString, format_map
|
||||
from ...helpers.names import build_name
|
||||
from ...testutils.middleware import debugflags_var
|
||||
from ._transactions import (
|
||||
@@ -1178,7 +1178,8 @@ class Order(LockModel, LoggedModel):
|
||||
recipient = position.attendee_email
|
||||
|
||||
email_content = render_mail(template, context)
|
||||
subject = format_map(subject, context)
|
||||
if not isinstance(subject, FormattedString):
|
||||
subject = format_map(subject, context)
|
||||
mail(
|
||||
recipient, subject, template, context,
|
||||
self.event, self.locale, self, headers=headers, sender=sender,
|
||||
@@ -2907,7 +2908,8 @@ class OrderPosition(AbstractPosition):
|
||||
with language(self.order.locale, self.order.event.settings.region):
|
||||
recipient = self.attendee_email
|
||||
email_content = render_mail(template, context)
|
||||
subject = format_map(subject, context)
|
||||
if not isinstance(subject, FormattedString):
|
||||
subject = format_map(subject, context)
|
||||
mail(
|
||||
recipient, subject, template, context,
|
||||
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
|
||||
@@ -3507,18 +3509,10 @@ class InvoiceAddress(models.Model):
|
||||
def describe_transmission(self):
|
||||
from pretix.base.invoicing.transmission import transmission_types
|
||||
data = []
|
||||
|
||||
t, __ = transmission_types.get(identifier=self.transmission_type)
|
||||
data.append((_("Transmission type"), t.public_name))
|
||||
form_data = t.transmission_info_to_form_data(self.transmission_info or {})
|
||||
for k, f in t.invoice_address_form_fields.items():
|
||||
v = form_data.get(k)
|
||||
if v is True:
|
||||
v = _("Yes")
|
||||
elif v is False:
|
||||
v = _("No")
|
||||
if v:
|
||||
data.append((f.label, v))
|
||||
if self.transmission_info:
|
||||
data += t.describe_info(self.transmission_info, self.country, self.is_business)
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ class Voucher(LoggedModel):
|
||||
)
|
||||
)
|
||||
price_mode = models.CharField(
|
||||
verbose_name=_("Price mode"),
|
||||
verbose_name=_("Price effect"),
|
||||
max_length=100,
|
||||
choices=PRICE_MODES,
|
||||
default='none'
|
||||
|
||||
@@ -1646,6 +1646,14 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
'transaction_id': trans.pk,
|
||||
}
|
||||
payment.confirm(send_mail=not is_early_special_case, generate_invoice=not is_early_special_case)
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.transaction.payment',
|
||||
data={
|
||||
'value': trans.value,
|
||||
'acceptor_id': self.event.organizer.id,
|
||||
'acceptor_slug': self.event.organizer.slug
|
||||
}
|
||||
)
|
||||
except PaymentException as e:
|
||||
payment.fail(info={'error': str(e)})
|
||||
raise e
|
||||
@@ -1670,6 +1678,15 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
'transaction_id': trans.pk,
|
||||
}
|
||||
refund.done()
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.transaction.refund',
|
||||
data={
|
||||
'value': refund.amount,
|
||||
'acceptor_id': self.event.organizer.id,
|
||||
'acceptor_slug': self.event.organizer.slug,
|
||||
'text': refund.comment,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@receiver(register_payment_providers, dispatch_uid="payment_free")
|
||||
|
||||
@@ -65,7 +65,7 @@ def get_all_plugins(*, event=None, organizer=None) -> List[type]:
|
||||
if app.name in settings.PRETIX_PLUGINS_EXCLUDE:
|
||||
continue
|
||||
|
||||
level = getattr(app, "level", PLUGIN_LEVEL_EVENT)
|
||||
level = getattr(meta, "level", PLUGIN_LEVEL_EVENT)
|
||||
if level == PLUGIN_LEVEL_EVENT:
|
||||
if event and hasattr(app, 'is_available'):
|
||||
if not app.is_available(event):
|
||||
|
||||
@@ -81,7 +81,9 @@ from pretix.base.signals import (
|
||||
)
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.format import SafeFormatter, format_map
|
||||
from pretix.helpers.format import (
|
||||
FormattedString, PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||
)
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.ical import get_private_icals
|
||||
@@ -218,6 +220,9 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
if email == INVALID_ADDRESS:
|
||||
return
|
||||
|
||||
if isinstance(template, FormattedString):
|
||||
raise TypeError("Cannot pass an already formatted body template")
|
||||
|
||||
if no_order_links and not plain_text_only:
|
||||
raise ValueError('If you set no_order_links, you also need to set plain_text_only.')
|
||||
|
||||
@@ -251,23 +256,28 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
if event and attach_tickets and not event.settings.mail_attach_tickets:
|
||||
attach_tickets = False
|
||||
|
||||
with language(locale):
|
||||
with language(locale), override(timezone):
|
||||
if isinstance(context, dict) and order:
|
||||
_autoextend_context(context, order)
|
||||
|
||||
# Build raw content
|
||||
body_plain = render_mail(template, context, placeholder_mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
||||
content_plain = render_mail(template, context, placeholder_mode=None)
|
||||
if settings_holder:
|
||||
signature = str(settings_holder.settings.get('mail_text_signature'))
|
||||
else:
|
||||
signature = ""
|
||||
|
||||
# Build full plain-text body
|
||||
if not isinstance(content_plain, FormattedString):
|
||||
body_plain = format_map(content_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
||||
else:
|
||||
body_plain = content_plain
|
||||
body_plain = _wrap_plain_body(body_plain, signature, event, order, position, no_order_links)
|
||||
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
||||
|
||||
# Build subject
|
||||
subject = str(subject).format_map(TolerantDict(context))
|
||||
if not isinstance(subject, FormattedString):
|
||||
subject = format_map(subject, context)
|
||||
|
||||
subject = raw_subject = subject.replace('\n', ' ').replace('\r', '')[:900]
|
||||
if settings_holder:
|
||||
subject = prefix_subject(settings_holder, subject)
|
||||
@@ -286,26 +296,24 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
else:
|
||||
renderer = ClassicMailRenderer(None, organizer)
|
||||
|
||||
with override(timezone):
|
||||
content_plain = render_mail(template, context, placeholder_mode=None)
|
||||
try:
|
||||
if 'context' in inspect.signature(renderer.render).parameters:
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
|
||||
elif 'position' in inspect.signature(renderer.render).parameters:
|
||||
# Backwards compatibility
|
||||
warnings.warn('Email renderer called without context argument because context argument is not '
|
||||
'supported.',
|
||||
DeprecationWarning)
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
||||
else:
|
||||
# Backwards compatibility
|
||||
warnings.warn('Email renderer called without position argument because position argument is not '
|
||||
'supported.',
|
||||
DeprecationWarning)
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order)
|
||||
except:
|
||||
logger.exception('Could not render HTML body')
|
||||
body_html = None
|
||||
try:
|
||||
if 'context' in inspect.signature(renderer.render).parameters:
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
|
||||
elif 'position' in inspect.signature(renderer.render).parameters:
|
||||
# Backwards compatibility
|
||||
warnings.warn('Email renderer called without context argument because context argument is not '
|
||||
'supported.',
|
||||
DeprecationWarning)
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
||||
else:
|
||||
# Backwards compatibility
|
||||
warnings.warn('Email renderer called without position argument because position argument is not '
|
||||
'supported.',
|
||||
DeprecationWarning)
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order)
|
||||
except:
|
||||
logger.exception('Could not render HTML body')
|
||||
body_html = None
|
||||
|
||||
m = OutgoingMail.objects.create(
|
||||
organizer=organizer,
|
||||
@@ -381,7 +389,7 @@ def mail_send_task(self, **kwargs) -> bool:
|
||||
# mail_send_task(self, *, outgoing_mail)
|
||||
with scopes_disabled():
|
||||
mail_send(**kwargs)
|
||||
return
|
||||
return False
|
||||
else:
|
||||
raise ValueError("Unknown arguments")
|
||||
|
||||
@@ -435,15 +443,24 @@ def mail_send_task(self, **kwargs) -> bool:
|
||||
content = ct.file.read()
|
||||
args.append((name, content, ct.type))
|
||||
attach_size += len(content)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
# This sometimes fails e.g. with FileNotFoundError. We haven't been able to figure out
|
||||
# why (probably some race condition with ticket cache invalidation?), so retry later.
|
||||
try:
|
||||
self.retry(max_retries=5, countdown=60)
|
||||
logger.exception(f'Could not attach tickets to email {outgoing_mail.guid}, will retry')
|
||||
retry_after = 60
|
||||
outgoing_mail.error = "Tickets not ready"
|
||||
outgoing_mail.error_detail = str(e)
|
||||
outgoing_mail.sent = now()
|
||||
outgoing_mail.status = OutgoingMail.STATUS_AWAITING_RETRY
|
||||
outgoing_mail.retry_after = now() + timedelta(seconds=retry_after)
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after",
|
||||
"actual_attachments"])
|
||||
self.retry(max_retries=5, countdown=retry_after)
|
||||
except MaxRetriesExceededError:
|
||||
# Well then, something is really wrong, let's send it without attachment before we
|
||||
# don't send at all
|
||||
logger.exception(f'Could not attach tickets to email {outgoing_mail.guid}')
|
||||
logger.exception(f'Too many retries attaching tickets to email {outgoing_mail.guid}, skip attachment')
|
||||
pass
|
||||
|
||||
if attach_size * 1.37 < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1024 * 1024:
|
||||
@@ -519,6 +536,7 @@ def mail_send_task(self, **kwargs) -> bool:
|
||||
)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
expected_recipients = {e.lower() for e in expected_recipients if e}
|
||||
if any(t in expected_recipients for t in outgoing_mail.to):
|
||||
invoices_to_mark_transmitted.append(inv)
|
||||
|
||||
@@ -789,7 +807,12 @@ def render_mail(template, context, placeholder_mode: Optional[int]=SafeFormatter
|
||||
body = format_map(body, context, mode=placeholder_mode)
|
||||
else:
|
||||
tpl = get_template(template)
|
||||
body = tpl.render(context)
|
||||
context = {
|
||||
# Known bug, should behave differently for plain and HTML but we'll fix after security release
|
||||
k: v.html if isinstance(v, PlainHtmlAlternativeString) else v
|
||||
for k, v in context.items()
|
||||
}
|
||||
body = FormattedString(tpl.render(context))
|
||||
return body
|
||||
|
||||
|
||||
@@ -935,7 +958,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
|
||||
body_plain += "\r\n\r\n-- \r\n"
|
||||
|
||||
if signature:
|
||||
signature = signature.format(event=event.name if event else '')
|
||||
signature = format_map(signature, {"event": event.name if event else ''})
|
||||
body_plain += signature
|
||||
body_plain += "\r\n\r\n-- \r\n"
|
||||
|
||||
@@ -947,7 +970,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
|
||||
body_plain += _(
|
||||
"You can view your order details at the following URL:\n{orderurl}."
|
||||
).replace("\n", "\r\n").format(
|
||||
event=event.name, orderurl=build_absolute_uri(
|
||||
orderurl=build_absolute_uri(
|
||||
order.event, 'presale:event.order.position', kwargs={
|
||||
'order': order.code,
|
||||
'secret': position.web_secret,
|
||||
|
||||
@@ -247,6 +247,16 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
|
||||
for gc in position.issued_gift_cards.all():
|
||||
gc = GiftCard.objects.select_for_update(of=OF_SELF).get(pk=gc.pk)
|
||||
gc.transactions.create(value=position.price, order=order, acceptor=order.event.organizer)
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=user,
|
||||
auth=auth,
|
||||
data={
|
||||
'value': position.price,
|
||||
'acceptor_id': order.event.organizer.id,
|
||||
'acceptor_slug': order.event.organizer.slug
|
||||
}
|
||||
)
|
||||
break
|
||||
|
||||
for m in position.granted_memberships.all():
|
||||
@@ -548,6 +558,15 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
||||
)
|
||||
else:
|
||||
gc.transactions.create(value=-position.price, order=order, acceptor=order.event.organizer)
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=user,
|
||||
data={
|
||||
'value': -position.price,
|
||||
'acceptor_id': order.event.organizer.id,
|
||||
'acceptor_slug': order.event.organizer.slug
|
||||
}
|
||||
)
|
||||
|
||||
for m in position.granted_memberships.all():
|
||||
m.canceled = True
|
||||
@@ -2434,6 +2453,16 @@ class OrderChangeManager:
|
||||
))
|
||||
else:
|
||||
gc.transactions.create(value=-position.price, order=self.order, acceptor=self.order.event.organizer)
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=self.user,
|
||||
auth=self.auth,
|
||||
data={
|
||||
'value': -position.price,
|
||||
'acceptor_id': self.order.event.organizer.id,
|
||||
'acceptor_slug': self.order.event.organizer.slug
|
||||
}
|
||||
)
|
||||
|
||||
for m in position.granted_memberships.with_usages().all():
|
||||
m.canceled = True
|
||||
@@ -2451,6 +2480,16 @@ class OrderChangeManager:
|
||||
))
|
||||
else:
|
||||
gc.transactions.create(value=-opa.position.price, order=self.order, acceptor=self.order.event.organizer)
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=self.user,
|
||||
auth=self.auth,
|
||||
data={
|
||||
'value': -opa.position.price,
|
||||
'acceptor_id': self.order.event.organizer.id,
|
||||
'acceptor_slug': self.order.event.organizer.slug
|
||||
}
|
||||
)
|
||||
|
||||
for m in opa.granted_memberships.with_usages().all():
|
||||
m.canceled = True
|
||||
@@ -3119,7 +3158,10 @@ def _try_auto_refund(order, auto_refund=True, manual_refund=False, allow_partial
|
||||
customer=order.customer,
|
||||
testmode=order.testmode
|
||||
)
|
||||
giftcard.log_action('pretix.giftcards.created', data={})
|
||||
giftcard.log_action(
|
||||
action='pretix.giftcards.created',
|
||||
data={}
|
||||
)
|
||||
r = order.refunds.create(
|
||||
order=order,
|
||||
payment=None,
|
||||
@@ -3406,7 +3448,18 @@ def signal_listener_issue_giftcards(sender: Event, order: Order, **kwargs):
|
||||
currency=sender.currency, issued_in=p, testmode=order.testmode,
|
||||
expires=sender.organizer.default_gift_card_expiry,
|
||||
)
|
||||
gc.transactions.create(value=p.price - issued, order=order, acceptor=sender.organizer)
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.created',
|
||||
)
|
||||
trans = gc.transactions.create(value=p.price - issued, order=order, acceptor=sender.organizer)
|
||||
gc.log_action(
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
data={
|
||||
'value': trans.value,
|
||||
'acceptor_id': order.event.organizer.id,
|
||||
'acceptor_slug': order.event.organizer.slug
|
||||
}
|
||||
)
|
||||
any_giftcards = True
|
||||
p.secret = gc.secret
|
||||
p.save(update_fields=['secret'])
|
||||
|
||||
@@ -2948,6 +2948,28 @@ If you did not request a new password, please ignore this email.
|
||||
|
||||
Best regards,
|
||||
|
||||
Your {organizer} team""")) # noqa: W291
|
||||
},
|
||||
'mail_subject_customer_security_notice': {
|
||||
'type': LazyI18nString,
|
||||
'default': LazyI18nString.from_gettext(gettext_noop("Changes to your account at {organizer}")),
|
||||
},
|
||||
'mail_text_customer_security_notice': {
|
||||
'type': LazyI18nString,
|
||||
'default': LazyI18nString.from_gettext(gettext_noop("""Hello {name},
|
||||
|
||||
the following change has been made to your account at {organizer}:
|
||||
|
||||
{message}
|
||||
|
||||
You can review and change your account settings here:
|
||||
|
||||
{url}
|
||||
|
||||
If this change was not performed by you, please contact us immediately.
|
||||
|
||||
Best regards,
|
||||
|
||||
Your {organizer} team""")) # noqa: W291
|
||||
},
|
||||
'smtp_use_custom': {
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
<h1>{% trans "Not found" %}</h1>
|
||||
<p>{% trans "I'm afraid we could not find the the resource you requested." %}</p>
|
||||
<p>{{ exception }}</p>
|
||||
<p class="links">
|
||||
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
|
||||
</p>
|
||||
{% if request.user.is_staff and not staff_session %}
|
||||
<form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post">
|
||||
<p>
|
||||
|
||||
@@ -95,6 +95,7 @@ class OrganizerSlugBanlistValidator(BanlistValidator):
|
||||
'csp_report',
|
||||
'widget',
|
||||
'lead',
|
||||
'scheduling',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import pycountry
|
||||
from django.conf import settings
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext, pgettext, pgettext_lazy
|
||||
@@ -29,6 +30,7 @@ from django_scopes import scope
|
||||
from pretix.base.addressvalidation import (
|
||||
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED,
|
||||
)
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.invoicing.transmission import get_transmission_types
|
||||
from pretix.base.models import Organizer
|
||||
from pretix.base.models.tax import VAT_ID_COUNTRIES
|
||||
@@ -89,7 +91,7 @@ def _info(cc):
|
||||
}
|
||||
|
||||
|
||||
def address_form(request):
|
||||
def _address_form(request):
|
||||
cc = request.GET.get("country", "DE")
|
||||
info = _info(cc)
|
||||
|
||||
@@ -157,4 +159,15 @@ def address_form(request):
|
||||
# The help text explains that it is optional, so we want to hide that if it is required
|
||||
info["vat_id"]["helptext_visible"] = False
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def address_form(request):
|
||||
locale = request.GET.get('locale')
|
||||
if locale in dict(settings.LANGUAGES):
|
||||
with language(locale):
|
||||
info = _address_form(request)
|
||||
else:
|
||||
info = _address_form(request)
|
||||
|
||||
return JsonResponse(info)
|
||||
|
||||
@@ -2820,8 +2820,8 @@ class DeviceFilterForm(FilterForm):
|
||||
|
||||
class OutgoingMailFilterForm(FilterForm):
|
||||
orders = {
|
||||
'date': 'd',
|
||||
'-date': '-d',
|
||||
'date': 'created',
|
||||
'-date': '-created',
|
||||
}
|
||||
query = forms.CharField(
|
||||
label=_('Search email address or subject'),
|
||||
|
||||
@@ -635,6 +635,16 @@ class MailSettingsForm(SettingsForm):
|
||||
required=False,
|
||||
widget=I18nMarkdownTextarea,
|
||||
)
|
||||
mail_subject_customer_security_notice = I18nFormField(
|
||||
label=_("Subject"),
|
||||
required=False,
|
||||
widget=I18nTextInput,
|
||||
)
|
||||
mail_text_customer_security_notice = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nMarkdownTextarea,
|
||||
)
|
||||
|
||||
base_context = {
|
||||
'mail_text_customer_registration': ['customer', 'url'],
|
||||
@@ -643,6 +653,8 @@ class MailSettingsForm(SettingsForm):
|
||||
'mail_subject_customer_email_change': ['customer', 'url'],
|
||||
'mail_text_customer_reset': ['customer', 'url'],
|
||||
'mail_subject_customer_reset': ['customer', 'url'],
|
||||
'mail_text_customer_security_notice': ['customer', 'url', 'message'],
|
||||
'mail_subject_customer_security_notice': ['customer', 'url', 'message'],
|
||||
}
|
||||
|
||||
def _get_sample_context(self, base_parameters):
|
||||
@@ -656,6 +668,9 @@ class MailSettingsForm(SettingsForm):
|
||||
'presale:organizer.customer.activate'
|
||||
) + '?token=' + get_random_string(30)
|
||||
|
||||
if 'message' in base_parameters:
|
||||
placeholders['message'] = _('Your password has been changed.')
|
||||
|
||||
if 'customer' in base_parameters:
|
||||
placeholders['name'] = pgettext_lazy('person_name_sample', 'John Doe')
|
||||
name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme]
|
||||
|
||||
@@ -19,17 +19,44 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes.forms import SafeModelChoiceField
|
||||
from phonenumber_field.formfields import PhoneNumberField
|
||||
|
||||
from pretix.base.forms import I18nModelForm
|
||||
from pretix.base.forms.questions import (
|
||||
NamePartsFormField, WrappedPhoneNumberPrefixWidget,
|
||||
)
|
||||
from pretix.base.models import WaitingListEntry
|
||||
from pretix.control.forms.widgets import Select2
|
||||
|
||||
|
||||
class WaitingListEntryTransferForm(I18nModelForm):
|
||||
class WaitingListEntryEditForm(I18nModelForm):
|
||||
itemvar = forms.ChoiceField(
|
||||
error_messages={
|
||||
'invalid_choice': _("Select a valid choice.")
|
||||
}
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.instance = kwargs.get('instance', None)
|
||||
initial = kwargs.get('initial', {})
|
||||
|
||||
choices = []
|
||||
if self.instance and self.instance.pk and 'itemvar' not in initial:
|
||||
if self.instance.variation is not None:
|
||||
initial['itemvar'] = f'{self.instance.item.pk}-{self.instance.variation.pk}'
|
||||
if self.instance.variation.active is False:
|
||||
choices.append((initial['itemvar'], str(self.instance.variation)))
|
||||
else:
|
||||
initial['itemvar'] = self.instance.item.pk
|
||||
if self.instance.item.active is False:
|
||||
choices.append((initial['itemvar'], str(self.instance)))
|
||||
|
||||
kwargs['initial'] = initial
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.event.has_subevents:
|
||||
@@ -45,12 +72,73 @@ class WaitingListEntryTransferForm(I18nModelForm):
|
||||
}
|
||||
)
|
||||
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
|
||||
else:
|
||||
del self.fields['subevent']
|
||||
|
||||
if self.event.settings.waiting_list_names_asked:
|
||||
self.fields['name_parts'] = NamePartsFormField(
|
||||
max_length=255,
|
||||
required=self.event.settings.waiting_list_names_required,
|
||||
scheme=self.event.organizer.settings.name_scheme,
|
||||
titles=self.event.organizer.settings.name_scheme_titles,
|
||||
label=_('Name'),
|
||||
)
|
||||
else:
|
||||
del self.fields['name_parts']
|
||||
|
||||
if not self.event.settings.waiting_list_phones_asked:
|
||||
del self.fields['phone']
|
||||
|
||||
items = self.event.items.filter(active=True).prefetch_related(
|
||||
'variations'
|
||||
)
|
||||
|
||||
for item in items:
|
||||
if len(item.variations.all()) > 0:
|
||||
for variation in item.variations.all():
|
||||
if variation.active:
|
||||
choices.append(
|
||||
('{}-{}'.format(item.pk, variation.pk), '{} - {}'.format(str(item), str(variation)))
|
||||
)
|
||||
else:
|
||||
choices.append(('{}'.format(item.pk), str(item)))
|
||||
|
||||
self.fields['itemvar'].label = _("Product")
|
||||
self.fields['itemvar'].help_text = _("Only includes active products.")
|
||||
self.fields['itemvar'].required = True
|
||||
self.fields['itemvar'].choices = choices
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
if self.instance.voucher is not None:
|
||||
raise forms.ValidationError(_('A voucher for this waiting list entry was already sent out.'))
|
||||
|
||||
itemvar = cleaned_data.get('itemvar')
|
||||
if itemvar:
|
||||
self.instance.item = self.event.items.get(pk=itemvar.split('-')[0])
|
||||
if '-' in itemvar:
|
||||
self.instance.variation = self.instance.item.variations.get(pk=itemvar.split('-')[1])
|
||||
|
||||
if ((self.instance.item and not self.instance.item.active) or
|
||||
(self.instance.variation and not self.instance.variation.active)):
|
||||
self.add_error('itemvar', _('The selected product is not active.'))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
model = WaitingListEntry
|
||||
fields = [
|
||||
'email',
|
||||
'name_parts',
|
||||
'phone',
|
||||
'subevent',
|
||||
]
|
||||
field_classes = {
|
||||
'subevent': SafeModelChoiceField,
|
||||
'email': forms.EmailField,
|
||||
'phone': PhoneNumberField,
|
||||
}
|
||||
widgets = {
|
||||
'phone': WrappedPhoneNumberPrefixWidget,
|
||||
}
|
||||
|
||||
@@ -801,6 +801,8 @@ class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
|
||||
'pretix.giftcards.created': _('The gift card has been created.'),
|
||||
'pretix.giftcards.modified': _('The gift card has been changed.'),
|
||||
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
|
||||
'pretix.giftcards.transaction.payment': _('A payment has been performed.'),
|
||||
'pretix.giftcards.transaction.refund': _('A refund has been performed. '),
|
||||
'pretix.team.token.created': _('The token "{name}" has been created.'),
|
||||
'pretix.team.token.deleted': _('The token "{name}" has been revoked.'),
|
||||
'pretix.event.checkin.reset': _('The check-in and print log state has been reset.')
|
||||
|
||||
@@ -19,6 +19,14 @@
|
||||
</ul>
|
||||
<br>
|
||||
{% endif %}
|
||||
{% if possible_cookie_problem %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
It looks like your browser is not accepting our cookie and you need to log in repeatedly. Please
|
||||
check if your browser is set to block cookies, or delete all existing cookies and retry.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
<div class="form-group buttons">
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
|
||||
{% blocktrans asvar title_reset %}Customer account password reset{% endblocktrans %}
|
||||
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="reset" title=title_reset items="mail_subject_customer_reset,mail_text_customer_reset" %}
|
||||
|
||||
{% blocktrans asvar title_security_notice %}Customer account security notification{% endblocktrans %}
|
||||
{% include "pretixcontrol/event/mail_settings_fragment.html" with pid="security_notice" title=title_security_notice items="mail_subject_customer_security_notice,mail_text_customer_security_notice" %}
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "From" context "email" %}</dt>
|
||||
<dd>{{ mail.sender }}</dd>
|
||||
<dd>{{ sender }}</dd>
|
||||
<dt>{% trans "To" context "email" %}</dt>
|
||||
<dd>{{ mail.to|join:", " }}</dd>
|
||||
{% if mail.cc %}
|
||||
|
||||
@@ -144,14 +144,23 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
{% trans "If you lose access to your devices, you can use one of the following keys to log in. We recommend to store them in a safe place, e.g. printed out or in a password manager. Every token can be used at most once." %}
|
||||
{% blocktrans trimmed %}
|
||||
If you lose access to your devices, you can use one of your emergency tokens to log in.
|
||||
We recommend to store them in a safe place, e.g. printed out or in a password manager.
|
||||
Every token can be used at most once.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>{% trans "Unused tokens:" %}</p>
|
||||
<ul>
|
||||
{% for t in static_tokens %}
|
||||
<li><code>{{ t.token }}</code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if static_tokens_device %}
|
||||
<p>
|
||||
{% blocktrans trimmed with generation_date_time=static_tokens_device.created_at %}
|
||||
You generated your emergency tokens on {{ generation_date_time }}.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
{% trans "You don't have any emergency tokens yet." %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<a href="{% url "control:user.settings.2fa.regenemergency" %}" class="btn btn-default">
|
||||
<span class="fa fa-refresh"></span>
|
||||
{% trans "Generate new emergency tokens" %}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Edit entry" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Edit entry" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if form.subevent %}
|
||||
{% bootstrap_field form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
|
||||
{% bootstrap_field form.email layout="control" %}
|
||||
|
||||
{% if form.name_parts %}
|
||||
{% bootstrap_field form.name_parts layout="control" %}
|
||||
{% endif %}
|
||||
|
||||
{% if form.phone %}
|
||||
{% bootstrap_field form.phone layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field form.itemvar layout="control" %}
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:event.orders.waitinglist" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -124,6 +124,7 @@
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input name="search" type="text" placeholder="{% trans "Search" %}" class="form-control" value="{{ request.GET.search }}">
|
||||
{% if request.event.has_subevents %}
|
||||
<select name="subevent" class="form-control">
|
||||
<option value="">{% trans "All dates" context "subevent" %}</option>
|
||||
@@ -267,13 +268,13 @@
|
||||
data-toggle="tooltip" title="{% trans "Move to the end of the list" %}">
|
||||
<span class="fa fa-thumbs-down"></span>
|
||||
</button>
|
||||
{% if request.event.has_subevents %}
|
||||
<a href="{% url "control:event.orders.waitinglist.transfer" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}"
|
||||
class="btn btn-default btn-sm" title="{% trans "Transfer to other date" context "subevent" %}"
|
||||
data-toggle="tooltip">
|
||||
<i class="fa fa-calendar" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url "control:event.orders.waitinglist.edit" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}"
|
||||
class="btn btn-default btn-sm" title="{% trans "Edit entry" %}"
|
||||
data-toggle="tooltip">
|
||||
<i class="fa fa-edit" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
<a href="{% url "control:event.orders.waitinglist.delete" organizer=request.event.organizer.slug event=request.event.slug entry=e.id %}?next={{ request.get_full_path|urlencode }}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% else %}
|
||||
<button class="btn btn-default btn-sm disabled">
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Transfer entry" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Transfer entry" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans trimmed context "subevent" %}
|
||||
Please select the date to which the following waiting list entry should be
|
||||
transferred: <strong>{{ entry }}</strong>?
|
||||
{% endblocktrans %}</p>
|
||||
{% bootstrap_field form.subevent layout="control" %}
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "control:event.orders.waitinglist" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Transfer" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -480,8 +480,8 @@ urlpatterns = [
|
||||
re_path(r'^waitinglist/auto_assign$', waitinglist.AutoAssign.as_view(), name='event.orders.waitinglist.auto'),
|
||||
re_path(r'^waitinglist/(?P<entry>\d+)/delete$', waitinglist.EntryDelete.as_view(),
|
||||
name='event.orders.waitinglist.delete'),
|
||||
re_path(r'^waitinglist/(?P<entry>\d+)/transfer$', waitinglist.EntryTransfer.as_view(),
|
||||
name='event.orders.waitinglist.transfer'),
|
||||
re_path(r'^waitinglist/(?P<entry>\d+)/edit$', waitinglist.EntryEdit.as_view(),
|
||||
name='event.orders.waitinglist.edit'),
|
||||
re_path(r'^checkins/$', checkin.CheckinListView.as_view(), name='event.orders.checkins'),
|
||||
re_path(r'^checkinlists/$', checkin.CheckinListList.as_view(), name='event.orders.checkinlists'),
|
||||
re_path(r'^checkinlists/add$', checkin.CheckinListCreate.as_view(), name='event.orders.checkinlists.add'),
|
||||
|
||||
@@ -149,6 +149,8 @@ def login(request):
|
||||
return process_login(request, form.user_cache, form.cleaned_data.get('keep_logged_in', False))
|
||||
else:
|
||||
form = LoginForm(backend=backend, request=request)
|
||||
# Detect redirection loop (usually means cookie not accepted)
|
||||
ctx['possible_cookie_problem'] = request.path in request.headers.get("Referer", "")
|
||||
ctx['form'] = form
|
||||
ctx['can_register'] = settings.PRETIX_REGISTRATION
|
||||
ctx['can_reset'] = settings.PRETIX_PASSWORD_RESET
|
||||
|
||||
@@ -870,11 +870,15 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
)
|
||||
|
||||
except ValueError:
|
||||
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
|
||||
PlaceholderValidator.error_message)
|
||||
msgs[self.supported_locale[idx]] = format_html(
|
||||
'<div class="alert alert-danger">{}</div>',
|
||||
PlaceholderValidator.error_message
|
||||
)
|
||||
except KeyError as e:
|
||||
msgs[self.supported_locale[idx]] = '<div class="alert alert-danger">{}</div>'.format(
|
||||
_('Invalid placeholder: {%(value)s}') % {'value': e.args[0]})
|
||||
msgs[self.supported_locale[idx]] = format_html(
|
||||
'<div class="alert alert-danger">{}</div>',
|
||||
_('Invalid placeholder: {%(value)s}') % {'value': e.args[0]}
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'item': preview_item,
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#
|
||||
import base64
|
||||
import logging
|
||||
from email.header import decode_header, make_header
|
||||
from email.utils import parseaddr
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -124,6 +126,12 @@ class OutgoingMailDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequir
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
if self.object.body_html:
|
||||
ctx['data_url'] = "data:text/html;charset=utf-8;base64," + base64.b64encode(self.object.body_html.encode()).decode()
|
||||
|
||||
from_name, from_email = parseaddr(self.object.sender)
|
||||
if from_name:
|
||||
from_name = make_header(decode_header(from_name))
|
||||
ctx['sender'] = "{} <{}>".format(from_name, from_email) if from_name else from_email
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
@@ -1226,7 +1226,11 @@ class OrderRefundView(OrderView):
|
||||
customer=order.customer,
|
||||
testmode=order.testmode
|
||||
)
|
||||
giftcard.log_action('pretix.giftcards.created', user=self.request.user, data={})
|
||||
giftcard.log_action(
|
||||
action='pretix.giftcards.created',
|
||||
user=self.request.user,
|
||||
data={}
|
||||
)
|
||||
refunds.append(OrderRefund(
|
||||
order=order,
|
||||
payment=None,
|
||||
|
||||
@@ -1667,9 +1667,12 @@ class GiftCardAcceptanceInviteView(OrganizerDetailViewMixin, OrganizerPermission
|
||||
active=False,
|
||||
)
|
||||
self.request.organizer.log_action(
|
||||
'pretix.giftcards.acceptance.acceptor.invited',
|
||||
data={'acceptor': form.cleaned_data['acceptor'].slug,
|
||||
'reusable_media': form.cleaned_data['reusable_media']},
|
||||
action='pretix.giftcards.acceptance.acceptor.invited',
|
||||
data={
|
||||
'acceptor': form.cleaned_data['acceptor'].slug,
|
||||
'issuer': self.request.organizer.slug,
|
||||
'reusable_media': form.cleaned_data['reusable_media']
|
||||
},
|
||||
user=self.request.user
|
||||
)
|
||||
messages.success(self.request, _('The selected organizer has been invited.'))
|
||||
@@ -1705,8 +1708,11 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
|
||||
).delete()
|
||||
if done:
|
||||
self.request.organizer.log_action(
|
||||
'pretix.giftcards.acceptance.acceptor.removed',
|
||||
data={'acceptor': request.POST.get("delete_acceptor")},
|
||||
action='pretix.giftcards.acceptance.acceptor.removed',
|
||||
data={
|
||||
'acceptor': request.POST.get("delete_acceptor"),
|
||||
'issuer': self.request.organizer.slug
|
||||
},
|
||||
user=request.user
|
||||
)
|
||||
messages.success(self.request, _('The selected connection has been removed.'))
|
||||
@@ -1716,8 +1722,11 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
|
||||
).delete()
|
||||
if done:
|
||||
self.request.organizer.log_action(
|
||||
'pretix.giftcards.acceptance.issuer.removed',
|
||||
data={'issuer': request.POST.get("delete_acceptor")},
|
||||
action='pretix.giftcards.acceptance.issuer.removed',
|
||||
data={
|
||||
'issuer': request.POST.get("delete_acceptor"),
|
||||
'acceptor': self.request.organizer.slug
|
||||
},
|
||||
user=request.user
|
||||
)
|
||||
messages.success(self.request, _('The selected connection has been removed.'))
|
||||
@@ -1727,8 +1736,11 @@ class GiftCardAcceptanceListView(OrganizerDetailViewMixin, OrganizerPermissionRe
|
||||
).update(active=True)
|
||||
if done:
|
||||
self.request.organizer.log_action(
|
||||
'pretix.giftcards.acceptance.issuer.accepted',
|
||||
data={'issuer': request.POST.get("accept_issuer")},
|
||||
action='pretix.giftcards.acceptance.issuer.accepted',
|
||||
data={
|
||||
'issuer': request.POST.get("accept_issuer"),
|
||||
'acceptor': self.request.organizer.slug
|
||||
},
|
||||
user=request.user
|
||||
)
|
||||
messages.success(self.request, _('The selected connection has been accepted.'))
|
||||
@@ -1834,10 +1846,12 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
acceptor=request.organizer,
|
||||
)
|
||||
self.object.log_action(
|
||||
'pretix.giftcards.transaction.manual',
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
data={
|
||||
'value': value,
|
||||
'text': request.POST.get('text')
|
||||
'text': request.POST.get('text'),
|
||||
'acceptor_id': self.request.organizer.id,
|
||||
'acceptor_slug': self.request.organizer.slug
|
||||
},
|
||||
user=self.request.user,
|
||||
)
|
||||
@@ -1886,15 +1900,24 @@ class GiftCardCreateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
messages.success(self.request, _('The gift card has been created and can now be used.'))
|
||||
form.instance.issuer = self.request.organizer
|
||||
super().form_valid(form)
|
||||
form.instance.transactions.create(
|
||||
acceptor=self.request.organizer,
|
||||
value=form.cleaned_data['value']
|
||||
form.instance.log_action(
|
||||
action='pretix.giftcards.created',
|
||||
user=self.request.user,
|
||||
)
|
||||
form.instance.log_action('pretix.giftcards.created', user=self.request.user, data={})
|
||||
if form.cleaned_data['value']:
|
||||
form.instance.log_action('pretix.giftcards.transaction.manual', user=self.request.user, data={
|
||||
'value': form.cleaned_data['value']
|
||||
})
|
||||
form.instance.transactions.create(
|
||||
acceptor=self.request.organizer,
|
||||
value=form.cleaned_data['value']
|
||||
)
|
||||
form.instance.log_action(
|
||||
action='pretix.giftcards.transaction.manual',
|
||||
user=self.request.user,
|
||||
data={
|
||||
'value': form.cleaned_data['value'],
|
||||
'acceptor_id': self.request.organizer.id,
|
||||
'acceptor_slug': self.request.organizer.slug
|
||||
}
|
||||
)
|
||||
return redirect(reverse(
|
||||
'control:organizer.giftcard',
|
||||
kwargs={
|
||||
@@ -1922,7 +1945,11 @@ class GiftCardUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('The gift card has been changed.'))
|
||||
super().form_valid(form)
|
||||
form.instance.log_action('pretix.giftcards.modified', user=self.request.user, data=dict(form.cleaned_data))
|
||||
form.instance.log_action(
|
||||
action='pretix.giftcards.modified',
|
||||
user=self.request.user,
|
||||
data=dict(form.cleaned_data)
|
||||
)
|
||||
return redirect(reverse(
|
||||
'control:organizer.giftcard',
|
||||
kwargs={
|
||||
|
||||
@@ -49,12 +49,14 @@ from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.decorators.cache import never_cache
|
||||
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
|
||||
@@ -85,8 +87,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RecentAuthenticationRequiredMixin:
|
||||
max_time = 3600
|
||||
max_time = 900
|
||||
|
||||
@method_decorator(never_cache)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
tdelta = time.time() - request.session.get('pretix_auth_login_time', 0)
|
||||
if tdelta > self.max_time:
|
||||
@@ -289,16 +292,13 @@ class User2FAMainView(RecentAuthenticationRequiredMixin, TemplateView):
|
||||
ctx = super().get_context_data()
|
||||
|
||||
try:
|
||||
ctx['static_tokens'] = StaticDevice.objects.get(user=self.request.user, name='emergency').token_set.all()
|
||||
ctx['static_tokens_device'] = StaticDevice.objects.get(user=self.request.user, name='emergency')
|
||||
except StaticDevice.MultipleObjectsReturned:
|
||||
ctx['static_tokens'] = StaticDevice.objects.filter(
|
||||
ctx['static_tokens_device'] = StaticDevice.objects.filter(
|
||||
user=self.request.user, name='emergency'
|
||||
).first().token_set.all()
|
||||
).first()
|
||||
except StaticDevice.DoesNotExist:
|
||||
d = StaticDevice.objects.create(user=self.request.user, name='emergency')
|
||||
for i in range(10):
|
||||
d.token_set.create(token=get_random_string(length=12, allowed_chars='1234567890'))
|
||||
ctx['static_tokens'] = d.token_set.all()
|
||||
ctx['static_tokens_device'] = None
|
||||
|
||||
ctx['devices'] = []
|
||||
for dt in REAL_DEVICE_TYPES:
|
||||
@@ -630,8 +630,14 @@ class User2FARegenerateEmergencyView(RecentAuthenticationRequiredMixin, Template
|
||||
])
|
||||
self.request.user.update_session_token()
|
||||
update_session_auth_hash(self.request, self.request.user)
|
||||
messages.success(request, _('Your emergency codes have been newly generated. Remember to store them in a safe '
|
||||
'place in case you lose access to your devices.'))
|
||||
messages.success(
|
||||
request,
|
||||
_('Your emergency codes have been newly generated. Remember to store them in a safe '
|
||||
'place in case you lose access to your devices. You will not be able to view them '
|
||||
'again here.\n\nYour emergency codes:\n{tokens}').format(
|
||||
tokens='- ' + '\n- '.join(t.token for t in d.token_set.all())
|
||||
)
|
||||
)
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ from pretix.base.models import Item, LogEntry, Quota, WaitingListEntry
|
||||
from pretix.base.models.waitinglist import WaitingListException
|
||||
from pretix.base.services.waitinglist import assign_automatically
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.control.forms.waitinglist import WaitingListEntryTransferForm
|
||||
from pretix.control.forms.waitinglist import WaitingListEntryEditForm
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
|
||||
@@ -138,6 +138,17 @@ class WaitingListQuerySetMixin:
|
||||
elif force_filtered and '__ALL' not in self.request_data:
|
||||
qs = qs.none()
|
||||
|
||||
if self.request_data.get("search", "") != "":
|
||||
s = self.request_data.get("search", "")
|
||||
search_q = Q(email__icontains=s)
|
||||
|
||||
if self.request.event.settings.waiting_list_names_asked:
|
||||
search_q = search_q | Q(name_cached__icontains=s)
|
||||
if self.request.event.settings.waiting_list_phones_asked:
|
||||
search_q = search_q | Q(phone__icontains=s)
|
||||
|
||||
qs = qs.filter(search_q)
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
@@ -238,7 +249,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['items'] = Item.objects.filter(event=self.request.event)
|
||||
ctx['filtered'] = ("status" in self.request.GET or "item" in self.request.GET)
|
||||
ctx['filtered'] = any(param in self.request.GET for param in ("status", "item", "search"))
|
||||
|
||||
itemvar_cache = {}
|
||||
quota_cache = {}
|
||||
@@ -390,25 +401,20 @@ class EntryDelete(EventPermissionRequiredMixin, CompatDeleteView):
|
||||
})
|
||||
|
||||
|
||||
class EntryTransfer(EventPermissionRequiredMixin, UpdateView):
|
||||
class EntryEdit(EventPermissionRequiredMixin, UpdateView):
|
||||
model = WaitingListEntry
|
||||
template_name = 'pretixcontrol/waitinglist/transfer.html'
|
||||
template_name = 'pretixcontrol/waitinglist/edit.html'
|
||||
permission = 'can_change_orders'
|
||||
form_class = WaitingListEntryTransferForm
|
||||
form_class = WaitingListEntryEditForm
|
||||
context_object_name = 'entry'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.request.event.has_subevents:
|
||||
raise Http404(_("This is not an event series."))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_object(self, queryset=None) -> WaitingListEntry:
|
||||
return get_object_or_404(WaitingListEntry, pk=self.kwargs['entry'], event=self.request.event, voucher__isnull=True)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('The waitinglist entry has been transferred.'))
|
||||
if form.has_changed():
|
||||
messages.success(self.request, _('The waitinglist entry has been changed.'))
|
||||
self.object.log_action(
|
||||
'pretix.event.orders.waitinglist.changed', user=self.request.user, data={
|
||||
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import logging
|
||||
from string import Formatter
|
||||
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.utils.html import conditional_escape
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -37,6 +38,17 @@ class PlainHtmlAlternativeString:
|
||||
return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')"
|
||||
|
||||
|
||||
class FormattedString(str):
|
||||
"""
|
||||
A str subclass that has been specifically marked as "already formatted" for email rendering
|
||||
purposes to avoid duplicate formatting.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __str__(self):
|
||||
return self
|
||||
|
||||
|
||||
class SafeFormatter(Formatter):
|
||||
"""
|
||||
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
|
||||
@@ -77,8 +89,19 @@ class SafeFormatter(Formatter):
|
||||
# Ignore format_spec
|
||||
return super().format_field(self._prepare_value(value), '')
|
||||
|
||||
def convert_field(self, value, conversion):
|
||||
# Ignore any conversions
|
||||
if conversion is None:
|
||||
return value
|
||||
else:
|
||||
return str(value)
|
||||
|
||||
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None):
|
||||
|
||||
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None) -> FormattedString:
|
||||
if isinstance(template, FormattedString):
|
||||
raise SuspiciousOperation("Calling format_map() on an already formatted string is likely unsafe.")
|
||||
if not isinstance(template, str):
|
||||
template = str(template)
|
||||
return SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)
|
||||
return FormattedString(
|
||||
SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)
|
||||
)
|
||||
|
||||
+2561
-2267
File diff suppressed because it is too large
Load Diff
+2735
-2270
File diff suppressed because it is too large
Load Diff
+2561
-2267
File diff suppressed because it is too large
Load Diff
+2661
-2277
File diff suppressed because it is too large
Load Diff
+2684
-2260
File diff suppressed because it is too large
Load Diff
+2581
-2267
File diff suppressed because it is too large
Load Diff
+2688
-2291
File diff suppressed because it is too large
Load Diff
+2661
-2268
File diff suppressed because it is too large
Load Diff
@@ -265,6 +265,7 @@ Objekt-IDs
|
||||
Offline-Scan
|
||||
OK
|
||||
Online-Banking
|
||||
Online-Banking-Nutzer
|
||||
Onlinebanking
|
||||
Onlinebanking-Zugangsdaten
|
||||
Open
|
||||
@@ -539,6 +540,8 @@ WeChat-Zahlung
|
||||
Weiterleitungs-URIs
|
||||
Weiterleitungs-URL
|
||||
Weiterleitungs-URLs
|
||||
WERO
|
||||
WERO-App
|
||||
WhatsApp
|
||||
Widget
|
||||
Widget-Code
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -265,6 +265,7 @@ Objekt-IDs
|
||||
Offline-Scan
|
||||
OK
|
||||
Online-Banking
|
||||
Online-Banking-Nutzer
|
||||
Onlinebanking
|
||||
Onlinebanking-Zugangsdaten
|
||||
Open
|
||||
@@ -539,6 +540,8 @@ WeChat-Zahlung
|
||||
Weiterleitungs-URIs
|
||||
Weiterleitungs-URL
|
||||
Weiterleitungs-URLs
|
||||
WERO
|
||||
WERO-App
|
||||
WhatsApp
|
||||
Widget
|
||||
Widget-Code
|
||||
|
||||
+2561
-2267
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-26 13:20+0000\n"
|
||||
"POT-Creation-Date: 2026-02-24 11:50+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"
|
||||
|
||||
+2702
-2282
File diff suppressed because it is too large
Load Diff
+2561
-2267
File diff suppressed because it is too large
Load Diff
+2682
-2270
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2563
-2267
File diff suppressed because it is too large
Load Diff
+2643
-2256
File diff suppressed because it is too large
Load Diff
+2684
-2258
File diff suppressed because it is too large
Load Diff
+2578
-2269
File diff suppressed because it is too large
Load Diff
+2687
-2272
File diff suppressed because it is too large
Load Diff
+2704
-2279
File diff suppressed because it is too large
Load Diff
+2853
-2455
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2662
-2254
File diff suppressed because it is too large
Load Diff
+2654
-2286
File diff suppressed because it is too large
Load Diff
+2716
-2259
File diff suppressed because it is too large
Load Diff
+2634
-2262
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-26 09:10+0000\n"
|
||||
"PO-Revision-Date: 2025-05-05 09:40+0000\n"
|
||||
"Last-Translator: \"Luca Martinelli [Sannita]\" <sannita@gmail.com>\n"
|
||||
"PO-Revision-Date: 2026-02-10 16:49+0000\n"
|
||||
"Last-Translator: Raffaele Doretto <ced@comune.portogruaro.ve.it>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/it/>\n"
|
||||
"Language: it\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.11.1\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -1134,6 +1134,9 @@ msgid ""
|
||||
"add yourself to the waiting list. We will then notify if seats are available "
|
||||
"again."
|
||||
msgstr ""
|
||||
"Alcune o tutte le categorie di biglietti sono attualmente esaurite. Se lo "
|
||||
"desideri, puoi aggiungerti alla lista d'attesa. Ti informeremo se i posti "
|
||||
"saranno nuovamente disponibili."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:76
|
||||
msgctxt "widget"
|
||||
|
||||
+3729
-3334
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-26 09:10+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 18:00+0000\n"
|
||||
"Last-Translator: Ryo Tagami <rtagami@airstrip.jp>\n"
|
||||
"PO-Revision-Date: 2026-02-23 10:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/ja/>\n"
|
||||
"Language: ja\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
"X-Generator: Weblate 5.16\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -27,7 +27,7 @@ msgstr "支払い済み"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
|
||||
msgid "Comment:"
|
||||
msgstr "注釈:"
|
||||
msgstr "コメント:"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
|
||||
msgid "PayPal"
|
||||
@@ -157,7 +157,7 @@ msgstr "受注件数"
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:63
|
||||
msgid "Paid orders"
|
||||
msgstr "支払い済みの注文"
|
||||
msgstr "支払い済み件数"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
@@ -167,7 +167,7 @@ msgstr "参加者 (注文済み)"
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Attendees (paid)"
|
||||
msgstr "参加者 (有料)"
|
||||
msgstr "参加者 (支払い済み)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
|
||||
msgid "Total revenue"
|
||||
@@ -215,7 +215,7 @@ msgstr "このチケットは特別な対応が必要です"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr "方向転換"
|
||||
msgstr "向きを切り替え"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
@@ -223,7 +223,7 @@ msgstr "入場"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr "退出"
|
||||
msgstr "退場"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
@@ -256,7 +256,7 @@ msgstr "承認保留中"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Redeemed"
|
||||
msgstr "使用済"
|
||||
msgstr "引き換え済み"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
|
||||
msgid "Cancel"
|
||||
@@ -297,7 +297,7 @@ msgstr "不明なチケット"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr "この種類のチケットは使用できません"
|
||||
msgstr "この種類のチケットはここでは使用できません"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Entry not allowed"
|
||||
@@ -321,7 +321,7 @@ msgstr "注文がキャンセルされました"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:66
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr "リストのチケットコードは曖昧です"
|
||||
msgstr "リスト上でチケットコードが一意に特定できません"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:67
|
||||
msgid "Order not approved"
|
||||
@@ -358,7 +358,7 @@ msgstr "閉じる"
|
||||
#: pretix/static/pretixbase/js/addressform.js:101
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:529
|
||||
msgid "required"
|
||||
msgstr "必要"
|
||||
msgstr "必須"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:13
|
||||
msgid ""
|
||||
@@ -370,8 +370,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:17
|
||||
msgid "Your request has been queued on the server and will soon be processed."
|
||||
msgstr ""
|
||||
"サーバへ送信されたリクエスト順にお応えしています。今しばらくお待ちください。"
|
||||
msgstr "お客様のリクエストはサーバーで受け付けられました。まもなく処理されます。"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:21
|
||||
msgid ""
|
||||
@@ -394,8 +393,7 @@ msgstr "{code} のエラーが発生しました。"
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
"現在サーバへの接続ができませんが、接続試行中です。エラーコード: {code}"
|
||||
msgstr "現在サーバへの接続ができませんが、接続試行中です。最新のエラーコード: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:162
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
@@ -419,9 +417,8 @@ msgid ""
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
"page and try again."
|
||||
msgstr ""
|
||||
"リクエストがサーバへ送信されました。1分以上経っても応答がない場合は、イン"
|
||||
"ターネット接続を確認してください。確認完了後、ウェブページを再度読込み、再試"
|
||||
"行してください。"
|
||||
"現在リクエストをサーバへ送信中です。1分以上経っても応答がない場合は、"
|
||||
"インターネット接続を確認し、このページを再読み込みして再試行してください。"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||
msgid "If this takes longer than a few minutes, please contact us."
|
||||
@@ -429,7 +426,7 @@ msgstr "数分以上かかる場合は、お問い合わせください。"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:331
|
||||
msgid "Close message"
|
||||
msgstr "閉じる"
|
||||
msgstr "メッセージを閉じる"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
@@ -443,7 +440,7 @@ msgstr "Ctrl-Cを押してコピー!"
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:18
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:24
|
||||
msgid "is one of"
|
||||
msgstr "の一つです"
|
||||
msgstr "次のいずれか"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:30
|
||||
msgid "is before"
|
||||
@@ -523,7 +520,7 @@ msgstr "以下のすべての条件(AND)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:183
|
||||
msgid "At least one of the conditions below (OR)"
|
||||
msgstr "以下の条件のうち、最低1つ(または)"
|
||||
msgstr "以下の条件のうち、最低1つ(OR)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:184
|
||||
msgid "Event start"
|
||||
@@ -564,16 +561,16 @@ msgstr "複製"
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:193
|
||||
msgctxt "entry_status"
|
||||
msgid "present"
|
||||
msgstr "出席"
|
||||
msgstr "入場中"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:194
|
||||
msgctxt "entry_status"
|
||||
msgid "absent"
|
||||
msgstr "欠席"
|
||||
msgstr "未入場"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:289
|
||||
msgid "Error: Product not found!"
|
||||
msgstr "エラー:製品が見つかりません!"
|
||||
msgstr "エラー:商品が見つかりません!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:296
|
||||
msgid "Error: Variation not found!"
|
||||
@@ -585,7 +582,7 @@ msgstr "チェックイン用QRコード"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:549
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "以下の理由によりPDFファイルの読み込みに失敗しました:"
|
||||
msgstr "以下の理由によりPDF背景ファイルの読み込みに失敗しました:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:904
|
||||
msgid "Group of objects"
|
||||
@@ -593,7 +590,7 @@ msgstr "オブジェクトグループ"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:909
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "テキストオブジェクト (廃止済)"
|
||||
msgstr "テキストオブジェクト(非推奨)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:911
|
||||
msgid "Text box"
|
||||
@@ -609,7 +606,7 @@ msgstr "画像エリア"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Pretixのイベントチケット売り場"
|
||||
msgstr "Powered by pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:919
|
||||
msgid "Object"
|
||||
@@ -647,24 +644,24 @@ msgstr "不明なエラー。"
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:309
|
||||
msgid "Your color has great contrast and will provide excellent accessibility."
|
||||
msgstr ""
|
||||
"あなたの色は素晴らしいコントラストを持ち、優れたアクセシビリティを提供しま"
|
||||
"す。"
|
||||
"選択した色は素晴らしいコントラストを持ち、優れたアクセシビリティを提供します"
|
||||
"。"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:313
|
||||
msgid ""
|
||||
"Your color has decent contrast and is sufficient for minimum accessibility "
|
||||
"requirements."
|
||||
msgstr ""
|
||||
"あなたの色は適切なコントラストを持ち、最小限のアクセシビリティ要件に十分で"
|
||||
"す。"
|
||||
"選択した色は適切なコントラストを持ち、最小限のアクセシビリティ要件に十分です"
|
||||
"。"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:317
|
||||
msgid ""
|
||||
"Your color has insufficient contrast to white. Accessibility of your site "
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
"あなたの色は白に対して十分なコントラストがありません。サイトのアクセシビリ"
|
||||
"ティに影響します。"
|
||||
"選択した色は白に対して十分なコントラストがありません。サイトの"
|
||||
"アクセシビリティに影響します。"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:443
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:463
|
||||
@@ -677,7 +674,7 @@ msgstr "全て"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "None"
|
||||
msgstr "ない"
|
||||
msgstr "なし"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:466
|
||||
msgid "Selected only"
|
||||
@@ -693,7 +690,7 @@ msgstr "無効なページ番号。"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1000
|
||||
msgid "Use a different name internally"
|
||||
msgstr "内部で別の名前を使用してください"
|
||||
msgstr "内部で別の名前を使用する"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:1040
|
||||
msgid "Click to close"
|
||||
@@ -722,15 +719,15 @@ msgstr "カウント"
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:112
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
msgstr[0] "({num} 他の日程)"
|
||||
msgstr[0] "(他に{num}件の日程)"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:47
|
||||
msgid ""
|
||||
"The items in your cart are no longer reserved for you. You can still "
|
||||
"complete your order as long as they’re available."
|
||||
msgstr ""
|
||||
"カートに入っている商品は現在売り切れです。在庫があれば、このまま注文を完了す"
|
||||
"ることができます。"
|
||||
"カート内のアイテムの予約が解除されました。在庫がある限り、引き続き注文を完了"
|
||||
"できます。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:49
|
||||
msgid "Cart expired"
|
||||
@@ -744,27 +741,27 @@ msgstr "カートの有効期限が近づいています。"
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:62
|
||||
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] "カート内の商品の予約は {num} 分以内に完了します。"
|
||||
msgstr[0] "カート内の商品はあと {num} 分間確保されています。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:83
|
||||
msgid "Your cart has expired."
|
||||
msgstr "カートの保存期限が切れています。"
|
||||
msgstr "カートの有効期限が切れています。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:86
|
||||
msgid ""
|
||||
"The items in your cart are no longer reserved for you. You can still "
|
||||
"complete your order as long as they're available."
|
||||
msgstr ""
|
||||
"カート内の商品の予約期限が切れました。在庫があれば、このまま注文を完了するこ"
|
||||
"カート内の商品の確保期限が切れました。在庫があれば、このまま注文を完了するこ"
|
||||
"とができます。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:87
|
||||
msgid "Do you want to renew the reservation period?"
|
||||
msgstr "予約の期間を更新しますか?"
|
||||
msgstr "確保期間を更新しますか?"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:90
|
||||
msgid "Renew reservation"
|
||||
msgstr "予約を更新"
|
||||
msgstr "確保を更新"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:194
|
||||
msgid "The organizer keeps %(currency)s %(amount)s"
|
||||
@@ -780,7 +777,7 @@ msgstr "主催者が留保する料金を入力してください。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:577
|
||||
msgid "Your local time:"
|
||||
msgstr "現地時間:"
|
||||
msgstr "お使いの地域の時刻:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/walletdetection.js:39
|
||||
msgid "Google Pay"
|
||||
@@ -863,7 +860,7 @@ msgstr "登録"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||
msgctxt "widget"
|
||||
msgid "Reserved"
|
||||
msgstr "予約完了"
|
||||
msgstr "予約済み"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||
msgctxt "widget"
|
||||
@@ -879,7 +876,7 @@ msgstr "%(currency)s %(price)sから"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr "%sのイメージ"
|
||||
msgstr "%sの画像"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
@@ -889,7 +886,7 @@ msgstr "%(rate)s% %(taxname)s込"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid "plus %(rate)s% %(taxname)s"
|
||||
msgstr "%(rate)s% %(taxname)s抜"
|
||||
msgstr "別途%(rate)s% %(taxname)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
@@ -899,13 +896,13 @@ msgstr "税込"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:37
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr "税抜"
|
||||
msgstr "税別"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "currently available: %s"
|
||||
msgstr "現在%s使用可能"
|
||||
msgstr "現在の残数: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
@@ -921,7 +918,7 @@ msgstr "提供開始前"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Not available anymore"
|
||||
msgstr "今後の提供不可"
|
||||
msgstr "提供終了"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
@@ -937,7 +934,7 @@ msgstr "最小注文数量:%s"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr "チケットショップ閉店"
|
||||
msgstr "チケットショップを閉じる"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
@@ -950,8 +947,8 @@ msgid ""
|
||||
"There are currently a lot of users in this ticket shop. Please open the shop "
|
||||
"in a new tab to continue."
|
||||
msgstr ""
|
||||
"現在チケットショップが混み合っています。新しいタブでチケットショップを開き続"
|
||||
"行してください。"
|
||||
"現在チケットショップが混み合っています。新しいタブでチケットショップを開いて"
|
||||
"続行してください。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgctxt "widget"
|
||||
@@ -975,7 +972,7 @@ msgid ""
|
||||
"this ticket shop. Please click \"Continue\" to retry in a new tab."
|
||||
msgstr ""
|
||||
"現在チケットショップが混雑しているため、お客様のカートを作ることができません"
|
||||
"でした。新しいタブを開き「次へ」をクリックしてください。"
|
||||
"でした。\"続ける\"をクリックして、新しいタブで再試行してください。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgctxt "widget"
|
||||
@@ -988,8 +985,8 @@ msgid ""
|
||||
"You currently have an active cart for this event. If you select more "
|
||||
"products, they will be added to your existing cart."
|
||||
msgstr ""
|
||||
"お客様のカートはイベントの申し込みに有効です。商品を選択し、カートへ追加して"
|
||||
"ください。"
|
||||
"このイベントのカートに商品が入っています。商品を追加すると、既存のカートに追"
|
||||
"加されます。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:57
|
||||
msgctxt "widget"
|
||||
@@ -1079,7 +1076,7 @@ msgstr "前週"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:74
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr "座席一覧を開く"
|
||||
msgstr "座席選択を開く"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:75
|
||||
msgctxt "widget"
|
||||
|
||||
+2643
-2260
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-26 09:10+0000\n"
|
||||
"PO-Revision-Date: 2025-11-18 17:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"PO-Revision-Date: 2026-02-01 21:00+0000\n"
|
||||
"Last-Translator: z3rrry <z3rrry@gmail.com>\n"
|
||||
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"ko/>\n"
|
||||
"Language: ko\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Weblate 5.14.3\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -175,12 +175,12 @@ msgstr "유료 주문"
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr ""
|
||||
msgstr "참가자 (정렬된)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Attendees (paid)"
|
||||
msgstr ""
|
||||
msgstr "참가자 (결제된)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:51
|
||||
msgid "Total revenue"
|
||||
|
||||
+2648
-2276
File diff suppressed because it is too large
Load Diff
+2563
-2267
File diff suppressed because it is too large
Load Diff
+2648
-2260
File diff suppressed because it is too large
Load Diff
+2561
-2267
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+3385
-2984
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-26 09:10+0000\n"
|
||||
"PO-Revision-Date: 2026-01-28 00:00+0000\n"
|
||||
"PO-Revision-Date: 2026-02-19 22:00+0000\n"
|
||||
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"nl/>\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.15.2\n"
|
||||
"X-Generator: Weblate 5.16\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -55,7 +55,7 @@ msgstr "Kredietkaart"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40
|
||||
msgid "PayPal Pay Later"
|
||||
msgstr "PayPal Pay Later"
|
||||
msgstr "PayPal - Later betalen"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL"
|
||||
@@ -186,15 +186,15 @@ msgstr "Verbinding maken met uw bank …"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr "Kies een inchecklijst"
|
||||
msgstr "Kies een check-in-lijst"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr "Geen actieve check-inlijsten gevonden."
|
||||
msgstr "Geen actieve check-in-lijsten gevonden."
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr "Andere inchecklijst kiezen"
|
||||
msgstr "Andere check-in-lijst kiezen"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
@@ -251,7 +251,7 @@ msgstr "Bevestigd"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Approval pending"
|
||||
msgstr "Goedkeuring in afwachting"
|
||||
msgstr "Goedkeuring in behandeling"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Redeemed"
|
||||
@@ -336,7 +336,7 @@ msgstr "Geldige tickets"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:70
|
||||
msgid "Currently inside"
|
||||
msgstr "Op dit moment binnen"
|
||||
msgstr "Nu binnen"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:71
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
@@ -406,8 +406,8 @@ msgstr "De aanvraag duurde te lang. Probeer het opnieuw."
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
"De server is op dit moment niet bereikbaar. Probeer het alstublieft opnieuw. "
|
||||
"Foutcode: {code}"
|
||||
"De server is op dit moment niet bereikbaar. Probeer het opnieuw. Foutcode: "
|
||||
"{code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:216
|
||||
msgid "We are processing your request …"
|
||||
@@ -602,11 +602,11 @@ msgstr "Tekstvak"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:913
|
||||
msgid "Barcode area"
|
||||
msgstr "Barcode gebied"
|
||||
msgstr "Ruimte voor streepjescode"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:915
|
||||
msgid "Image area"
|
||||
msgstr "Afbeeldingsgebied"
|
||||
msgstr "Ruimte voor afbeelding"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:917
|
||||
msgid "Powered by pretix"
|
||||
@@ -664,8 +664,8 @@ msgid ""
|
||||
"Your color has insufficient contrast to white. Accessibility of your site "
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
"Uw kleur heeft te weinig contrast met wit. De toegankelijkheid van uw site "
|
||||
"wordt negatief beïnvloed."
|
||||
"Uw kleur heeft onvoldoende contrast met wit. Dit heeft invloed op de "
|
||||
"toegankelijkheid van uw website."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:443
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:463
|
||||
@@ -674,7 +674,7 @@ msgstr "Zoekopdracht"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:461
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
msgstr "Allemaal"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:462
|
||||
msgid "None"
|
||||
@@ -736,7 +736,7 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:49
|
||||
msgid "Cart expired"
|
||||
msgstr "Winkelwagen is verlopen"
|
||||
msgstr "Winkelwagen verlopen"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:58
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:84
|
||||
@@ -936,12 +936,12 @@ msgstr "Momenteel niet beschikbaar"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "minimum amount to order: %s"
|
||||
msgstr "minimale hoeveelheid om te bestellen: %s"
|
||||
msgstr "minimale bestelhoeveelheid: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr "Sluit ticketverkoop"
|
||||
msgstr "Sluit ticketwinkel"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
@@ -1160,51 +1160,51 @@ msgstr "zondag"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:94
|
||||
msgid "January"
|
||||
msgstr "Januari"
|
||||
msgstr "januari"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:95
|
||||
msgid "February"
|
||||
msgstr "Februari"
|
||||
msgstr "februari"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:96
|
||||
msgid "March"
|
||||
msgstr "Maart"
|
||||
msgstr "maart"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:97
|
||||
msgid "April"
|
||||
msgstr "April"
|
||||
msgstr "april"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:98
|
||||
msgid "May"
|
||||
msgstr "Mei"
|
||||
msgstr "mei"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:99
|
||||
msgid "June"
|
||||
msgstr "Juni"
|
||||
msgstr "juni"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:100
|
||||
msgid "July"
|
||||
msgstr "Juli"
|
||||
msgstr "juli"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:101
|
||||
msgid "August"
|
||||
msgstr "Augustus"
|
||||
msgstr "augustus"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:102
|
||||
msgid "September"
|
||||
msgstr "September"
|
||||
msgstr "september"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:103
|
||||
msgid "October"
|
||||
msgstr "Oktober"
|
||||
msgstr "oktober"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:104
|
||||
msgid "November"
|
||||
msgstr "November"
|
||||
msgstr "november"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:105
|
||||
msgid "December"
|
||||
msgstr "December"
|
||||
msgstr "december"
|
||||
|
||||
#~ msgid "Time zone:"
|
||||
#~ msgstr "Tijdzone:"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2727
-2261
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2580
-2247
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2732
-2273
File diff suppressed because it is too large
Load Diff
+2648
-2265
File diff suppressed because it is too large
Load Diff
+2551
-2244
File diff suppressed because it is too large
Load Diff
+2704
-2258
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user