forked from CGM_Public/pretix_original
Define LogEntryTypes for all actions in pretix core, improve content_object handling (#4768)
Create LogEntryType definitions for all missing action_types (order changes, check-in events, settings changes of PaymentProviders and TicketOutputs). Check whether the stored content_object is of the expected model type, preventing incorrect links. Refactoring: - Move the base LogEntryType definitions for our models to their own file - Move HTML escaping into make_link to make it less likely to oversee in the LogEntryType definitions - Log pretix.event.order.deleted with the deleted Order model as content_object, matching the other *.deleted action_types
This commit is contained in:
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
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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})
|
||||||
|
|||||||
Reference in New Issue
Block a user