forked from CGM_Public/pretix_original
Compare commits
1 Commits
migration-
...
hide-zero-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
784b588aa8 |
@@ -100,7 +100,7 @@ dependencies = [
|
||||
"ua-parser==1.0.*",
|
||||
"vat_moss_forked==2020.3.20.0.11.0",
|
||||
"vobject==0.9.*",
|
||||
"webauthn==2.5.*",
|
||||
"webauthn==2.4.*",
|
||||
"zeep==4.3.*"
|
||||
]
|
||||
|
||||
|
||||
@@ -163,12 +163,6 @@ EXTRA_LANG_INFO = {
|
||||
'name': 'Portuguese',
|
||||
'name_local': 'Português',
|
||||
},
|
||||
'nb-no': {
|
||||
'bidi': False,
|
||||
'code': 'nb-no',
|
||||
'name': 'Norwegian Bokmal',
|
||||
'name_local': 'norsk (bokmål)',
|
||||
},
|
||||
}
|
||||
|
||||
django.conf.locale.LANG_INFO.update(EXTRA_LANG_INFO)
|
||||
|
||||
@@ -1059,10 +1059,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# If an individual or company address is acceptable, #id_is_business_0 == individual, _1 == company.
|
||||
# However, if only company addresses are acceptable, #id_is_business_0 == company and is the only choice
|
||||
self.fields["company"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_{int(not self.company_required)}'
|
||||
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_{int(not self.company_required)}'
|
||||
self.fields["company"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_1'
|
||||
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_1'
|
||||
|
||||
if not self.ask_vat_id:
|
||||
del self.fields['vat_id']
|
||||
|
||||
@@ -388,15 +388,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
except:
|
||||
logger.exception("Can not resize image")
|
||||
pass
|
||||
try:
|
||||
# Valid ZUGFeRD invoices must be compliant with PDF/A-3. pretix-zugferd ensures this by passing them
|
||||
# through ghost script. Unfortunately, if the logo contains transparency, this will still fail.
|
||||
# I was unable to figure out a way to fix this in GhostScript, so the easy fix is to remove the
|
||||
# transparency, as our invoices always have a white background anyways.
|
||||
ir.remove_transparency()
|
||||
except:
|
||||
logger.exception("Can not remove transparency from logo")
|
||||
pass
|
||||
canvas.drawImage(ir,
|
||||
self.logo_left,
|
||||
self.pagesize[1] - self.logo_height - self.logo_top,
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from collections import defaultdict
|
||||
from typing import Optional
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.signals import EventPluginRegistry
|
||||
|
||||
|
||||
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
|
||||
if a_map:
|
||||
if 'href' not in a_map:
|
||||
a_map['val'] = format_html('<i>{val}</i>', **a_map)
|
||||
elif is_active:
|
||||
a_map['val'] = format_html('<a href="{href}">{val}</a>', **a_map)
|
||||
elif event and plugin_name:
|
||||
a_map['val'] = format_html(
|
||||
'<i>{val}</i> <a href="{plugin_href}">'
|
||||
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>',
|
||||
**a_map,
|
||||
errmes=_("The relevant plugin is currently not active. To activate it, click here to go to the plugin settings."),
|
||||
plugin_href=reverse('control:event.settings.plugins', kwargs={
|
||||
'organizer': event.organizer.slug,
|
||||
'event': event.slug,
|
||||
}) + '#plugin_' + plugin_name,
|
||||
)
|
||||
else:
|
||||
a_map['val'] = format_html(
|
||||
'<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>',
|
||||
**a_map,
|
||||
errmes=_("The relevant plugin is currently not active."),
|
||||
)
|
||||
return format_html(wrapper, **a_map)
|
||||
|
||||
|
||||
class LogEntryTypeRegistry(EventPluginRegistry):
|
||||
def __init__(self):
|
||||
super().__init__({'action_type': lambda o: getattr(o, 'action_type')})
|
||||
|
||||
def register(self, *objs):
|
||||
for obj in objs:
|
||||
if not isinstance(obj, LogEntryType):
|
||||
raise TypeError('Entries must be derived from LogEntryType')
|
||||
|
||||
if obj.__module__.startswith('pretix.base.'):
|
||||
raise TypeError('Must not register base classes, only derived ones')
|
||||
|
||||
return super().register(*objs)
|
||||
|
||||
def new_from_dict(self, data):
|
||||
"""
|
||||
Register multiple instance of a `LogEntryType` class with different `action_type`
|
||||
and plain text strings, as given by the items of the specified data dictionary.
|
||||
|
||||
This method is designed to be used as a decorator as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.item.added': _('The product has been created.'),
|
||||
'pretix.event.item.changed': _('The product has been changed.'),
|
||||
# ...
|
||||
})
|
||||
class CoreItemLogEntryType(ItemLogEntryType):
|
||||
# ...
|
||||
|
||||
:param data: action types and descriptions
|
||||
``{"some_action_type": "Plain text description", ...}``
|
||||
"""
|
||||
def reg(clz):
|
||||
for action_type, plain in data.items():
|
||||
self.register(clz(action_type=action_type, plain=plain))
|
||||
return clz
|
||||
return reg
|
||||
|
||||
|
||||
"""
|
||||
Registry for LogEntry types.
|
||||
|
||||
Each entry in this registry should be an instance of a subclass of ``LogEntryType``.
|
||||
They are annotated with their ``action_type`` and the defining ``plugin``.
|
||||
"""
|
||||
log_entry_types = LogEntryTypeRegistry()
|
||||
|
||||
|
||||
class LogEntryType:
|
||||
"""
|
||||
Base class for a type of LogEntry, identified by its action_type.
|
||||
"""
|
||||
|
||||
def __init__(self, action_type=None, plain=None):
|
||||
if action_type:
|
||||
self.action_type = action_type
|
||||
if plain:
|
||||
self.plain = plain
|
||||
|
||||
def display(self, logentry, data):
|
||||
"""
|
||||
Returns the message to be displayed for a given logentry of this type.
|
||||
|
||||
:return: `str` or `LazyI18nString`
|
||||
"""
|
||||
if hasattr(self, 'plain'):
|
||||
plain = str(self.plain)
|
||||
if '{' in plain:
|
||||
data = defaultdict(lambda: '?', data)
|
||||
return plain.format_map(data)
|
||||
else:
|
||||
return plain
|
||||
|
||||
def get_object_link_info(self, logentry) -> Optional[dict]:
|
||||
"""
|
||||
Return information to generate a link to the `content_object` of a given log entry.
|
||||
|
||||
Not implemented in the base class, causing the object link to be omitted.
|
||||
|
||||
:return: Dictionary with the keys ``href`` (URL to view/edit the object) and
|
||||
``val`` (text for the anchor element)
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_object_link(self, logentry):
|
||||
a_map = self.get_object_link_info(logentry)
|
||||
return make_link(a_map, self.object_link_wrapper)
|
||||
|
||||
object_link_wrapper = '{val}'
|
||||
|
||||
def shred_pii(self, logentry):
|
||||
"""
|
||||
To be used for shredding personally identified information contained in the data field of a LogEntry of this
|
||||
type.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class NoOpShredderMixin:
|
||||
def shred_pii(self, logentry):
|
||||
pass
|
||||
|
||||
|
||||
class ClearDataShredderMixin:
|
||||
def shred_pii(self, logentry):
|
||||
logentry.data = None
|
||||
@@ -19,20 +19,137 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import Optional
|
||||
from collections import defaultdict
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.models import (
|
||||
Discount, Item, ItemCategory, Order, Question, Quota, SubEvent, TaxRule,
|
||||
Voucher,
|
||||
)
|
||||
from pretix.base.signals import EventPluginRegistry
|
||||
|
||||
from .logentrytype_registry import ( # noqa
|
||||
ClearDataShredderMixin, LogEntryType, NoOpShredderMixin, log_entry_types,
|
||||
make_link, LogEntryTypeRegistry,
|
||||
)
|
||||
|
||||
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
|
||||
if a_map:
|
||||
if is_active:
|
||||
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
|
||||
elif event and plugin_name:
|
||||
a_map['val'] = (
|
||||
'<i>{val}</i> <a href="{plugin_href}">'
|
||||
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>'
|
||||
).format_map({
|
||||
**a_map,
|
||||
"errmes": _("The relevant plugin is currently not active. To activate it, click here to go to the plugin settings."),
|
||||
"plugin_href": reverse('control:event.settings.plugins', kwargs={
|
||||
'organizer': event.organizer.slug,
|
||||
'event': event.slug,
|
||||
}) + '#plugin_' + plugin_name,
|
||||
})
|
||||
else:
|
||||
a_map['val'] = '<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>'.format_map({
|
||||
**a_map,
|
||||
"errmes": _("The relevant plugin is currently not active."),
|
||||
})
|
||||
return wrapper.format_map(a_map)
|
||||
|
||||
|
||||
class LogEntryTypeRegistry(EventPluginRegistry):
|
||||
def __init__(self):
|
||||
super().__init__({'action_type': lambda o: getattr(o, 'action_type')})
|
||||
|
||||
def register(self, *objs):
|
||||
for obj in objs:
|
||||
if not isinstance(obj, LogEntryType):
|
||||
raise TypeError('Entries must be derived from LogEntryType')
|
||||
|
||||
if obj.__module__ == LogEntryType.__module__:
|
||||
raise TypeError('Must not register base classes, only derived ones')
|
||||
|
||||
return super().register(*objs)
|
||||
|
||||
def new_from_dict(self, data):
|
||||
"""
|
||||
Register multiple instance of a `LogEntryType` class with different `action_type`
|
||||
and plain text strings, as given by the items of the specified data dictionary.
|
||||
|
||||
This method is designed to be used as a decorator as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.item.added': _('The product has been created.'),
|
||||
'pretix.event.item.changed': _('The product has been changed.'),
|
||||
# ...
|
||||
})
|
||||
class CoreItemLogEntryType(ItemLogEntryType):
|
||||
# ...
|
||||
|
||||
:param data: action types and descriptions
|
||||
``{"some_action_type": "Plain text description", ...}``
|
||||
"""
|
||||
def reg(clz):
|
||||
for action_type, plain in data.items():
|
||||
self.register(clz(action_type=action_type, plain=plain))
|
||||
return clz
|
||||
return reg
|
||||
|
||||
|
||||
"""
|
||||
Registry for LogEntry types.
|
||||
|
||||
Each entry in this registry should be an instance of a subclass of ``LogEntryType``.
|
||||
They are annotated with their ``action_type`` and the defining ``plugin``.
|
||||
"""
|
||||
log_entry_types = LogEntryTypeRegistry()
|
||||
|
||||
|
||||
class LogEntryType:
|
||||
"""
|
||||
Base class for a type of LogEntry, identified by its action_type.
|
||||
"""
|
||||
|
||||
def __init__(self, action_type=None, plain=None):
|
||||
if action_type:
|
||||
self.action_type = action_type
|
||||
if plain:
|
||||
self.plain = plain
|
||||
|
||||
def display(self, logentry):
|
||||
"""
|
||||
Returns the message to be displayed for a given logentry of this type.
|
||||
|
||||
:return: `str` or `LazyI18nString`
|
||||
"""
|
||||
if hasattr(self, 'plain'):
|
||||
plain = str(self.plain)
|
||||
if '{' in plain:
|
||||
data = defaultdict(lambda: '?', logentry.parsed_data)
|
||||
return plain.format_map(data)
|
||||
else:
|
||||
return plain
|
||||
|
||||
def get_object_link_info(self, logentry) -> dict:
|
||||
"""
|
||||
Return information to generate a link to the `content_object` of a given log entry.
|
||||
|
||||
Not implemented in the base class, causing the object link to be omitted.
|
||||
|
||||
:return: Dictionary with the keys ``href`` (containing a URL to view/edit the object) and ``val`` (containing the
|
||||
escaped text for the anchor element)
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_object_link(self, logentry):
|
||||
a_map = self.get_object_link_info(logentry)
|
||||
return make_link(a_map, self.object_link_wrapper)
|
||||
|
||||
object_link_wrapper = '{val}'
|
||||
|
||||
def shred_pii(self, logentry):
|
||||
"""
|
||||
To be used for shredding personally identified information contained in the data field of a LogEntry of this
|
||||
type.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class EventLogEntryType(LogEntryType):
|
||||
@@ -40,27 +157,15 @@ class EventLogEntryType(LogEntryType):
|
||||
Base class for any `LogEntry` type whose `content_object` is either an `Event` itself or belongs to a specific `Event`.
|
||||
"""
|
||||
|
||||
def get_object_link_info(self, logentry) -> Optional[dict]:
|
||||
if hasattr(self, 'object_link_viewname'):
|
||||
content = logentry.content_object
|
||||
if not content:
|
||||
if logentry.content_type_id:
|
||||
return {
|
||||
'val': _('(deleted)'),
|
||||
}
|
||||
else:
|
||||
return
|
||||
|
||||
if hasattr(self, 'content_type') and not isinstance(content, self.content_type):
|
||||
return
|
||||
|
||||
def get_object_link_info(self, logentry) -> dict:
|
||||
if hasattr(self, 'object_link_viewname') and logentry.content_object:
|
||||
return {
|
||||
'href': reverse(self.object_link_viewname, kwargs={
|
||||
'event': logentry.event.slug,
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
**self.object_link_args(content),
|
||||
**self.object_link_args(logentry.content_object),
|
||||
}),
|
||||
'val': self.object_link_display_name(logentry.content_object),
|
||||
'val': escape(self.object_link_display_name(logentry.content_object)),
|
||||
}
|
||||
|
||||
def object_link_args(self, content_object):
|
||||
@@ -77,7 +182,6 @@ class EventLogEntryType(LogEntryType):
|
||||
class OrderLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Order {val}')
|
||||
object_link_viewname = 'control:event.order'
|
||||
content_type = Order
|
||||
|
||||
def object_link_args(self, order):
|
||||
return {'code': order.code}
|
||||
@@ -90,7 +194,6 @@ class VoucherLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Voucher {val}…')
|
||||
object_link_viewname = 'control:event.voucher'
|
||||
object_link_argname = 'voucher'
|
||||
content_type = Voucher
|
||||
|
||||
def object_link_display_name(self, voucher):
|
||||
if len(voucher.code) > 6:
|
||||
@@ -102,46 +205,49 @@ class ItemLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Product {val}')
|
||||
object_link_viewname = 'control:event.item'
|
||||
object_link_argname = 'item'
|
||||
content_type = Item
|
||||
|
||||
|
||||
class SubEventLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = pgettext_lazy('subevent', 'Date {val}')
|
||||
object_link_viewname = 'control:event.subevent'
|
||||
object_link_argname = 'subevent'
|
||||
content_type = SubEvent
|
||||
|
||||
|
||||
class QuotaLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Quota {val}')
|
||||
object_link_viewname = 'control:event.items.quotas.show'
|
||||
object_link_argname = 'quota'
|
||||
content_type = Quota
|
||||
|
||||
|
||||
class DiscountLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Discount {val}')
|
||||
object_link_viewname = 'control:event.items.discounts.edit'
|
||||
object_link_argname = 'discount'
|
||||
content_type = Discount
|
||||
|
||||
|
||||
class ItemCategoryLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Category {val}')
|
||||
object_link_viewname = 'control:event.items.categories.edit'
|
||||
object_link_argname = 'category'
|
||||
content_type = ItemCategory
|
||||
|
||||
|
||||
class QuestionLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Question {val}')
|
||||
object_link_viewname = 'control:event.items.questions.show'
|
||||
object_link_argname = 'question'
|
||||
content_type = Question
|
||||
|
||||
|
||||
class TaxRuleLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Tax rule {val}')
|
||||
object_link_viewname = 'control:event.settings.tax.edit'
|
||||
object_link_argname = 'rule'
|
||||
content_type = TaxRule
|
||||
|
||||
|
||||
class NoOpShredderMixin:
|
||||
def shred_pii(self, logentry):
|
||||
pass
|
||||
|
||||
|
||||
class ClearDataShredderMixin:
|
||||
def shred_pii(self, logentry):
|
||||
logentry.data = None
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.16 on 2025-01-23 11:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0275_alter_question_valid_number_max_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="item",
|
||||
name="hidden_if_item_available_mode",
|
||||
field=models.CharField(default="hide", max_length=16, null=True),
|
||||
),
|
||||
]
|
||||
@@ -40,7 +40,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from pretix.base.logentrytype_registry import log_entry_types, make_link
|
||||
from pretix.base.logentrytypes import log_entry_types, make_link
|
||||
from pretix.base.signals import is_app_active, logentry_object_link
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class LogEntry(models.Model):
|
||||
def display(self):
|
||||
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
|
||||
if log_entry_type:
|
||||
return log_entry_type.display(self, self.parsed_data)
|
||||
return log_entry_type.display(self)
|
||||
|
||||
from ..signals import logentry_display
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ class Order(LockModel, LoggedModel):
|
||||
|
||||
if not self.testmode:
|
||||
raise TypeError("Only test mode orders can be deleted.")
|
||||
self.log_action(
|
||||
self.event.log_action(
|
||||
'pretix.event.order.deleted', user=user, auth=auth,
|
||||
data={
|
||||
'code': self.code,
|
||||
@@ -2281,9 +2281,9 @@ class OrderFee(models.Model):
|
||||
FEE_TYPE_OTHER = "other"
|
||||
FEE_TYPE_GIFTCARD = "giftcard"
|
||||
FEE_TYPES = (
|
||||
(FEE_TYPE_SERVICE, _("Service fee")),
|
||||
(FEE_TYPE_PAYMENT, _("Payment fee")),
|
||||
(FEE_TYPE_SHIPPING, _("Shipping fee")),
|
||||
(FEE_TYPE_SERVICE, _("Service fee")),
|
||||
(FEE_TYPE_CANCELLATION, _("Cancellation fee")),
|
||||
(FEE_TYPE_INSURANCE, _("Insurance fee")),
|
||||
(FEE_TYPE_LATE, _("Late fee")),
|
||||
@@ -3218,12 +3218,6 @@ class CartPosition(AbstractPosition):
|
||||
self.tax_code = line_price.code
|
||||
self.save(update_fields=['line_price_gross', 'tax_rate'])
|
||||
|
||||
@property
|
||||
def discount_percentage(self):
|
||||
if not self.line_price_gross:
|
||||
return 0
|
||||
return (self.line_price_gross - self.price) / self.line_price_gross * 100
|
||||
|
||||
@property
|
||||
def addons_without_bundled(self):
|
||||
addons = [op for op in self.addons.all() if not op.is_bundled]
|
||||
|
||||
@@ -495,18 +495,12 @@ def build_preview_invoice_pdf(event):
|
||||
invoice=invoice, description=_("Sample product {}").format(i + 1),
|
||||
gross_value=tax.gross, tax_value=tax.tax,
|
||||
tax_rate=tax.rate, tax_name=tax.name, tax_code=tax.code,
|
||||
event_date_from=event.date_from,
|
||||
event_date_to=event.date_to,
|
||||
event_location=event.settings.invoice_event_location,
|
||||
)
|
||||
else:
|
||||
for i in range(5):
|
||||
InvoiceLine.objects.create(
|
||||
invoice=invoice, description=_("Sample product A"),
|
||||
gross_value=100, tax_value=0, tax_rate=0, tax_code=None,
|
||||
event_date_from=event.date_from,
|
||||
event_date_to=event.date_to,
|
||||
event_location=event.settings.invoice_event_location,
|
||||
)
|
||||
|
||||
return event.invoice_renderer.generate(invoice)
|
||||
|
||||
@@ -231,7 +231,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
||||
raise ValidationError({
|
||||
'timezone': _('Your default locale must be specified.')
|
||||
})
|
||||
if not data.get("no_taxes") and data.get("tax_rate") is None:
|
||||
if not data.get("no_taxes") and not data.get("tax_rate"):
|
||||
raise ValidationError({
|
||||
'tax_rate': _('You have not specified a tax rate. If you do not want us to compute sales taxes, please '
|
||||
'check "{field}" above.').format(field=self.fields["no_taxes"].label)
|
||||
|
||||
@@ -539,8 +539,6 @@ class ItemCreateForm(I18nModelForm):
|
||||
v.pk = None
|
||||
v.item = instance
|
||||
v.save()
|
||||
if not variation.all_sales_channels:
|
||||
v.limit_sales_channels.set(variation.limit_sales_channels.all())
|
||||
for mv in variation.meta_values.all():
|
||||
mv.pk = None
|
||||
mv.variation = v
|
||||
|
||||
@@ -612,13 +612,7 @@ class OrderFeeChangeForm(forms.Form):
|
||||
|
||||
|
||||
class OrderFeeAddForm(forms.Form):
|
||||
fee_type = forms.ChoiceField(
|
||||
choices=[("", ""), *OrderFee.FEE_TYPES],
|
||||
help_text=_(
|
||||
"Note that payment fees have a special semantic and might automatically be changed if the "
|
||||
"payment method of the order is changed."
|
||||
)
|
||||
)
|
||||
fee_type = forms.ChoiceField(choices=OrderFee.FEE_TYPES)
|
||||
value = forms.DecimalField(
|
||||
max_digits=13, decimal_places=2,
|
||||
localize=True,
|
||||
|
||||
@@ -33,16 +33,16 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
import bleach
|
||||
import dateutil.parser
|
||||
from django.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape, format_html
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from i18nfield.strings import LazyI18nString
|
||||
@@ -68,212 +68,128 @@ OVERVIEW_BANLIST = [
|
||||
]
|
||||
|
||||
|
||||
class OrderChangeLogEntryType(OrderLogEntryType):
|
||||
prefix = _('The order has been changed:')
|
||||
def _display_order_changed(event: Event, logentry: LogEntry):
|
||||
data = json.loads(logentry.data)
|
||||
|
||||
def display(self, logentry, data):
|
||||
return self.prefix + ' ' + self.display_prefixed(logentry.event, logentry, data)
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return super().display(logentry, data)
|
||||
|
||||
|
||||
class OrderPositionChangeLogEntryType(OrderChangeLogEntryType):
|
||||
prefix = _('The order has been changed:')
|
||||
|
||||
def display(self, logentry, data):
|
||||
return super().display(logentry, {**data, 'posid': data.get('positionid', '?')})
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderItemChanged(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.item'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
text = _('The order has been changed:')
|
||||
if logentry.action_type == 'pretix.event.order.changed.item':
|
||||
old_item = str(event.items.get(pk=data['old_item']))
|
||||
if data['old_variation']:
|
||||
old_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['old_variation']))
|
||||
new_item = str(event.items.get(pk=data['new_item']))
|
||||
if data['new_variation']:
|
||||
new_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['new_variation']))
|
||||
return _('Position #{posid}: {old_item} ({old_price}) changed to {new_item} ({new_price}).').format(
|
||||
return text + ' ' + _('Position #{posid}: {old_item} ({old_price}) changed '
|
||||
'to {new_item} ({new_price}).').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_item=old_item, new_item=new_item,
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderMembershipChanged(OrderPositionChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.membership'
|
||||
plain = _('Position #{posid}: Used membership changed.')
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderSeatChanged(OrderPositionChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.seat'
|
||||
plain = _('Position #{posid}: Seat "{old_seat}" changed to "{new_seat}".')
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderSubeventChanged(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.subevent'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
elif logentry.action_type == 'pretix.event.order.changed.membership':
|
||||
return text + ' ' + _('Position #{posid}: Used membership changed.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.seat':
|
||||
return text + ' ' + _('Position #{posid}: Seat "{old_seat}" changed '
|
||||
'to "{new_seat}".').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_seat=data.get('old_seat'), new_seat=data.get('new_seat'),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.subevent':
|
||||
old_se = str(event.subevents.get(pk=data['old_subevent']))
|
||||
new_se = str(event.subevents.get(pk=data['new_subevent']))
|
||||
return _('Position #{posid}: Event date "{old_event}" ({old_price}) changed '
|
||||
'to "{new_event}" ({new_price}).').format(
|
||||
return text + ' ' + _('Position #{posid}: Event date "{old_event}" ({old_price}) changed '
|
||||
'to "{new_event}" ({new_price}).').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_event=old_se, new_event=new_se,
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderPriceChanged(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.price'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return _('Price of position #{posid} changed from {old_price} to {new_price}.').format(
|
||||
elif logentry.action_type == 'pretix.event.order.changed.price':
|
||||
return text + ' ' + _('Price of position #{posid} changed from {old_price} '
|
||||
'to {new_price}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderTaxRuleChanged(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.tax_rule'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
elif logentry.action_type == 'pretix.event.order.changed.tax_rule':
|
||||
if 'positionid' in data:
|
||||
return _('Tax rule of position #{posid} changed from {old_rule} to {new_rule}.').format(
|
||||
return text + ' ' + _('Tax rule of position #{posid} changed from {old_rule} '
|
||||
'to {new_rule}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
||||
)
|
||||
elif 'fee' in data:
|
||||
return _('Tax rule of fee #{fee} changed from {old_rule} to {new_rule}.').format(
|
||||
return text + ' ' + _('Tax rule of fee #{fee} changed from {old_rule} '
|
||||
'to {new_rule}.').format(
|
||||
fee=data.get('fee', '?'),
|
||||
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderFeeAdded(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.addfee'
|
||||
plain = _('A fee has been added')
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderFeeChanged(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.feevalue'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return _('A fee was changed from {old_price} to {new_price}.').format(
|
||||
elif logentry.action_type == 'pretix.event.order.changed.addfee':
|
||||
return text + ' ' + str(_('A fee has been added'))
|
||||
elif logentry.action_type == 'pretix.event.order.changed.feevalue':
|
||||
return text + ' ' + _('A fee was changed from {old_price} to {new_price}.').format(
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderFeeRemoved(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.cancelfee'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return _('A fee of {old_price} was removed.').format(
|
||||
elif logentry.action_type == 'pretix.event.order.changed.cancelfee':
|
||||
return text + ' ' + _('A fee of {old_price} was removed.').format(
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderCanceled(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.cancel'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
elif logentry.action_type == 'pretix.event.order.changed.cancel':
|
||||
old_item = str(event.items.get(pk=data['old_item']))
|
||||
if data['old_variation']:
|
||||
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
||||
return _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
|
||||
return text + ' ' + _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_item=old_item,
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderPositionAdded(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.add'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
elif logentry.action_type == 'pretix.event.order.changed.add':
|
||||
item = str(event.items.get(pk=data['item']))
|
||||
if data['variation']:
|
||||
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
|
||||
if data['addon_to']:
|
||||
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
|
||||
return _('Position #{posid} created: {item} ({price}) as an add-on to position #{addon_to}.').format(
|
||||
return text + ' ' + _('Position #{posid} created: {item} ({price}) as an add-on to '
|
||||
'position #{addon_to}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
item=item, addon_to=addon_to.positionid,
|
||||
price=money_filter(Decimal(data['price']), event.currency),
|
||||
)
|
||||
else:
|
||||
return _('Position #{posid} created: {item} ({price}).').format(
|
||||
return text + ' ' + _('Position #{posid} created: {item} ({price}).').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
item=item,
|
||||
price=money_filter(Decimal(data['price']), event.currency),
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderSecretChanged(OrderPositionChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.secret'
|
||||
plain = _('A new secret has been generated for position #{posid}.')
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderValidFromChanged(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.valid_from'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return _('The validity start date for position #{posid} has been changed to {value}.').format(
|
||||
elif logentry.action_type == 'pretix.event.order.changed.secret':
|
||||
return text + ' ' + _('A new secret has been generated for position #{posid}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.valid_from':
|
||||
return text + ' ' + _('The validity start date for position #{posid} has been changed to {value}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get(
|
||||
'new_value') else '–'
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderValidUntilChanged(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.valid_until'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
return _('The validity end date for position #{posid} has been changed to {value}.').format(
|
||||
elif logentry.action_type == 'pretix.event.order.changed.valid_until':
|
||||
return text + ' ' + _('The validity end date for position #{posid} has been changed to {value}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get('new_value') else '–'
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderChangedBlockAdded(OrderPositionChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.add_block'
|
||||
plain = _('A block has been added for position #{posid}.')
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderChangedBlockRemoved(OrderPositionChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.remove_block'
|
||||
plain = _('A block has been removed for position #{posid}.')
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderChangedSplit(OrderChangeLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.split'
|
||||
|
||||
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||
elif logentry.action_type == 'pretix.event.order.changed.add_block':
|
||||
return text + ' ' + _('A block has been added for position #{posid}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.remove_block':
|
||||
return text + ' ' + _('A block has been removed for position #{posid}.').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.split':
|
||||
old_item = str(event.items.get(pk=data['old_item']))
|
||||
if data['old_variation']:
|
||||
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
||||
@@ -282,144 +198,194 @@ class OrderChangedSplit(OrderChangeLogEntryType):
|
||||
'organizer': event.organizer.slug,
|
||||
'code': data['new_order']
|
||||
})
|
||||
return mark_safe(self.prefix + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
|
||||
return mark_safe(escape(text) + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
|
||||
old_item=escape(old_item),
|
||||
posid=data.get('positionid', '?'),
|
||||
order='<a href="{}">{}</a>'.format(url, data['new_order']),
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
))
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class OrderChangedSplitFrom(OrderLogEntryType):
|
||||
action_type = 'pretix.event.order.changed.split_from'
|
||||
|
||||
def display(self, logentry: LogEntry, data):
|
||||
elif logentry.action_type == 'pretix.event.order.changed.split_from':
|
||||
return _('This order has been created by splitting the order {order}').format(
|
||||
order=data['original_order'],
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.checkin.unknown': (
|
||||
_('Unknown scan of code "{barcode}…" at {datetime} for list "{list}", type "{type}".'),
|
||||
_('Unknown scan of code "{barcode}…" for list "{list}", type "{type}".'),
|
||||
),
|
||||
'pretix.event.checkin.revoked': (
|
||||
_('Scan of revoked code "{barcode}…" at {datetime} for list "{list}", type "{type}", was uploaded.'),
|
||||
_('Scan of revoked code "{barcode}" for list "{list}", type "{type}", was uploaded.'),
|
||||
),
|
||||
'pretix.event.checkin.denied': (
|
||||
_('Denied scan of position #{posid} at {datetime} for list "{list}", type "{type}", error code "{errorcode}".'),
|
||||
_('Denied scan of position #{posid} for list "{list}", type "{type}", error code "{errorcode}".'),
|
||||
),
|
||||
'pretix.control.views.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
|
||||
'pretix.event.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
|
||||
})
|
||||
class CheckinErrorLogEntryType(OrderLogEntryType):
|
||||
def display(self, logentry: LogEntry, data):
|
||||
self.display_plain(self.plain, logentry, data)
|
||||
def _display_checkin(event, logentry):
|
||||
data = logentry.parsed_data
|
||||
|
||||
def display_plain(self, plain, logentry: LogEntry, data):
|
||||
if isinstance(plain, tuple):
|
||||
plain_with_dt, plain_without_dt = plain
|
||||
else:
|
||||
plain_with_dt, plain_without_dt = plain, plain
|
||||
show_dt = False
|
||||
if 'datetime' in data:
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
show_dt = abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data
|
||||
tz = event.timezone
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
|
||||
data = defaultdict(lambda: '?', data)
|
||||
if 'list' in data:
|
||||
try:
|
||||
checkin_list = event.checkin_lists.get(pk=data.get('list')).name
|
||||
except CheckinList.DoesNotExist:
|
||||
checkin_list = _("(unknown)")
|
||||
else:
|
||||
checkin_list = _("(unknown)")
|
||||
|
||||
event = logentry.event
|
||||
|
||||
if 'list' in data:
|
||||
try:
|
||||
data['list'] = event.checkin_lists.get(pk=data.get('list')).name
|
||||
except CheckinList.DoesNotExist:
|
||||
data['list'] = _("(unknown)")
|
||||
else:
|
||||
data['list'] = _("(unknown)")
|
||||
|
||||
data['barcode'] = data.get('barcode')[:16]
|
||||
data['posid'] = logentry.parsed_data.get('positionid', '?')
|
||||
|
||||
if 'datetime' in data:
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
if abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data:
|
||||
tz = event.timezone
|
||||
data['datetime'] = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
return str(plain_with_dt).format_map(data)
|
||||
else:
|
||||
return str(plain_without_dt).format_map(data)
|
||||
|
||||
|
||||
class CheckinLogEntryType(CheckinErrorLogEntryType):
|
||||
def display(self, logentry: LogEntry, data):
|
||||
if data.get('type') == Checkin.TYPE_EXIT:
|
||||
return self.display_plain((
|
||||
_('Position #{posid} has been checked out at {datetime} for list "{list}".'),
|
||||
_('Position #{posid} has been checked out for list "{list}".'),
|
||||
), logentry, data)
|
||||
elif data.get('first'):
|
||||
return self.display_plain((
|
||||
_('Position #{posid} has been checked in at {datetime} for list "{list}".'),
|
||||
_('Position #{posid} has been checked in for list "{list}".'),
|
||||
), logentry, data)
|
||||
elif data.get('forced'):
|
||||
return self.display_plain(
|
||||
_('A scan for position #{posid} at {datetime} for list "{list}" has been uploaded even though it has '
|
||||
'been scanned already.'),
|
||||
logentry, data
|
||||
if logentry.action_type == 'pretix.event.checkin.unknown':
|
||||
if show_dt:
|
||||
return _(
|
||||
'Unknown scan of code "{barcode}…" at {datetime} for list "{list}", type "{type}".'
|
||||
).format(
|
||||
posid=data.get('positionid'),
|
||||
type=data.get('type'),
|
||||
barcode=data.get('barcode')[:16],
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
else:
|
||||
return self.display_plain(
|
||||
_('Position #{posid} has been scanned and rejected because it has already been scanned before '
|
||||
'on list "{list}".'),
|
||||
logentry, data
|
||||
return _(
|
||||
'Unknown scan of code "{barcode}…" for list "{list}", type "{type}".'
|
||||
).format(
|
||||
posid=data.get('positionid'),
|
||||
type=data.get('type'),
|
||||
barcode=data.get('barcode')[:16],
|
||||
list=checkin_list
|
||||
)
|
||||
|
||||
|
||||
class OrderConsentLogEntryType(OrderLogEntryType):
|
||||
action_type = 'pretix.event.order.consent'
|
||||
|
||||
def display(self, logentry: LogEntry, data):
|
||||
return _('The user confirmed the following message: "{}"').format(
|
||||
bleach.clean(data.get('msg'), tags=set(), strip=True)
|
||||
)
|
||||
|
||||
|
||||
class OrderCanceledLogEntryType(OrderLogEntryType):
|
||||
action_type = 'pretix.event.order.canceled'
|
||||
|
||||
def display(self, logentry: LogEntry, data):
|
||||
comment = data.get('comment')
|
||||
if comment:
|
||||
return _('The order has been canceled (comment: "{comment}").').format(comment=comment)
|
||||
if logentry.action_type == 'pretix.event.checkin.revoked':
|
||||
if show_dt:
|
||||
return _(
|
||||
'Scan scan of revoked code "{barcode}…" at {datetime} for list "{list}", type "{type}", was uploaded.'
|
||||
).format(
|
||||
posid=data.get('positionid'),
|
||||
type=data.get('type'),
|
||||
barcode=data.get('barcode')[:16],
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
else:
|
||||
return _('The order has been canceled.')
|
||||
return _(
|
||||
'Scan of revoked code "{barcode}" for list "{list}", type "{type}", was uploaded.'
|
||||
).format(
|
||||
posid=data.get('positionid'),
|
||||
type=data.get('type'),
|
||||
barcode=data.get('barcode')[:16],
|
||||
list=checkin_list
|
||||
)
|
||||
|
||||
if logentry.action_type == 'pretix.event.checkin.denied':
|
||||
if show_dt:
|
||||
return _(
|
||||
'Denied scan of position #{posid} at {datetime} for list "{list}", type "{type}", '
|
||||
'error code "{errorcode}".'
|
||||
).format(
|
||||
posid=data.get('positionid'),
|
||||
type=data.get('type'),
|
||||
errorcode=data.get('errorcode'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
else:
|
||||
return _(
|
||||
'Denied scan of position #{posid} for list "{list}", type "{type}", error code "{errorcode}".'
|
||||
).format(
|
||||
posid=data.get('positionid'),
|
||||
type=data.get('type'),
|
||||
errorcode=data.get('errorcode'),
|
||||
list=checkin_list
|
||||
)
|
||||
|
||||
class OrderPrintLogEntryType(OrderLogEntryType):
|
||||
action_type = 'pretix.event.order.print'
|
||||
|
||||
def display(self, logentry: LogEntry, data):
|
||||
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=date_format(
|
||||
dateutil.parser.parse(data["datetime"]).astimezone(logentry.event.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
),
|
||||
type=dict(PrintLog.PRINT_TYPES)[data["type"]],
|
||||
if data.get('type') == Checkin.TYPE_EXIT:
|
||||
if show_dt:
|
||||
return _('Position #{posid} has been checked out at {datetime} for list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
else:
|
||||
return _('Position #{posid} has been checked out for list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
list=checkin_list
|
||||
)
|
||||
if data.get('first'):
|
||||
if show_dt:
|
||||
return _('Position #{posid} has been checked in at {datetime} for list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
else:
|
||||
return _('Position #{posid} has been checked in for list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
list=checkin_list
|
||||
)
|
||||
else:
|
||||
if data.get('forced'):
|
||||
return _(
|
||||
'A scan for position #{posid} at {datetime} for list "{list}" has been uploaded even though it has '
|
||||
'been scanned already.'.format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
)
|
||||
return _(
|
||||
'Position #{posid} has been scanned and rejected because it has already been scanned before '
|
||||
'on list "{list}".'.format(
|
||||
posid=data.get('positionid'),
|
||||
list=checkin_list
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
|
||||
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
|
||||
if logentry.action_type.startswith('pretix.event.order.changed'):
|
||||
return _display_order_changed(sender, logentry)
|
||||
|
||||
if logentry.action_type.startswith('pretix.event.payment.provider.'):
|
||||
return _('The settings of a payment provider have been changed.')
|
||||
|
||||
if logentry.action_type.startswith('pretix.event.tickets.provider.'):
|
||||
return _('The settings of a ticket output provider have been changed.')
|
||||
|
||||
if logentry.action_type == 'pretix.event.order.consent':
|
||||
return _('The user confirmed the following message: "{}"').format(
|
||||
bleach.clean(logentry.parsed_data.get('msg'), tags=set(), strip=True)
|
||||
)
|
||||
|
||||
if logentry.action_type == 'pretix.event.order.canceled':
|
||||
comment = logentry.parsed_data.get('comment')
|
||||
if comment:
|
||||
return _('The order has been canceled (comment: "{comment}").').format(comment=comment)
|
||||
else:
|
||||
return _('The order has been canceled.')
|
||||
|
||||
if logentry.action_type in ('pretix.control.views.checkin.reverted', 'pretix.event.checkin.reverted'):
|
||||
if 'list' in logentry.parsed_data:
|
||||
try:
|
||||
checkin_list = sender.checkin_lists.get(pk=logentry.parsed_data.get('list')).name
|
||||
except CheckinList.DoesNotExist:
|
||||
checkin_list = _("(unknown)")
|
||||
else:
|
||||
checkin_list = _("(unknown)")
|
||||
|
||||
return _('The check-in of position #{posid} on list "{list}" has been reverted.').format(
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
list=checkin_list,
|
||||
)
|
||||
|
||||
if sender and logentry.action_type.startswith('pretix.event.checkin'):
|
||||
return _display_checkin(sender, logentry)
|
||||
|
||||
if logentry.action_type == 'pretix.event.order.print':
|
||||
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
datetime=date_format(
|
||||
dateutil.parser.parse(logentry.parsed_data["datetime"]).astimezone(sender.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
),
|
||||
type=dict(PrintLog.PRINT_TYPES)[logentry.parsed_data["type"]],
|
||||
)
|
||||
|
||||
|
||||
@receiver(signal=orderposition_blocked_display, dispatch_uid="pretixcontrol_orderposition_blocked_display")
|
||||
def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, block_name, **kwargs):
|
||||
@@ -430,7 +396,6 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
|
||||
'pretix.event.order.modified': _('The order details have been changed.'),
|
||||
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
||||
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
||||
@@ -521,16 +486,17 @@ class VoucherRedeemedLogEntryType(VoucherLogEntryType):
|
||||
action_type = 'pretix.voucher.redeemed'
|
||||
plain = _('The voucher has been redeemed in order {order_code}.')
|
||||
|
||||
def display(self, logentry, data):
|
||||
def display(self, logentry):
|
||||
data = json.loads(logentry.data)
|
||||
data = defaultdict(lambda: '?', data)
|
||||
url = reverse('control:event.order', kwargs={
|
||||
'event': logentry.event.slug,
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'code': data.get('order_code', '?')
|
||||
'code': data['order_code']
|
||||
})
|
||||
return format_html(
|
||||
self.plain,
|
||||
order_code=format_html('<a href="{}">{}</a>', url, data('order_code', '?')),
|
||||
)
|
||||
return mark_safe(self.plain.format(
|
||||
order_code='<a href="{}">{}</a>'.format(url, data['order_code']),
|
||||
))
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
@@ -553,8 +519,8 @@ class CoreTaxRuleLogEntryType(TaxRuleLogEntryType):
|
||||
|
||||
|
||||
class TeamMembershipLogEntryType(LogEntryType):
|
||||
def display(self, logentry, data):
|
||||
return self.plain.format(user=data.get('email'))
|
||||
def display(self, logentry):
|
||||
return self.plain.format(user=logentry.parsed_data.get('email'))
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
@@ -571,9 +537,9 @@ class CoreTeamMembershipLogEntryType(TeamMembershipLogEntryType):
|
||||
class TeamMemberJoinedLogEntryType(LogEntryType):
|
||||
action_type = 'pretix.team.member.joined'
|
||||
|
||||
def display(self, logentry, data):
|
||||
def display(self, logentry):
|
||||
return _('{user} has joined the team using the invite sent to {email}.').format(
|
||||
user=data.get('email'), email=data.get('invite_email')
|
||||
user=logentry.parsed_data.get('email'), email=logentry.parsed_data.get('invite_email')
|
||||
)
|
||||
|
||||
|
||||
@@ -581,23 +547,23 @@ class TeamMemberJoinedLogEntryType(LogEntryType):
|
||||
class UserSettingsChangedLogEntryType(LogEntryType):
|
||||
action_type = 'pretix.user.settings.changed'
|
||||
|
||||
def display(self, logentry, data):
|
||||
def display(self, logentry):
|
||||
text = str(_('Your account settings have been changed.'))
|
||||
if 'email' in data:
|
||||
if 'email' in logentry.parsed_data:
|
||||
text = text + ' ' + str(
|
||||
_('Your email address has been changed to {email}.').format(email=data['email']))
|
||||
if 'new_pw' in data:
|
||||
_('Your email address has been changed to {email}.').format(email=logentry.parsed_data['email']))
|
||||
if 'new_pw' in logentry.parsed_data:
|
||||
text = text + ' ' + str(_('Your password has been changed.'))
|
||||
if data.get('is_active') is True:
|
||||
if logentry.parsed_data.get('is_active') is True:
|
||||
text = text + ' ' + str(_('Your account has been enabled.'))
|
||||
elif data.get('is_active') is False:
|
||||
elif logentry.parsed_data.get('is_active') is False:
|
||||
text = text + ' ' + str(_('Your account has been disabled.'))
|
||||
return text
|
||||
|
||||
|
||||
class UserImpersonatedLogEntryType(LogEntryType):
|
||||
def display(self, logentry, data):
|
||||
return self.plain.format(data['other_email'])
|
||||
def display(self, logentry):
|
||||
return self.plain.format(logentry.parsed_data['other_email'])
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
@@ -722,13 +688,16 @@ class CoreLogEntryType(LogEntryType):
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
|
||||
'pretix.event.item_meta_property.added': _('A meta property has been added to this event.'),
|
||||
'pretix.event.item_meta_property.deleted': _('A meta property has been removed from this event.'),
|
||||
'pretix.event.item_meta_property.changed': _('A meta property has been changed on this event.'),
|
||||
'pretix.event.checkinlist.added': _('The check-in list has been added.'),
|
||||
'pretix.event.checkinlist.deleted': _('The check-in list has been deleted.'),
|
||||
'pretix.event.checkinlists.deleted': _('The check-in list has been deleted.'), # backwards compatibility
|
||||
'pretix.event.checkinlist.changed': _('The check-in list has been changed.'),
|
||||
'pretix.event.settings': _('The event settings have been changed.'),
|
||||
'pretix.event.tickets.settings': _('The ticket download settings have been changed.'),
|
||||
'pretix.event.tickets.provider': _('The settings of a ticket output provider have been changed.'),
|
||||
'pretix.event.payment.provider': _('The settings of a payment provider have been changed.'),
|
||||
'pretix.event.live.activated': _('The shop has been taken live.'),
|
||||
'pretix.event.live.deactivated': _('The shop has been taken offline.'),
|
||||
'pretix.event.testmode.activated': _('The shop has been taken into test mode.'),
|
||||
@@ -748,19 +717,6 @@ class CoreEventLogEntryType(EventLogEntryType):
|
||||
pass
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.checkinlist.added': _('The check-in list has been added.'),
|
||||
'pretix.event.checkinlist.deleted': _('The check-in list has been deleted.'),
|
||||
'pretix.event.checkinlists.deleted': _('The check-in list has been deleted.'), # backwards compatibility
|
||||
'pretix.event.checkinlist.changed': _('The check-in list has been changed.'),
|
||||
})
|
||||
class CheckinlistLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Check-in list {val}')
|
||||
object_link_viewname = 'control:event.orders.checkinlists.edit'
|
||||
object_link_argname = 'list'
|
||||
content_type = CheckinList
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.plugins.enabled': _('The plugin has been enabled.'),
|
||||
'pretix.event.plugins.disabled': _('The plugin has been disabled.'),
|
||||
@@ -768,7 +724,7 @@ class CheckinlistLogEntryType(EventLogEntryType):
|
||||
class EventPluginStateLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Plugin {val}')
|
||||
|
||||
def get_object_link_info(self, logentry) -> Optional[dict]:
|
||||
def get_object_link_info(self, logentry) -> dict:
|
||||
if 'plugin' in logentry.parsed_data:
|
||||
app = app_cache.get(logentry.parsed_data['plugin'])
|
||||
if app and hasattr(app, 'PretixPluginMeta'):
|
||||
@@ -803,17 +759,17 @@ class CoreItemLogEntryType(ItemLogEntryType):
|
||||
'pretix.event.item.variation.changed': _('The variation "{value}" has been changed.'),
|
||||
})
|
||||
class VariationLogEntryType(ItemLogEntryType):
|
||||
def display(self, logentry, data):
|
||||
if 'value' not in data:
|
||||
def display(self, logentry):
|
||||
if 'value' not in logentry.parsed_data:
|
||||
# Backwards compatibility
|
||||
var = ItemVariation.objects.filter(id=data['id']).first()
|
||||
var = ItemVariation.objects.filter(id=logentry.parsed_data['id']).first()
|
||||
if var:
|
||||
data['value'] = str(var.value)
|
||||
logentry.parsed_data['value'] = str(var.value)
|
||||
else:
|
||||
data['value'] = '?'
|
||||
logentry.parsed_data['value'] = '?'
|
||||
else:
|
||||
data['value'] = LazyI18nString(data['value'])
|
||||
return super().display(logentry, data)
|
||||
logentry.parsed_data['value'] = LazyI18nString(logentry.parsed_data['value'])
|
||||
return super().display(logentry)
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
@@ -869,27 +825,27 @@ class CoreDiscountLogEntryType(DiscountLogEntryType):
|
||||
class LegacyCheckinLogEntryType(OrderLogEntryType):
|
||||
action_type = 'pretix.control.views.checkin'
|
||||
|
||||
def display(self, logentry, data):
|
||||
def display(self, logentry):
|
||||
# deprecated
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
dt = dateutil.parser.parse(logentry.parsed_data.get('datetime'))
|
||||
tz = logentry.event.timezone
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
if 'list' in data:
|
||||
if 'list' in logentry.parsed_data:
|
||||
try:
|
||||
checkin_list = logentry.event.checkin_lists.get(pk=data.get('list')).name
|
||||
checkin_list = logentry.event.checkin_lists.get(pk=logentry.parsed_data.get('list')).name
|
||||
except CheckinList.DoesNotExist:
|
||||
checkin_list = _("(unknown)")
|
||||
else:
|
||||
checkin_list = _("(unknown)")
|
||||
|
||||
if data.get('first'):
|
||||
if logentry.parsed_data.get('first'):
|
||||
return _('Position #{posid} has been checked in manually at {datetime} on list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list,
|
||||
)
|
||||
return _('Position #{posid} has been checked in again at {datetime} on list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
|
||||
@@ -491,11 +491,8 @@ class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMix
|
||||
if self.form.is_valid():
|
||||
if self.form.has_changed():
|
||||
self.request.event.log_action(
|
||||
'pretix.event.payment.provider', user=self.request.user, data={
|
||||
'provider': self.provider.identifier,
|
||||
'new_values': {
|
||||
k: self.form.cleaned_data.get(k) for k in self.form.changed_data
|
||||
}
|
||||
'pretix.event.payment.provider.' + self.provider.identifier, user=self.request.user, data={
|
||||
k: self.form.cleaned_data.get(k) for k in self.form.changed_data
|
||||
}
|
||||
)
|
||||
self.form.save()
|
||||
@@ -891,14 +888,11 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
|
||||
provider.form.save()
|
||||
if provider.form.has_changed():
|
||||
self.request.event.log_action(
|
||||
'pretix.event.tickets.provider', user=self.request.user, data={
|
||||
'provider': provider.identifier,
|
||||
'new_values': {
|
||||
k: (provider.form.cleaned_data.get(k).name
|
||||
if isinstance(provider.form.cleaned_data.get(k), File)
|
||||
else provider.form.cleaned_data.get(k))
|
||||
for k in provider.form.changed_data
|
||||
}
|
||||
'pretix.event.tickets.provider.' + provider.identifier, user=self.request.user, data={
|
||||
k: (provider.form.cleaned_data.get(k).name
|
||||
if isinstance(provider.form.cleaned_data.get(k), File)
|
||||
else provider.form.cleaned_data.get(k))
|
||||
for k in provider.form.changed_data
|
||||
}
|
||||
)
|
||||
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'provider': provider.identifier})
|
||||
|
||||
@@ -313,7 +313,7 @@ class EventWizard(SafeSessionWizardView):
|
||||
)
|
||||
event.set_defaults()
|
||||
|
||||
if basics_data['tax_rate'] is not None:
|
||||
if basics_data['tax_rate']:
|
||||
if not event.settings.tax_rate_default or event.settings.tax_rate_default.rate != basics_data['tax_rate']:
|
||||
event.settings.tax_rate_default = event.tax_rules.create(
|
||||
name=LazyI18nString.from_gettext(gettext('VAT')),
|
||||
|
||||
@@ -39,12 +39,6 @@ class ThumbnailingImageReader(ImageReader):
|
||||
self._data = None
|
||||
return width, height
|
||||
|
||||
def remove_transparency(self, background_color="WHITE"):
|
||||
if "A" in self._image.mode:
|
||||
new_image = Image.new("RGBA", self._image.size, background_color)
|
||||
new_image.paste(self._image, mask=self._image)
|
||||
self._image = new_image.convert("RGB")
|
||||
|
||||
def _jpeg_fh(self):
|
||||
# Bypass a reportlab-internal optimization that falls back to the original
|
||||
# file handle if the file is a JPEG, and therefore does not respect the
|
||||
|
||||
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
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:46+0000\n"
|
||||
"PO-Revision-Date: 2025-01-22 16:00+0000\n"
|
||||
"PO-Revision-Date: 2024-09-10 07:17+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"de/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.9.2\n"
|
||||
"X-Generator: Weblate 5.7\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -777,13 +777,13 @@ msgstr "Preis"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr "Originalpreis: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "New price: %s"
|
||||
msgstr "Neuer Preis: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
@@ -836,7 +836,7 @@ msgstr "ab %(currency)s %(price)s"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr "Bild von %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:46+0000\n"
|
||||
"PO-Revision-Date: 2025-01-22 16:00+0000\n"
|
||||
"PO-Revision-Date: 2024-09-10 07:17+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix-js/de_Informal/>\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.9.2\n"
|
||||
"X-Generator: Weblate 5.7\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -776,13 +776,13 @@ msgstr "Preis"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr "Originalpreis: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "New price: %s"
|
||||
msgstr "Neuer Preis: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
@@ -835,7 +835,7 @@ msgstr "ab %(currency)s %(price)s"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr "Bild von %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-21 16:43+0000\n"
|
||||
"POT-Creation-Date: 2025-01-15 16:46+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"
|
||||
|
||||
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
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:46+0000\n"
|
||||
"PO-Revision-Date: 2025-01-21 00:00+0000\n"
|
||||
"Last-Translator: Hector <hector@demandaeventos.es>\n"
|
||||
"PO-Revision-Date: 2025-01-16 10:32+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/es/>\n"
|
||||
"Language: es\n"
|
||||
@@ -775,7 +775,7 @@ msgstr "Precio"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr "Precio original: %s"
|
||||
msgstr "Prix initial : %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
#, javascript-format
|
||||
|
||||
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
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
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
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
File diff suppressed because it is too large
Load Diff
@@ -8,10 +8,10 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:46+0000\n"
|
||||
"PO-Revision-Date: 2025-01-18 18:00+0000\n"
|
||||
"PO-Revision-Date: 2024-12-27 11:45+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/ja/>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/ja/>\n"
|
||||
"Language: ja\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -31,7 +31,7 @@ msgstr "注釈:"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
|
||||
msgid "PayPal"
|
||||
msgstr "PayPal"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:35
|
||||
msgid "Venmo"
|
||||
@@ -60,19 +60,19 @@ msgstr "PayPal後払い"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL"
|
||||
msgstr "iDEAL"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
msgstr "SEPA Direct Debit"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
|
||||
msgid "Bancontact"
|
||||
msgstr "Bancontact"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:44
|
||||
msgid "giropay"
|
||||
msgstr "giropay"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:45
|
||||
msgid "SOFORT"
|
||||
@@ -88,7 +88,7 @@ msgstr "MyBank"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:48
|
||||
msgid "Przelewy24"
|
||||
msgstr "Przelewy24"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:49
|
||||
msgid "Verkkopankki"
|
||||
@@ -124,7 +124,7 @@ msgstr "Boleto"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:57
|
||||
msgid "WeChat Pay"
|
||||
msgstr "WeChat Pay"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:58
|
||||
msgid "Mercado Pago"
|
||||
@@ -241,7 +241,7 @@ msgstr "確認済み"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Approval pending"
|
||||
msgstr "承認保留中"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Redeemed"
|
||||
@@ -297,12 +297,16 @@ msgid "Ticket code revoked/changed"
|
||||
msgstr "チケットコードのブロック/変更"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||
#, fuzzy
|
||||
#| msgid "Ticket not paid"
|
||||
msgid "Ticket blocked"
|
||||
msgstr "チケットブロック中"
|
||||
msgstr "チケット未払い"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
#, fuzzy
|
||||
#| msgid "Ticket not paid"
|
||||
msgid "Ticket not valid at this time"
|
||||
msgstr "現時点で無効なチケット"
|
||||
msgstr "チケット未払い"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
|
||||
msgid "Order canceled"
|
||||
@@ -310,11 +314,11 @@ 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"
|
||||
msgstr "承認されない注文"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:68
|
||||
msgid "Checked-in Tickets"
|
||||
@@ -451,7 +455,7 @@ msgstr "商品の種類"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
|
||||
msgid "Gate"
|
||||
msgstr "ゲート"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:111
|
||||
msgid "Current date and time"
|
||||
@@ -572,8 +576,10 @@ msgid "Text object (deprecated)"
|
||||
msgstr "テキストオブジェクト (廃止済)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:901
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "テキストボックス"
|
||||
msgstr "テキストオブジェクト"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:903
|
||||
msgid "Barcode area"
|
||||
@@ -764,13 +770,13 @@ msgstr "価格"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr "元の価格: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "New price: %s"
|
||||
msgstr "新しい価格: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
@@ -823,7 +829,7 @@ msgstr "%(currency)s %(price)sから"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr "%sのイメージ"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
|
||||
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
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
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
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
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
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
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
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
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -82,17 +82,17 @@
|
||||
{% endif %}
|
||||
{% if event_logo and event_logo_image_large %}
|
||||
<a href="{% eventurl event "presale:event.index" cart_namespace=cart_namespace|default_if_none:"" %}"
|
||||
title="{% trans 'Homepage' %}">
|
||||
aria-label="{% trans 'Homepage' %}" title="{% trans 'Homepage' %}">
|
||||
<img src="{{ event_logo|thumb:'1170x5000' }}" alt="{{ event.name }}" class="event-logo" />
|
||||
</a>
|
||||
{% elif event_logo %}
|
||||
<a href="{% eventurl event "presale:event.index" cart_namespace=cart_namespace|default_if_none:"" %}"
|
||||
title="{% trans 'Homepage' %}">
|
||||
aria-label="{% trans 'Homepage' %}" title="{% trans 'Homepage' %}">
|
||||
<img src="{{ event_logo|thumb:'5000x120' }}" alt="{{ event.name }}" class="event-logo" />
|
||||
</a>
|
||||
{% else %}
|
||||
<h1>
|
||||
<a href="{% eventurl event "presale:event.index" cart_namespace=cart_namespace|default_if_none:"" %}">{{ event.name }}</a>
|
||||
<a href="{% eventurl event "presale:event.index" cart_namespace=cart_namespace|default_if_none:"" %}" aria-label="{% trans 'Homepage' %}">{{ event.name }}</a>
|
||||
{% if request.event.settings.show_dates_on_frontpage and not event.has_subevents %}
|
||||
<small>{{ event.get_date_range_display_as_html }}</small>
|
||||
{% endif %}
|
||||
|
||||
@@ -268,14 +268,8 @@
|
||||
{% if line.discount and line.line_price_gross != line.price %}
|
||||
<span class="text-success discounted" data-toggle="tooltip" title="{% trans "The price of this product was reduced because of an automatic discount." %}">
|
||||
<br>
|
||||
<span class="fa fa-star fa-fw" aria-hidden="true"></span>
|
||||
{% if line.price < line.line_price_gross %}
|
||||
{% blocktranslate trimmed with percent=line.discount_percentage|floatformat:0 %}
|
||||
{{ percent }} % Discount
|
||||
{% endblocktranslate %}
|
||||
{% else %}
|
||||
{% trans "Discounted" %}
|
||||
{% endif %}
|
||||
<span class="fa fa-percent fa-fw" aria-hidden="true"></span>
|
||||
{% trans "Discounted" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -341,14 +335,8 @@
|
||||
{% if line.discount and line.line_price_gross != line.price %}
|
||||
<span class="text-success discounted" data-toggle="tooltip" title="{% trans "The price of this product was reduced because of an automatic discount." %}">
|
||||
<br>
|
||||
<span class="fa fa-star fa-fw" aria-hidden="true"></span>
|
||||
{% if line.price < line.line_price_gross %}
|
||||
{% blocktranslate trimmed with percent=line.discount_percentage|floatformat:0 %}
|
||||
{{ percent }} % Discount
|
||||
{% endblocktranslate %}
|
||||
{% else %}
|
||||
{% trans "Discounted" %}
|
||||
{% endif %}
|
||||
<span class="fa fa-percent fa-fw" aria-hidden="true"></span>
|
||||
{% trans "Discounted" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -34,17 +34,10 @@ $(function () {
|
||||
}
|
||||
for(var k in dependents) {
|
||||
const options = data[k],
|
||||
dependent = dependents[k];
|
||||
let visible = 'visible' in options ? options.visible : true;
|
||||
dependent = dependents[k],
|
||||
visible = 'visible' in options ? options.visible : true,
|
||||
required = 'required' in options && options.required && isRequired && visible;
|
||||
|
||||
if (dependent.is("[data-display-dependency]")) {
|
||||
const dependency = $(dependent.attr("data-display-dependency"));
|
||||
visible = visible && (
|
||||
(dependency.attr("type") === 'checkbox' || dependency.attr("type") === 'radio') ? dependency.prop('checked') : !!dependency.val()
|
||||
);
|
||||
}
|
||||
|
||||
const required = 'required' in options && options.required && isRequired && visible;
|
||||
dependent.closest(".form-group").toggle(visible).toggleClass('required', required);
|
||||
dependent.prop("required", required);
|
||||
}
|
||||
|
||||
@@ -673,12 +673,7 @@ class ItemsTest(ItemFormTest):
|
||||
with scopes_disabled():
|
||||
q = Question.objects.create(event=self.event1, question="Size", type="N")
|
||||
q.items.add(self.item2)
|
||||
self.item2.limit_sales_channels.set(self.orga1.sales_channels.filter(identifier__in=["web", "bar"]))
|
||||
self.item2.all_sales_channels = False
|
||||
self.item2.save()
|
||||
self.var2.limit_sales_channels.set(self.orga1.sales_channels.filter(identifier__in=["web"]))
|
||||
self.var2.all_sales_channels = False
|
||||
self.var2.save()
|
||||
self.item2.sales_channels = ["web", "bar"]
|
||||
prop = self.event1.item_meta_properties.create(name="Foo")
|
||||
self.item2.meta_values.create(property=prop, value="Bar")
|
||||
|
||||
@@ -695,7 +690,6 @@ class ItemsTest(ItemFormTest):
|
||||
with scopes_disabled():
|
||||
i_old = Item.objects.get(name__icontains='Business')
|
||||
i_new = Item.objects.get(name__icontains='Intermediate')
|
||||
v2_new = i_new.variations.get(value__icontains='Gold')
|
||||
assert i_new.category == i_old.category
|
||||
assert i_new.description == i_old.description
|
||||
assert i_new.active == i_old.active
|
||||
@@ -704,8 +698,7 @@ class ItemsTest(ItemFormTest):
|
||||
assert i_new.require_voucher == i_old.require_voucher
|
||||
assert i_new.hide_without_voucher == i_old.hide_without_voucher
|
||||
assert i_new.allow_cancel == i_old.allow_cancel
|
||||
assert set(i_new.limit_sales_channels.values_list("identifier", flat=True)) == {"web", "bar"}
|
||||
assert set(v2_new.limit_sales_channels.values_list("identifier", flat=True)) == {"web"}
|
||||
assert set(i_new.limit_sales_channels.all()) == set(i_old.limit_sales_channels.all())
|
||||
assert i_new.meta_data == i_old.meta_data == {"Foo": "Bar"}
|
||||
assert set(i_new.questions.all()) == set(i_old.questions.all())
|
||||
assert set([str(v.value) for v in i_new.variations.all()]) == set([str(v.value) for v in i_old.variations.all()])
|
||||
|
||||
Reference in New Issue
Block a user