forked from CGM_Public/pretix_original
Compare commits
7 Commits
hideifitem
...
fix-busine
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4246cf752 | ||
|
|
f3022ddca9 | ||
|
|
939a591061 | ||
|
|
b847612e1a | ||
|
|
832f4e4d68 | ||
|
|
0a23aeece4 | ||
|
|
9622bf41a1 |
@@ -69,6 +69,10 @@ hidden_if_available integer **DEPRECATED*
|
|||||||
hidden_if_item_available integer The internal ID of a different item, or ``null``. If
|
hidden_if_item_available integer The internal ID of a different item, or ``null``. If
|
||||||
set, this item won't be shown publicly as long as this
|
set, this item won't be shown publicly as long as this
|
||||||
other item is available.
|
other item is available.
|
||||||
|
hidden_if_item_available_mode string If ``hide`` (the default), this item is hidden in the shop
|
||||||
|
if unavailable due to the ``hidden_if_item_available`` setting.
|
||||||
|
If ``info``, the item is visible, but can't be purchased,
|
||||||
|
and a note explaining the unavailability is displayed.
|
||||||
require_voucher boolean If ``true``, this item can only be bought using a
|
require_voucher boolean If ``true``, this item can only be bought using a
|
||||||
voucher that is specifically assigned to this item.
|
voucher that is specifically assigned to this item.
|
||||||
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
|
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
|
||||||
@@ -239,6 +243,10 @@ meta_data object Values set fo
|
|||||||
The ``hidden_if_item_available`` attributes has been added, the ``hidden_if_available`` attribute has been
|
The ``hidden_if_item_available`` attributes has been added, the ``hidden_if_available`` attribute has been
|
||||||
deprecated.
|
deprecated.
|
||||||
|
|
||||||
|
.. versionchanged:: 2025.01
|
||||||
|
|
||||||
|
The ``hidden_if_item_available_mode`` attributes has been added.
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@@ -308,6 +316,7 @@ Endpoints
|
|||||||
"available_until_mode": "hide",
|
"available_until_mode": "hide",
|
||||||
"hidden_if_available": null,
|
"hidden_if_available": null,
|
||||||
"hidden_if_item_available": null,
|
"hidden_if_item_available": null,
|
||||||
|
"hidden_if_item_available_mode": "hide",
|
||||||
"require_voucher": false,
|
"require_voucher": false,
|
||||||
"hide_without_voucher": false,
|
"hide_without_voucher": false,
|
||||||
"allow_cancel": true,
|
"allow_cancel": true,
|
||||||
@@ -459,6 +468,7 @@ Endpoints
|
|||||||
"available_until_mode": "hide",
|
"available_until_mode": "hide",
|
||||||
"hidden_if_available": null,
|
"hidden_if_available": null,
|
||||||
"hidden_if_item_available": null,
|
"hidden_if_item_available": null,
|
||||||
|
"hidden_if_item_available_mode": "hide",
|
||||||
"require_voucher": false,
|
"require_voucher": false,
|
||||||
"hide_without_voucher": false,
|
"hide_without_voucher": false,
|
||||||
"allow_cancel": true,
|
"allow_cancel": true,
|
||||||
@@ -589,6 +599,7 @@ Endpoints
|
|||||||
"available_until_mode": "hide",
|
"available_until_mode": "hide",
|
||||||
"hidden_if_available": null,
|
"hidden_if_available": null,
|
||||||
"hidden_if_item_available": null,
|
"hidden_if_item_available": null,
|
||||||
|
"hidden_if_item_available_mode": "hide",
|
||||||
"require_voucher": false,
|
"require_voucher": false,
|
||||||
"hide_without_voucher": false,
|
"hide_without_voucher": false,
|
||||||
"allow_cancel": true,
|
"allow_cancel": true,
|
||||||
@@ -705,6 +716,7 @@ Endpoints
|
|||||||
"available_until_mode": "hide",
|
"available_until_mode": "hide",
|
||||||
"hidden_if_available": null,
|
"hidden_if_available": null,
|
||||||
"hidden_if_item_available": null,
|
"hidden_if_item_available": null,
|
||||||
|
"hidden_if_item_available_mode": "hide",
|
||||||
"require_voucher": false,
|
"require_voucher": false,
|
||||||
"hide_without_voucher": false,
|
"hide_without_voucher": false,
|
||||||
"allow_cancel": true,
|
"allow_cancel": true,
|
||||||
@@ -855,6 +867,7 @@ Endpoints
|
|||||||
"available_until_mode": "hide",
|
"available_until_mode": "hide",
|
||||||
"hidden_if_available": null,
|
"hidden_if_available": null,
|
||||||
"hidden_if_item_available": null,
|
"hidden_if_item_available": null,
|
||||||
|
"hidden_if_item_available_mode": "hide",
|
||||||
"require_voucher": false,
|
"require_voucher": false,
|
||||||
"hide_without_voucher": false,
|
"hide_without_voucher": false,
|
||||||
"generate_tickets": null,
|
"generate_tickets": null,
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
|
|||||||
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
|
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
|
||||||
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
|
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
|
||||||
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
|
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
|
||||||
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'allow_waitinglist',
|
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist',
|
||||||
'issue_giftcard', 'meta_data',
|
'issue_giftcard', 'meta_data',
|
||||||
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
|
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
|
||||||
'grant_membership_duration_like_event', 'grant_membership_duration_days',
|
'grant_membership_duration_like_event', 'grant_membership_duration_days',
|
||||||
|
|||||||
@@ -1025,10 +1025,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
'autocomplete': 'address-level2',
|
'autocomplete': 'address-level2',
|
||||||
}),
|
}),
|
||||||
'company': forms.TextInput(attrs={
|
'company': forms.TextInput(attrs={
|
||||||
'data-display-dependency': '#id_is_business_1',
|
|
||||||
'autocomplete': 'organization',
|
'autocomplete': 'organization',
|
||||||
}),
|
}),
|
||||||
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
|
'vat_id': forms.TextInput(),
|
||||||
'internal_reference': forms.TextInput,
|
'internal_reference': forms.TextInput,
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
@@ -1059,8 +1058,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields["company"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_1'
|
self.fields["company"].widget.attrs["data-display-dependency"] = f'input[name="{self.add_prefix("is_business")}"][value="business"]'
|
||||||
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_1'
|
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'input[name="{self.add_prefix("is_business")}"][value="business"]'
|
||||||
|
|
||||||
if not self.ask_vat_id:
|
if not self.ask_vat_id:
|
||||||
del self.fields['vat_id']
|
del self.fields['vat_id']
|
||||||
@@ -1141,9 +1140,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
if self.address_required and not self.company_required and not self.all_optional:
|
if self.address_required and not self.company_required and not self.all_optional:
|
||||||
if not event.settings.invoice_name_required:
|
if not event.settings.invoice_name_required:
|
||||||
self.fields['name_parts'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_0'
|
self.fields['name_parts'].widget.attrs['data-required-if'] = f'input[name="{self.add_prefix("is_business")}"][value="individual"]'
|
||||||
self.fields['name_parts'].widget.attrs['data-no-required-attr'] = '1'
|
self.fields['name_parts'].widget.attrs['data-no-required-attr'] = '1'
|
||||||
self.fields['company'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_1'
|
self.fields['company'].widget.attrs['data-required-if'] = f'input[name="{self.add_prefix("is_business")}"][value="business"]'
|
||||||
|
|
||||||
if not event.settings.invoice_address_beneficiary:
|
if not event.settings.invoice_address_beneficiary:
|
||||||
del self.fields['beneficiary']
|
del self.fields['beneficiary']
|
||||||
|
|||||||
165
src/pretix/base/logentrytype_registry.py
Normal file
165
src/pretix/base/logentrytype_registry.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#
|
||||||
|
# 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,137 +19,20 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# 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/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from collections import defaultdict
|
from typing import Optional
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.html import escape
|
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||||
|
|
||||||
from pretix.base.signals import EventPluginRegistry
|
from pretix.base.models import (
|
||||||
|
Discount, Item, ItemCategory, Order, Question, Quota, SubEvent, TaxRule,
|
||||||
|
Voucher,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .logentrytype_registry import ( # noqa
|
||||||
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
|
ClearDataShredderMixin, LogEntryType, NoOpShredderMixin, log_entry_types,
|
||||||
if a_map:
|
make_link, LogEntryTypeRegistry,
|
||||||
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):
|
class EventLogEntryType(LogEntryType):
|
||||||
@@ -157,15 +40,27 @@ class EventLogEntryType(LogEntryType):
|
|||||||
Base class for any `LogEntry` type whose `content_object` is either an `Event` itself or belongs to a specific `Event`.
|
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) -> dict:
|
def get_object_link_info(self, logentry) -> Optional[dict]:
|
||||||
if hasattr(self, 'object_link_viewname') and logentry.content_object:
|
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
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'href': reverse(self.object_link_viewname, kwargs={
|
'href': reverse(self.object_link_viewname, kwargs={
|
||||||
'event': logentry.event.slug,
|
'event': logentry.event.slug,
|
||||||
'organizer': logentry.event.organizer.slug,
|
'organizer': logentry.event.organizer.slug,
|
||||||
**self.object_link_args(logentry.content_object),
|
**self.object_link_args(content),
|
||||||
}),
|
}),
|
||||||
'val': escape(self.object_link_display_name(logentry.content_object)),
|
'val': self.object_link_display_name(logentry.content_object),
|
||||||
}
|
}
|
||||||
|
|
||||||
def object_link_args(self, content_object):
|
def object_link_args(self, content_object):
|
||||||
@@ -182,6 +77,7 @@ class EventLogEntryType(LogEntryType):
|
|||||||
class OrderLogEntryType(EventLogEntryType):
|
class OrderLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = _('Order {val}')
|
object_link_wrapper = _('Order {val}')
|
||||||
object_link_viewname = 'control:event.order'
|
object_link_viewname = 'control:event.order'
|
||||||
|
content_type = Order
|
||||||
|
|
||||||
def object_link_args(self, order):
|
def object_link_args(self, order):
|
||||||
return {'code': order.code}
|
return {'code': order.code}
|
||||||
@@ -194,6 +90,7 @@ class VoucherLogEntryType(EventLogEntryType):
|
|||||||
object_link_wrapper = _('Voucher {val}…')
|
object_link_wrapper = _('Voucher {val}…')
|
||||||
object_link_viewname = 'control:event.voucher'
|
object_link_viewname = 'control:event.voucher'
|
||||||
object_link_argname = 'voucher'
|
object_link_argname = 'voucher'
|
||||||
|
content_type = Voucher
|
||||||
|
|
||||||
def object_link_display_name(self, voucher):
|
def object_link_display_name(self, voucher):
|
||||||
if len(voucher.code) > 6:
|
if len(voucher.code) > 6:
|
||||||
@@ -205,49 +102,46 @@ class ItemLogEntryType(EventLogEntryType):
|
|||||||
object_link_wrapper = _('Product {val}')
|
object_link_wrapper = _('Product {val}')
|
||||||
object_link_viewname = 'control:event.item'
|
object_link_viewname = 'control:event.item'
|
||||||
object_link_argname = 'item'
|
object_link_argname = 'item'
|
||||||
|
content_type = Item
|
||||||
|
|
||||||
|
|
||||||
class SubEventLogEntryType(EventLogEntryType):
|
class SubEventLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = pgettext_lazy('subevent', 'Date {val}')
|
object_link_wrapper = pgettext_lazy('subevent', 'Date {val}')
|
||||||
object_link_viewname = 'control:event.subevent'
|
object_link_viewname = 'control:event.subevent'
|
||||||
object_link_argname = 'subevent'
|
object_link_argname = 'subevent'
|
||||||
|
content_type = SubEvent
|
||||||
|
|
||||||
|
|
||||||
class QuotaLogEntryType(EventLogEntryType):
|
class QuotaLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = _('Quota {val}')
|
object_link_wrapper = _('Quota {val}')
|
||||||
object_link_viewname = 'control:event.items.quotas.show'
|
object_link_viewname = 'control:event.items.quotas.show'
|
||||||
object_link_argname = 'quota'
|
object_link_argname = 'quota'
|
||||||
|
content_type = Quota
|
||||||
|
|
||||||
|
|
||||||
class DiscountLogEntryType(EventLogEntryType):
|
class DiscountLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = _('Discount {val}')
|
object_link_wrapper = _('Discount {val}')
|
||||||
object_link_viewname = 'control:event.items.discounts.edit'
|
object_link_viewname = 'control:event.items.discounts.edit'
|
||||||
object_link_argname = 'discount'
|
object_link_argname = 'discount'
|
||||||
|
content_type = Discount
|
||||||
|
|
||||||
|
|
||||||
class ItemCategoryLogEntryType(EventLogEntryType):
|
class ItemCategoryLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = _('Category {val}')
|
object_link_wrapper = _('Category {val}')
|
||||||
object_link_viewname = 'control:event.items.categories.edit'
|
object_link_viewname = 'control:event.items.categories.edit'
|
||||||
object_link_argname = 'category'
|
object_link_argname = 'category'
|
||||||
|
content_type = ItemCategory
|
||||||
|
|
||||||
|
|
||||||
class QuestionLogEntryType(EventLogEntryType):
|
class QuestionLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = _('Question {val}')
|
object_link_wrapper = _('Question {val}')
|
||||||
object_link_viewname = 'control:event.items.questions.show'
|
object_link_viewname = 'control:event.items.questions.show'
|
||||||
object_link_argname = 'question'
|
object_link_argname = 'question'
|
||||||
|
content_type = Question
|
||||||
|
|
||||||
|
|
||||||
class TaxRuleLogEntryType(EventLogEntryType):
|
class TaxRuleLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = _('Tax rule {val}')
|
object_link_wrapper = _('Tax rule {val}')
|
||||||
object_link_viewname = 'control:event.settings.tax.edit'
|
object_link_viewname = 'control:event.settings.tax.edit'
|
||||||
object_link_argname = 'rule'
|
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
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -442,8 +442,12 @@ class Item(LoggedModel):
|
|||||||
UNAVAIL_MODE_INFO = "info"
|
UNAVAIL_MODE_INFO = "info"
|
||||||
UNAVAIL_MODES = (
|
UNAVAIL_MODES = (
|
||||||
(UNAVAIL_MODE_HIDDEN, _("Hide product if unavailable")),
|
(UNAVAIL_MODE_HIDDEN, _("Hide product if unavailable")),
|
||||||
(UNAVAIL_MODE_INFO, _("Show info text if unavailable")),
|
(UNAVAIL_MODE_INFO, _("Show product with info on why it’s unavailable")),
|
||||||
)
|
)
|
||||||
|
UNAVAIL_MODE_ICONS = {
|
||||||
|
UNAVAIL_MODE_HIDDEN: 'eye-slash',
|
||||||
|
UNAVAIL_MODE_INFO: 'info'
|
||||||
|
}
|
||||||
|
|
||||||
MEDIA_POLICY_REUSE = 'reuse'
|
MEDIA_POLICY_REUSE = 'reuse'
|
||||||
MEDIA_POLICY_NEW = 'new'
|
MEDIA_POLICY_NEW = 'new'
|
||||||
@@ -596,6 +600,11 @@ class Item(LoggedModel):
|
|||||||
"be a short period in which both products are visible while all tickets of the referenced "
|
"be a short period in which both products are visible while all tickets of the referenced "
|
||||||
"product are reserved, but not yet sold.")
|
"product are reserved, but not yet sold.")
|
||||||
)
|
)
|
||||||
|
hidden_if_item_available_mode = models.CharField(
|
||||||
|
choices=UNAVAIL_MODES,
|
||||||
|
default=UNAVAIL_MODE_HIDDEN,
|
||||||
|
max_length=16,
|
||||||
|
)
|
||||||
require_voucher = models.BooleanField(
|
require_voucher = models.BooleanField(
|
||||||
verbose_name=_('This product can only be bought using a voucher.'),
|
verbose_name=_('This product can only be bought using a voucher.'),
|
||||||
default=False,
|
default=False,
|
||||||
@@ -885,6 +894,8 @@ class Item(LoggedModel):
|
|||||||
return 'available_from'
|
return 'available_from'
|
||||||
elif subevent_item and subevent_item.available_until and subevent_item.available_until < now_dt:
|
elif subevent_item and subevent_item.available_until and subevent_item.available_until < now_dt:
|
||||||
return 'available_until'
|
return 'available_until'
|
||||||
|
elif self.hidden_if_item_available and self._dependency_available:
|
||||||
|
return 'hidden_if_item_available'
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
from pretix.base.logentrytypes import log_entry_types, make_link
|
from pretix.base.logentrytype_registry import log_entry_types, make_link
|
||||||
from pretix.base.signals import is_app_active, logentry_object_link
|
from pretix.base.signals import is_app_active, logentry_object_link
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ class LogEntry(models.Model):
|
|||||||
def display(self):
|
def display(self):
|
||||||
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
|
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
|
||||||
if log_entry_type:
|
if log_entry_type:
|
||||||
return log_entry_type.display(self)
|
return log_entry_type.display(self, self.parsed_data)
|
||||||
|
|
||||||
from ..signals import logentry_display
|
from ..signals import logentry_display
|
||||||
|
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ class Order(LockModel, LoggedModel):
|
|||||||
|
|
||||||
if not self.testmode:
|
if not self.testmode:
|
||||||
raise TypeError("Only test mode orders can be deleted.")
|
raise TypeError("Only test mode orders can be deleted.")
|
||||||
self.event.log_action(
|
self.log_action(
|
||||||
'pretix.event.order.deleted', user=user, auth=auth,
|
'pretix.event.order.deleted', user=user, auth=auth,
|
||||||
data={
|
data={
|
||||||
'code': self.code,
|
'code': self.code,
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class EventWizardBasicsForm(I18nModelForm):
|
|||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'timezone': _('Your default locale must be specified.')
|
'timezone': _('Your default locale must be specified.')
|
||||||
})
|
})
|
||||||
if not data.get("no_taxes") and not data.get("tax_rate"):
|
if not data.get("no_taxes") and data.get("tax_rate") is None:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'tax_rate': _('You have not specified a tax rate. If you do not want us to compute sales taxes, please '
|
'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)
|
'check "{field}" above.').format(field=self.fields["no_taxes"].label)
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ class ItemCreateForm(I18nModelForm):
|
|||||||
'show_quota_left',
|
'show_quota_left',
|
||||||
'hidden_if_available',
|
'hidden_if_available',
|
||||||
'hidden_if_item_available',
|
'hidden_if_item_available',
|
||||||
|
'hidden_if_item_available_mode',
|
||||||
'require_bundling',
|
'require_bundling',
|
||||||
'require_membership',
|
'require_membership',
|
||||||
'grant_membership_type',
|
'grant_membership_type',
|
||||||
@@ -646,18 +647,12 @@ class ItemUpdateForm(I18nModelForm):
|
|||||||
|
|
||||||
self.fields['available_from_mode'].widget = ButtonGroupRadioSelect(
|
self.fields['available_from_mode'].widget = ButtonGroupRadioSelect(
|
||||||
choices=self.fields['available_from_mode'].choices,
|
choices=self.fields['available_from_mode'].choices,
|
||||||
option_icons={
|
option_icons=Item.UNAVAIL_MODE_ICONS
|
||||||
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
|
|
||||||
Item.UNAVAIL_MODE_INFO: 'info'
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields['available_until_mode'].widget = ButtonGroupRadioSelect(
|
self.fields['available_until_mode'].widget = ButtonGroupRadioSelect(
|
||||||
choices=self.fields['available_until_mode'].choices,
|
choices=self.fields['available_until_mode'].choices,
|
||||||
option_icons={
|
option_icons=Item.UNAVAIL_MODE_ICONS
|
||||||
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
|
|
||||||
Item.UNAVAIL_MODE_INFO: 'info'
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields['hide_without_voucher'].widget = ButtonGroupRadioSelect(
|
self.fields['hide_without_voucher'].widget = ButtonGroupRadioSelect(
|
||||||
@@ -672,6 +667,11 @@ class ItemUpdateForm(I18nModelForm):
|
|||||||
attrs={'data-checkbox-dependency': '#id_require_voucher'}
|
attrs={'data-checkbox-dependency': '#id_require_voucher'}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.fields['hidden_if_item_available_mode'].widget = ButtonGroupRadioSelect(
|
||||||
|
choices=self.fields['hidden_if_item_available_mode'].choices,
|
||||||
|
option_icons=Item.UNAVAIL_MODE_ICONS
|
||||||
|
)
|
||||||
|
|
||||||
if self.instance.hidden_if_available_id:
|
if self.instance.hidden_if_available_id:
|
||||||
self.fields['hidden_if_available'].queryset = self.event.quotas.all()
|
self.fields['hidden_if_available'].queryset = self.event.quotas.all()
|
||||||
self.fields['hidden_if_available'].help_text = format_html(
|
self.fields['hidden_if_available'].help_text = format_html(
|
||||||
@@ -853,6 +853,7 @@ class ItemUpdateForm(I18nModelForm):
|
|||||||
'show_quota_left',
|
'show_quota_left',
|
||||||
'hidden_if_available',
|
'hidden_if_available',
|
||||||
'hidden_if_item_available',
|
'hidden_if_item_available',
|
||||||
|
'hidden_if_item_available_mode',
|
||||||
'issue_giftcard',
|
'issue_giftcard',
|
||||||
'require_membership',
|
'require_membership',
|
||||||
'require_membership_types',
|
'require_membership_types',
|
||||||
@@ -970,18 +971,12 @@ class ItemVariationForm(I18nModelForm):
|
|||||||
|
|
||||||
self.fields['available_from_mode'].widget = ButtonGroupRadioSelect(
|
self.fields['available_from_mode'].widget = ButtonGroupRadioSelect(
|
||||||
choices=self.fields['available_from_mode'].choices,
|
choices=self.fields['available_from_mode'].choices,
|
||||||
option_icons={
|
option_icons=Item.UNAVAIL_MODE_ICONS
|
||||||
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
|
|
||||||
Item.UNAVAIL_MODE_INFO: 'info'
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.fields['available_until_mode'].widget = ButtonGroupRadioSelect(
|
self.fields['available_until_mode'].widget = ButtonGroupRadioSelect(
|
||||||
choices=self.fields['available_until_mode'].choices,
|
choices=self.fields['available_until_mode'].choices,
|
||||||
option_icons={
|
option_icons=Item.UNAVAIL_MODE_ICONS
|
||||||
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
|
|
||||||
Item.UNAVAIL_MODE_INFO: 'info'
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.meta_fields = []
|
self.meta_fields = []
|
||||||
|
|||||||
@@ -33,16 +33,16 @@
|
|||||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# 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.
|
# License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
import json
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import bleach
|
import bleach
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape, format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
@@ -68,128 +68,212 @@ OVERVIEW_BANLIST = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def _display_order_changed(event: Event, logentry: LogEntry):
|
class OrderChangeLogEntryType(OrderLogEntryType):
|
||||||
data = json.loads(logentry.data)
|
prefix = _('The order has been changed:')
|
||||||
|
|
||||||
text = _('The order has been changed:')
|
def display(self, logentry, data):
|
||||||
if logentry.action_type == 'pretix.event.order.changed.item':
|
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):
|
||||||
old_item = str(event.items.get(pk=data['old_item']))
|
old_item = str(event.items.get(pk=data['old_item']))
|
||||||
if data['old_variation']:
|
if data['old_variation']:
|
||||||
old_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['old_variation']))
|
old_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['old_variation']))
|
||||||
new_item = str(event.items.get(pk=data['new_item']))
|
new_item = str(event.items.get(pk=data['new_item']))
|
||||||
if data['new_variation']:
|
if data['new_variation']:
|
||||||
new_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['new_variation']))
|
new_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['new_variation']))
|
||||||
return text + ' ' + _('Position #{posid}: {old_item} ({old_price}) changed '
|
return _('Position #{posid}: {old_item} ({old_price}) changed to {new_item} ({new_price}).').format(
|
||||||
'to {new_item} ({new_price}).').format(
|
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
old_item=old_item, new_item=new_item,
|
old_item=old_item, new_item=new_item,
|
||||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||||
)
|
)
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.membership':
|
|
||||||
return text + ' ' + _('Position #{posid}: Used membership changed.').format(
|
|
||||||
posid=data.get('positionid', '?'),
|
@log_entry_types.new()
|
||||||
)
|
class OrderMembershipChanged(OrderPositionChangeLogEntryType):
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.seat':
|
action_type = 'pretix.event.order.changed.membership'
|
||||||
return text + ' ' + _('Position #{posid}: Seat "{old_seat}" changed '
|
plain = _('Position #{posid}: Used membership changed.')
|
||||||
'to "{new_seat}".').format(
|
|
||||||
posid=data.get('positionid', '?'),
|
|
||||||
old_seat=data.get('old_seat'), new_seat=data.get('new_seat'),
|
@log_entry_types.new()
|
||||||
)
|
class OrderSeatChanged(OrderPositionChangeLogEntryType):
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.subevent':
|
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):
|
||||||
old_se = str(event.subevents.get(pk=data['old_subevent']))
|
old_se = str(event.subevents.get(pk=data['old_subevent']))
|
||||||
new_se = str(event.subevents.get(pk=data['new_subevent']))
|
new_se = str(event.subevents.get(pk=data['new_subevent']))
|
||||||
return text + ' ' + _('Position #{posid}: Event date "{old_event}" ({old_price}) changed '
|
return _('Position #{posid}: Event date "{old_event}" ({old_price}) changed '
|
||||||
'to "{new_event}" ({new_price}).').format(
|
'to "{new_event}" ({new_price}).').format(
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
old_event=old_se, new_event=new_se,
|
old_event=old_se, new_event=new_se,
|
||||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||||
)
|
)
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.price':
|
|
||||||
return text + ' ' + _('Price of position #{posid} changed from {old_price} '
|
|
||||||
'to {new_price}.').format(
|
@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(
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||||
)
|
)
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.tax_rule':
|
|
||||||
|
|
||||||
|
@log_entry_types.new()
|
||||||
|
class OrderTaxRuleChanged(OrderChangeLogEntryType):
|
||||||
|
action_type = 'pretix.event.order.changed.tax_rule'
|
||||||
|
|
||||||
|
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||||
if 'positionid' in data:
|
if 'positionid' in data:
|
||||||
return text + ' ' + _('Tax rule of position #{posid} changed from {old_rule} '
|
return _('Tax rule of position #{posid} changed from {old_rule} to {new_rule}.').format(
|
||||||
'to {new_rule}.').format(
|
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||||
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
||||||
)
|
)
|
||||||
elif 'fee' in data:
|
elif 'fee' in data:
|
||||||
return text + ' ' + _('Tax rule of fee #{fee} changed from {old_rule} '
|
return _('Tax rule of fee #{fee} changed from {old_rule} to {new_rule}.').format(
|
||||||
'to {new_rule}.').format(
|
|
||||||
fee=data.get('fee', '?'),
|
fee=data.get('fee', '?'),
|
||||||
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '–',
|
||||||
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
|
||||||
)
|
)
|
||||||
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':
|
@log_entry_types.new()
|
||||||
return text + ' ' + _('A fee was changed from {old_price} to {new_price}.').format(
|
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(
|
||||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||||
)
|
)
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.cancelfee':
|
|
||||||
return text + ' ' + _('A fee of {old_price} was removed.').format(
|
|
||||||
|
@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(
|
||||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||||
)
|
)
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.cancel':
|
|
||||||
|
|
||||||
|
@log_entry_types.new()
|
||||||
|
class OrderCanceled(OrderChangeLogEntryType):
|
||||||
|
action_type = 'pretix.event.order.changed.cancel'
|
||||||
|
|
||||||
|
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||||
old_item = str(event.items.get(pk=data['old_item']))
|
old_item = str(event.items.get(pk=data['old_item']))
|
||||||
if data['old_variation']:
|
if data['old_variation']:
|
||||||
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
||||||
return text + ' ' + _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
|
return _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
old_item=old_item,
|
old_item=old_item,
|
||||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||||
)
|
)
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.add':
|
|
||||||
|
|
||||||
|
@log_entry_types.new()
|
||||||
|
class OrderPositionAdded(OrderChangeLogEntryType):
|
||||||
|
action_type = 'pretix.event.order.changed.add'
|
||||||
|
|
||||||
|
def display_prefixed(self, event: Event, logentry: LogEntry, data):
|
||||||
item = str(event.items.get(pk=data['item']))
|
item = str(event.items.get(pk=data['item']))
|
||||||
if data['variation']:
|
if data['variation']:
|
||||||
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
|
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
|
||||||
if data['addon_to']:
|
if data['addon_to']:
|
||||||
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
|
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
|
||||||
return text + ' ' + _('Position #{posid} created: {item} ({price}) as an add-on to '
|
return _('Position #{posid} created: {item} ({price}) as an add-on to position #{addon_to}.').format(
|
||||||
'position #{addon_to}.').format(
|
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
item=item, addon_to=addon_to.positionid,
|
item=item, addon_to=addon_to.positionid,
|
||||||
price=money_filter(Decimal(data['price']), event.currency),
|
price=money_filter(Decimal(data['price']), event.currency),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return text + ' ' + _('Position #{posid} created: {item} ({price}).').format(
|
return _('Position #{posid} created: {item} ({price}).').format(
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
item=item,
|
item=item,
|
||||||
price=money_filter(Decimal(data['price']), event.currency),
|
price=money_filter(Decimal(data['price']), event.currency),
|
||||||
)
|
)
|
||||||
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', '?'),
|
@log_entry_types.new()
|
||||||
)
|
class OrderSecretChanged(OrderPositionChangeLogEntryType):
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.valid_from':
|
action_type = 'pretix.event.order.changed.secret'
|
||||||
return text + ' ' + _('The validity start date for position #{posid} has been changed to {value}.').format(
|
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(
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get(
|
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get(
|
||||||
'new_value') else '–'
|
'new_value') else '–'
|
||||||
)
|
)
|
||||||
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(
|
|
||||||
|
@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(
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get('new_value') else '–'
|
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get('new_value') else '–'
|
||||||
)
|
)
|
||||||
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', '?'),
|
@log_entry_types.new()
|
||||||
)
|
class OrderChangedBlockAdded(OrderPositionChangeLogEntryType):
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.remove_block':
|
action_type = 'pretix.event.order.changed.add_block'
|
||||||
return text + ' ' + _('A block has been removed for position #{posid}.').format(
|
plain = _('A block has been added for position #{posid}.')
|
||||||
posid=data.get('positionid', '?'),
|
|
||||||
)
|
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.split':
|
@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):
|
||||||
old_item = str(event.items.get(pk=data['old_item']))
|
old_item = str(event.items.get(pk=data['old_item']))
|
||||||
if data['old_variation']:
|
if data['old_variation']:
|
||||||
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
|
||||||
@@ -198,194 +282,144 @@ def _display_order_changed(event: Event, logentry: LogEntry):
|
|||||||
'organizer': event.organizer.slug,
|
'organizer': event.organizer.slug,
|
||||||
'code': data['new_order']
|
'code': data['new_order']
|
||||||
})
|
})
|
||||||
return mark_safe(escape(text) + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
|
return mark_safe(self.prefix + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
|
||||||
old_item=escape(old_item),
|
old_item=escape(old_item),
|
||||||
posid=data.get('positionid', '?'),
|
posid=data.get('positionid', '?'),
|
||||||
order='<a href="{}">{}</a>'.format(url, data['new_order']),
|
order='<a href="{}">{}</a>'.format(url, data['new_order']),
|
||||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||||
))
|
))
|
||||||
elif logentry.action_type == 'pretix.event.order.changed.split_from':
|
|
||||||
|
|
||||||
|
@log_entry_types.new()
|
||||||
|
class OrderChangedSplitFrom(OrderLogEntryType):
|
||||||
|
action_type = 'pretix.event.order.changed.split_from'
|
||||||
|
|
||||||
|
def display(self, logentry: LogEntry, data):
|
||||||
return _('This order has been created by splitting the order {order}').format(
|
return _('This order has been created by splitting the order {order}').format(
|
||||||
order=data['original_order'],
|
order=data['original_order'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _display_checkin(event, logentry):
|
@log_entry_types.new_from_dict({
|
||||||
data = logentry.parsed_data
|
'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)
|
||||||
|
|
||||||
show_dt = False
|
def display_plain(self, plain, logentry: LogEntry, data):
|
||||||
if 'datetime' in data:
|
if isinstance(plain, tuple):
|
||||||
dt = dateutil.parser.parse(data.get('datetime'))
|
plain_with_dt, plain_without_dt = plain
|
||||||
show_dt = abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data
|
else:
|
||||||
tz = event.timezone
|
plain_with_dt, plain_without_dt = plain, plain
|
||||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
|
||||||
|
|
||||||
if 'list' in data:
|
data = defaultdict(lambda: '?', data)
|
||||||
try:
|
|
||||||
checkin_list = event.checkin_lists.get(pk=data.get('list')).name
|
|
||||||
except CheckinList.DoesNotExist:
|
|
||||||
checkin_list = _("(unknown)")
|
|
||||||
else:
|
|
||||||
checkin_list = _("(unknown)")
|
|
||||||
|
|
||||||
if logentry.action_type == 'pretix.event.checkin.unknown':
|
event = logentry.event
|
||||||
if show_dt:
|
|
||||||
return _(
|
if 'list' in data:
|
||||||
'Unknown scan of code "{barcode}…" at {datetime} for list "{list}", type "{type}".'
|
try:
|
||||||
).format(
|
data['list'] = event.checkin_lists.get(pk=data.get('list')).name
|
||||||
posid=data.get('positionid'),
|
except CheckinList.DoesNotExist:
|
||||||
type=data.get('type'),
|
data['list'] = _("(unknown)")
|
||||||
barcode=data.get('barcode')[:16],
|
else:
|
||||||
datetime=dt_formatted,
|
data['list'] = _("(unknown)")
|
||||||
list=checkin_list
|
|
||||||
|
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
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return _(
|
return self.display_plain(
|
||||||
'Unknown scan of code "{barcode}…" for list "{list}", type "{type}".'
|
_('Position #{posid} has been scanned and rejected because it has already been scanned before '
|
||||||
).format(
|
'on list "{list}".'),
|
||||||
posid=data.get('positionid'),
|
logentry, data
|
||||||
type=data.get('type'),
|
|
||||||
barcode=data.get('barcode')[:16],
|
|
||||||
list=checkin_list
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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 _(
|
|
||||||
'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':
|
class OrderConsentLogEntryType(OrderLogEntryType):
|
||||||
if show_dt:
|
action_type = 'pretix.event.order.consent'
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
if data.get('type') == Checkin.TYPE_EXIT:
|
def display(self, logentry: LogEntry, data):
|
||||||
if show_dt:
|
return _('The user confirmed the following message: "{}"').format(
|
||||||
return _('Position #{posid} has been checked out at {datetime} for list "{list}".').format(
|
bleach.clean(data.get('msg'), tags=set(), strip=True)
|
||||||
posid=data.get('positionid'),
|
)
|
||||||
datetime=dt_formatted,
|
|
||||||
list=checkin_list
|
|
||||||
)
|
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)
|
||||||
else:
|
else:
|
||||||
return _('Position #{posid} has been checked out for list "{list}".').format(
|
return _('The order has been canceled.')
|
||||||
posid=data.get('positionid'),
|
|
||||||
list=checkin_list
|
|
||||||
)
|
class OrderPrintLogEntryType(OrderLogEntryType):
|
||||||
if data.get('first'):
|
action_type = 'pretix.event.order.print'
|
||||||
if show_dt:
|
|
||||||
return _('Position #{posid} has been checked in at {datetime} for list "{list}".').format(
|
def display(self, logentry: LogEntry, data):
|
||||||
posid=data.get('positionid'),
|
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
|
||||||
datetime=dt_formatted,
|
posid=data.get('positionid'),
|
||||||
list=checkin_list
|
datetime=date_format(
|
||||||
)
|
dateutil.parser.parse(data["datetime"]).astimezone(logentry.event.timezone),
|
||||||
else:
|
"SHORT_DATETIME_FORMAT"
|
||||||
return _('Position #{posid} has been checked in for list "{list}".').format(
|
),
|
||||||
posid=data.get('positionid'),
|
type=dict(PrintLog.PRINT_TYPES)[data["type"]],
|
||||||
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")
|
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
|
||||||
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
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.'):
|
if logentry.action_type.startswith('pretix.event.payment.provider.'):
|
||||||
return _('The settings of a payment provider have been changed.')
|
return _('The settings of a payment provider have been changed.')
|
||||||
|
|
||||||
if logentry.action_type.startswith('pretix.event.tickets.provider.'):
|
if logentry.action_type.startswith('pretix.event.tickets.provider.'):
|
||||||
return _('The settings of a ticket output provider have been changed.')
|
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")
|
@receiver(signal=orderposition_blocked_display, dispatch_uid="pretixcontrol_orderposition_blocked_display")
|
||||||
def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, block_name, **kwargs):
|
def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, block_name, **kwargs):
|
||||||
@@ -396,6 +430,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
|
|||||||
|
|
||||||
|
|
||||||
@log_entry_types.new_from_dict({
|
@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.modified': _('The order details have been changed.'),
|
||||||
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
||||||
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
||||||
@@ -486,17 +521,16 @@ class VoucherRedeemedLogEntryType(VoucherLogEntryType):
|
|||||||
action_type = 'pretix.voucher.redeemed'
|
action_type = 'pretix.voucher.redeemed'
|
||||||
plain = _('The voucher has been redeemed in order {order_code}.')
|
plain = _('The voucher has been redeemed in order {order_code}.')
|
||||||
|
|
||||||
def display(self, logentry):
|
def display(self, logentry, data):
|
||||||
data = json.loads(logentry.data)
|
|
||||||
data = defaultdict(lambda: '?', data)
|
|
||||||
url = reverse('control:event.order', kwargs={
|
url = reverse('control:event.order', kwargs={
|
||||||
'event': logentry.event.slug,
|
'event': logentry.event.slug,
|
||||||
'organizer': logentry.event.organizer.slug,
|
'organizer': logentry.event.organizer.slug,
|
||||||
'code': data['order_code']
|
'code': data.get('order_code', '?')
|
||||||
})
|
})
|
||||||
return mark_safe(self.plain.format(
|
return format_html(
|
||||||
order_code='<a href="{}">{}</a>'.format(url, data['order_code']),
|
self.plain,
|
||||||
))
|
order_code=format_html('<a href="{}">{}</a>', url, data('order_code', '?')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@log_entry_types.new_from_dict({
|
@log_entry_types.new_from_dict({
|
||||||
@@ -519,8 +553,8 @@ class CoreTaxRuleLogEntryType(TaxRuleLogEntryType):
|
|||||||
|
|
||||||
|
|
||||||
class TeamMembershipLogEntryType(LogEntryType):
|
class TeamMembershipLogEntryType(LogEntryType):
|
||||||
def display(self, logentry):
|
def display(self, logentry, data):
|
||||||
return self.plain.format(user=logentry.parsed_data.get('email'))
|
return self.plain.format(user=data.get('email'))
|
||||||
|
|
||||||
|
|
||||||
@log_entry_types.new_from_dict({
|
@log_entry_types.new_from_dict({
|
||||||
@@ -537,9 +571,9 @@ class CoreTeamMembershipLogEntryType(TeamMembershipLogEntryType):
|
|||||||
class TeamMemberJoinedLogEntryType(LogEntryType):
|
class TeamMemberJoinedLogEntryType(LogEntryType):
|
||||||
action_type = 'pretix.team.member.joined'
|
action_type = 'pretix.team.member.joined'
|
||||||
|
|
||||||
def display(self, logentry):
|
def display(self, logentry, data):
|
||||||
return _('{user} has joined the team using the invite sent to {email}.').format(
|
return _('{user} has joined the team using the invite sent to {email}.').format(
|
||||||
user=logentry.parsed_data.get('email'), email=logentry.parsed_data.get('invite_email')
|
user=data.get('email'), email=data.get('invite_email')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -547,23 +581,23 @@ class TeamMemberJoinedLogEntryType(LogEntryType):
|
|||||||
class UserSettingsChangedLogEntryType(LogEntryType):
|
class UserSettingsChangedLogEntryType(LogEntryType):
|
||||||
action_type = 'pretix.user.settings.changed'
|
action_type = 'pretix.user.settings.changed'
|
||||||
|
|
||||||
def display(self, logentry):
|
def display(self, logentry, data):
|
||||||
text = str(_('Your account settings have been changed.'))
|
text = str(_('Your account settings have been changed.'))
|
||||||
if 'email' in logentry.parsed_data:
|
if 'email' in data:
|
||||||
text = text + ' ' + str(
|
text = text + ' ' + str(
|
||||||
_('Your email address has been changed to {email}.').format(email=logentry.parsed_data['email']))
|
_('Your email address has been changed to {email}.').format(email=data['email']))
|
||||||
if 'new_pw' in logentry.parsed_data:
|
if 'new_pw' in data:
|
||||||
text = text + ' ' + str(_('Your password has been changed.'))
|
text = text + ' ' + str(_('Your password has been changed.'))
|
||||||
if logentry.parsed_data.get('is_active') is True:
|
if data.get('is_active') is True:
|
||||||
text = text + ' ' + str(_('Your account has been enabled.'))
|
text = text + ' ' + str(_('Your account has been enabled.'))
|
||||||
elif logentry.parsed_data.get('is_active') is False:
|
elif data.get('is_active') is False:
|
||||||
text = text + ' ' + str(_('Your account has been disabled.'))
|
text = text + ' ' + str(_('Your account has been disabled.'))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
class UserImpersonatedLogEntryType(LogEntryType):
|
class UserImpersonatedLogEntryType(LogEntryType):
|
||||||
def display(self, logentry):
|
def display(self, logentry, data):
|
||||||
return self.plain.format(logentry.parsed_data['other_email'])
|
return self.plain.format(data['other_email'])
|
||||||
|
|
||||||
|
|
||||||
@log_entry_types.new_from_dict({
|
@log_entry_types.new_from_dict({
|
||||||
@@ -688,16 +722,13 @@ class CoreLogEntryType(LogEntryType):
|
|||||||
|
|
||||||
|
|
||||||
@log_entry_types.new_from_dict({
|
@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.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.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.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.settings': _('The event settings have been changed.'),
|
||||||
'pretix.event.tickets.settings': _('The ticket download 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.activated': _('The shop has been taken live.'),
|
||||||
'pretix.event.live.deactivated': _('The shop has been taken offline.'),
|
'pretix.event.live.deactivated': _('The shop has been taken offline.'),
|
||||||
'pretix.event.testmode.activated': _('The shop has been taken into test mode.'),
|
'pretix.event.testmode.activated': _('The shop has been taken into test mode.'),
|
||||||
@@ -717,6 +748,19 @@ class CoreEventLogEntryType(EventLogEntryType):
|
|||||||
pass
|
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({
|
@log_entry_types.new_from_dict({
|
||||||
'pretix.event.plugins.enabled': _('The plugin has been enabled.'),
|
'pretix.event.plugins.enabled': _('The plugin has been enabled.'),
|
||||||
'pretix.event.plugins.disabled': _('The plugin has been disabled.'),
|
'pretix.event.plugins.disabled': _('The plugin has been disabled.'),
|
||||||
@@ -724,7 +768,7 @@ class CoreEventLogEntryType(EventLogEntryType):
|
|||||||
class EventPluginStateLogEntryType(EventLogEntryType):
|
class EventPluginStateLogEntryType(EventLogEntryType):
|
||||||
object_link_wrapper = _('Plugin {val}')
|
object_link_wrapper = _('Plugin {val}')
|
||||||
|
|
||||||
def get_object_link_info(self, logentry) -> dict:
|
def get_object_link_info(self, logentry) -> Optional[dict]:
|
||||||
if 'plugin' in logentry.parsed_data:
|
if 'plugin' in logentry.parsed_data:
|
||||||
app = app_cache.get(logentry.parsed_data['plugin'])
|
app = app_cache.get(logentry.parsed_data['plugin'])
|
||||||
if app and hasattr(app, 'PretixPluginMeta'):
|
if app and hasattr(app, 'PretixPluginMeta'):
|
||||||
@@ -759,17 +803,17 @@ class CoreItemLogEntryType(ItemLogEntryType):
|
|||||||
'pretix.event.item.variation.changed': _('The variation "{value}" has been changed.'),
|
'pretix.event.item.variation.changed': _('The variation "{value}" has been changed.'),
|
||||||
})
|
})
|
||||||
class VariationLogEntryType(ItemLogEntryType):
|
class VariationLogEntryType(ItemLogEntryType):
|
||||||
def display(self, logentry):
|
def display(self, logentry, data):
|
||||||
if 'value' not in logentry.parsed_data:
|
if 'value' not in data:
|
||||||
# Backwards compatibility
|
# Backwards compatibility
|
||||||
var = ItemVariation.objects.filter(id=logentry.parsed_data['id']).first()
|
var = ItemVariation.objects.filter(id=data['id']).first()
|
||||||
if var:
|
if var:
|
||||||
logentry.parsed_data['value'] = str(var.value)
|
data['value'] = str(var.value)
|
||||||
else:
|
else:
|
||||||
logentry.parsed_data['value'] = '?'
|
data['value'] = '?'
|
||||||
else:
|
else:
|
||||||
logentry.parsed_data['value'] = LazyI18nString(logentry.parsed_data['value'])
|
data['value'] = LazyI18nString(data['value'])
|
||||||
return super().display(logentry)
|
return super().display(logentry, data)
|
||||||
|
|
||||||
|
|
||||||
@log_entry_types.new_from_dict({
|
@log_entry_types.new_from_dict({
|
||||||
@@ -825,27 +869,27 @@ class CoreDiscountLogEntryType(DiscountLogEntryType):
|
|||||||
class LegacyCheckinLogEntryType(OrderLogEntryType):
|
class LegacyCheckinLogEntryType(OrderLogEntryType):
|
||||||
action_type = 'pretix.control.views.checkin'
|
action_type = 'pretix.control.views.checkin'
|
||||||
|
|
||||||
def display(self, logentry):
|
def display(self, logentry, data):
|
||||||
# deprecated
|
# deprecated
|
||||||
dt = dateutil.parser.parse(logentry.parsed_data.get('datetime'))
|
dt = dateutil.parser.parse(data.get('datetime'))
|
||||||
tz = logentry.event.timezone
|
tz = logentry.event.timezone
|
||||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||||
if 'list' in logentry.parsed_data:
|
if 'list' in data:
|
||||||
try:
|
try:
|
||||||
checkin_list = logentry.event.checkin_lists.get(pk=logentry.parsed_data.get('list')).name
|
checkin_list = logentry.event.checkin_lists.get(pk=data.get('list')).name
|
||||||
except CheckinList.DoesNotExist:
|
except CheckinList.DoesNotExist:
|
||||||
checkin_list = _("(unknown)")
|
checkin_list = _("(unknown)")
|
||||||
else:
|
else:
|
||||||
checkin_list = _("(unknown)")
|
checkin_list = _("(unknown)")
|
||||||
|
|
||||||
if logentry.parsed_data.get('first'):
|
if data.get('first'):
|
||||||
return _('Position #{posid} has been checked in manually at {datetime} on list "{list}".').format(
|
return _('Position #{posid} has been checked in manually at {datetime} on list "{list}".').format(
|
||||||
posid=logentry.parsed_data.get('positionid'),
|
posid=data.get('positionid'),
|
||||||
datetime=dt_formatted,
|
datetime=dt_formatted,
|
||||||
list=checkin_list,
|
list=checkin_list,
|
||||||
)
|
)
|
||||||
return _('Position #{posid} has been checked in again at {datetime} on list "{list}".').format(
|
return _('Position #{posid} has been checked in again at {datetime} on list "{list}".').format(
|
||||||
posid=logentry.parsed_data.get('positionid'),
|
posid=data.get('positionid'),
|
||||||
datetime=dt_formatted,
|
datetime=dt_formatted,
|
||||||
list=checkin_list
|
list=checkin_list
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -172,7 +172,7 @@
|
|||||||
{% if form.hidden_if_available %}
|
{% if form.hidden_if_available %}
|
||||||
{% bootstrap_field form.hidden_if_available layout="control" horizontal_field_class="col-md-7" %}
|
{% bootstrap_field form.hidden_if_available layout="control" horizontal_field_class="col-md-7" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_field form.hidden_if_item_available layout="control" horizontal_field_class="col-md-7" %}
|
{% bootstrap_field form.hidden_if_item_available visibility_field=form.hidden_if_item_available_mode layout="control_with_visibility" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% for v in formsets.values %}
|
{% for v in formsets.values %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
{% if mode == "hide" %}
|
{% if mode == "hide" %}
|
||||||
<span class="pull-right text-muted unavail-mode-indicator" data-toggle="tooltip" title="{% trans "Hide product if unavailable" %}. {% if f.variation %}{% trans "You can change this option in the variation settings." %}{% else %}{% trans "You can change this option in the product settings." %}{% endif %}"><span class="fa fa-eye-slash"></span></span>
|
<span class="pull-right text-muted unavail-mode-indicator" data-toggle="tooltip" title="{% trans "Hide product if unavailable" %}. {% if f.variation %}{% trans "You can change this option in the variation settings." %}{% else %}{% trans "You can change this option in the product settings." %}{% endif %}"><span class="fa fa-eye-slash"></span></span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="pull-right text-muted unavail-mode-indicator" data-toggle="tooltip" title="{% trans "Show info text if unavailable" %}. {% if f.variation %}{% trans "You can change this option in the variation settings." %}{% else %}{% trans "You can change this option in the product settings." %}{% endif %}"><span class="fa fa-info-circle"></span></span>
|
<span class="pull-right text-muted unavail-mode-indicator" data-toggle="tooltip" title="{% trans "Show product with info on why it’s unavailable" %}. {% if f.variation %}{% trans "You can change this option in the variation settings." %}{% else %}{% trans "You can change this option in the product settings." %}{% endif %}"><span class="fa fa-info-circle"></span></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -491,8 +491,11 @@ class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMix
|
|||||||
if self.form.is_valid():
|
if self.form.is_valid():
|
||||||
if self.form.has_changed():
|
if self.form.has_changed():
|
||||||
self.request.event.log_action(
|
self.request.event.log_action(
|
||||||
'pretix.event.payment.provider.' + self.provider.identifier, user=self.request.user, data={
|
'pretix.event.payment.provider', user=self.request.user, data={
|
||||||
k: self.form.cleaned_data.get(k) for k in self.form.changed_data
|
'provider': self.provider.identifier,
|
||||||
|
'new_values': {
|
||||||
|
k: self.form.cleaned_data.get(k) for k in self.form.changed_data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.form.save()
|
self.form.save()
|
||||||
@@ -888,11 +891,14 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
|
|||||||
provider.form.save()
|
provider.form.save()
|
||||||
if provider.form.has_changed():
|
if provider.form.has_changed():
|
||||||
self.request.event.log_action(
|
self.request.event.log_action(
|
||||||
'pretix.event.tickets.provider.' + provider.identifier, user=self.request.user, data={
|
'pretix.event.tickets.provider', user=self.request.user, data={
|
||||||
k: (provider.form.cleaned_data.get(k).name
|
'provider': provider.identifier,
|
||||||
if isinstance(provider.form.cleaned_data.get(k), File)
|
'new_values': {
|
||||||
else provider.form.cleaned_data.get(k))
|
k: (provider.form.cleaned_data.get(k).name
|
||||||
for k in provider.form.changed_data
|
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})
|
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'provider': provider.identifier})
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ class EventWizard(SafeSessionWizardView):
|
|||||||
)
|
)
|
||||||
event.set_defaults()
|
event.set_defaults()
|
||||||
|
|
||||||
if basics_data['tax_rate']:
|
if basics_data['tax_rate'] is not None:
|
||||||
if not event.settings.tax_rate_default or event.settings.tax_rate_default.rate != 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(
|
event.settings.tax_rate_default = event.tax_rules.create(
|
||||||
name=LazyI18nString.from_gettext(gettext('VAT')),
|
name=LazyI18nString.from_gettext(gettext('VAT')),
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||||
<p><small><a href="#voucher">{% trans "Enter a voucher code below to buy this product." %}</a></small></p>
|
<p><small><a href="#voucher">{% trans "Enter a voucher code below to buy this product." %}</a></small></p>
|
||||||
</div>
|
</div>
|
||||||
|
{% elif item.current_unavailability_reason == 'hidden_if_item_available' %}
|
||||||
|
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||||
|
<p><small>{% trans "Not available yet." %}</small></p>
|
||||||
|
</div>
|
||||||
{% elif item.current_unavailability_reason == 'available_from' or var.current_unavailability_reason == 'available_from' %}
|
{% elif item.current_unavailability_reason == 'available_from' or var.current_unavailability_reason == 'available_from' %}
|
||||||
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
|
||||||
<p><small>{% trans "Not available yet." %}</small></p>
|
<p><small>{% trans "Not available yet." %}</small></p>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ from pretix.base.models import (
|
|||||||
)
|
)
|
||||||
from pretix.base.models.event import Event, SubEvent
|
from pretix.base.models.event import Event, SubEvent
|
||||||
from pretix.base.models.items import (
|
from pretix.base.models.items import (
|
||||||
ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
|
Item, ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
|
||||||
)
|
)
|
||||||
from pretix.base.services.placeholders import PlaceholderContext
|
from pretix.base.services.placeholders import PlaceholderContext
|
||||||
from pretix.base.services.quotas import QuotaAvailability
|
from pretix.base.services.quotas import QuotaAvailability
|
||||||
@@ -302,14 +302,14 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
|
|||||||
|
|
||||||
if item.hidden_if_item_available:
|
if item.hidden_if_item_available:
|
||||||
if item.hidden_if_item_available.has_variations:
|
if item.hidden_if_item_available.has_variations:
|
||||||
dependency_available = any(
|
item._dependency_available = any(
|
||||||
var.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)[0] == Quota.AVAILABILITY_OK
|
var.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)[0] == Quota.AVAILABILITY_OK
|
||||||
for var in item.hidden_if_item_available.available_variations
|
for var in item.hidden_if_item_available.available_variations
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
|
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
|
||||||
dependency_available = q[0] == Quota.AVAILABILITY_OK
|
item._dependency_available = q[0] == Quota.AVAILABILITY_OK
|
||||||
if dependency_available:
|
if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN:
|
||||||
item._remove = True
|
item._remove = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ var strings = {
|
|||||||
'unavailable_available_from': django.pgettext('widget', 'Not yet available'),
|
'unavailable_available_from': django.pgettext('widget', 'Not yet available'),
|
||||||
'unavailable_available_until': django.pgettext('widget', 'Not available anymore'),
|
'unavailable_available_until': django.pgettext('widget', 'Not available anymore'),
|
||||||
'unavailable_active': django.pgettext('widget', 'Currently not available'),
|
'unavailable_active': django.pgettext('widget', 'Currently not available'),
|
||||||
|
'unavailable_hidden_if_item_available': django.pgettext('widget', 'Not yet available'),
|
||||||
'order_min': django.pgettext('widget', 'minimum amount to order: %s'),
|
'order_min': django.pgettext('widget', 'minimum amount to order: %s'),
|
||||||
'exit': django.pgettext('widget', 'Close ticket shop'),
|
'exit': django.pgettext('widget', 'Close ticket shop'),
|
||||||
'loading_error': django.pgettext('widget', 'The ticket shop could not be loaded.'),
|
'loading_error': django.pgettext('widget', 'The ticket shop could not be loaded.'),
|
||||||
|
|||||||
@@ -323,6 +323,7 @@ TEST_ITEM_RES = {
|
|||||||
"max_per_order": None,
|
"max_per_order": None,
|
||||||
"hidden_if_available": None,
|
"hidden_if_available": None,
|
||||||
"hidden_if_item_available": None,
|
"hidden_if_item_available": None,
|
||||||
|
"hidden_if_item_available_mode": "hide",
|
||||||
"checkin_attention": False,
|
"checkin_attention": False,
|
||||||
"checkin_text": None,
|
"checkin_text": None,
|
||||||
"has_variations": False,
|
"has_variations": False,
|
||||||
|
|||||||
Reference in New Issue
Block a user