store plugin name for registered types

This commit is contained in:
Mira Weller
2024-06-18 15:40:47 +02:00
parent 166f50fcb0
commit 9269a485a6
8 changed files with 189 additions and 206 deletions

View File

@@ -33,6 +33,7 @@
# License for the specific language governing permissions and limitations under the License.
import json
import logging
from collections import defaultdict
from django.contrib.contenttypes.fields import GenericForeignKey
@@ -43,7 +44,7 @@ from django.utils.functional import cached_property
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.signals import logentry_object_link
from pretix.base.signals import logentry_object_link, EventPluginRegistry
class VisibleOnlyManager(models.Manager):
@@ -51,6 +52,27 @@ class VisibleOnlyManager(models.Manager):
return super().get_queryset().filter(visible=True)
def make_link(a_map, wrapper, is_active=True, event=None):
if a_map:
if is_active:
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
elif event:
a_map['val'] = '<i>{val}</i> <a href="{plugin_href}"><span data-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,
}),
})
else:
a_map['val'] = '<i>{val}</i> <span data-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 LogEntry(models.Model):
"""
Represents a change or action that has been performed on another object
@@ -93,7 +115,7 @@ class LogEntry(models.Model):
indexes = [models.Index(fields=["datetime", "id"])]
def display(self):
log_entry_type = log_entry_types.find(action_type=self.action_type)
log_entry_type, meta = log_entry_types.find(action_type=self.action_type)
if log_entry_type:
return log_entry_type.display(self)
@@ -142,92 +164,19 @@ class LogEntry(models.Model):
co = self.content_object
except:
return ''
a_map = None
a_text = None
if isinstance(co, Order):
a_text = _('Order {val}')
a_map = {
'href': reverse('control:event.order', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'code': co.code
}),
'val': escape(co.code),
}
elif isinstance(co, Voucher):
a_text = _('Voucher {val}')
a_map = {
'href': reverse('control:event.voucher', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'voucher': co.id
}),
'val': escape(co.code[:6]),
}
elif isinstance(co, Item):
a_text = _('Product {val}')
a_map = {
'href': reverse('control:event.item', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'item': co.id
}),
'val': escape(co.name),
}
elif isinstance(co, SubEvent):
a_text = pgettext_lazy('subevent', 'Date {val}')
a_map = {
'href': reverse('control:event.subevent', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'subevent': co.id
}),
'val': escape(str(co))
}
elif isinstance(co, Quota):
a_text = _('Quota {val}')
a_map = {
'href': reverse('control:event.items.quotas.show', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'quota': co.id
}),
'val': escape(co.name),
}
elif isinstance(co, Discount):
a_text = _('Discount {val}')
a_map = {
'href': reverse('control:event.items.discounts.edit', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'discount': co.id
}),
'val': escape(co.internal_name),
}
elif isinstance(co, Question):
a_text = _('Question {val}')
a_map = {
'href': reverse('control:event.items.questions.show', kwargs={
'event': self.event.slug,
'organizer': self.event.organizer.slug,
'question': co.id
}),
'val': escape(co.question),
}
log_entry_type, meta = log_entry_types.find(action_type=self.action_type)
if log_entry_type:
return log_entry_type.get_object_link(self, is_active=meta['plugin'] == 'CORE' or (meta['plugin'] and meta['plugin'] in self.event.get_plugins()))
if a_text and a_map:
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
return a_text.format_map(a_map)
else:
log_entry_type = log_entry_types.find(action_type=self.action_type)
if log_entry_type:
return log_entry_type.get_object_link(self)
for receiver, response in logentry_object_link.send(self.event, logentry=self):
if response:
return response
for receiver, response in logentry_object_link.send(self.event, logentry=self):
if response:
return response
return ''
if isinstance(co, (Order, Voucher, Item, SubEvent, Quota, Discount, Question)):
logging.warning("LogEntryType missing or ill-defined: %s", self.action_type)
return ''
@cached_property
def parsed_data(self):
@@ -250,33 +199,23 @@ class LogEntry(models.Model):
notify_webhooks.apply_async(args=(to_wh,))
class Registry:
def __init__(self, keys):
self.registered_items = list()
self.keys = keys
self.by_key = {key: {} for key in self.keys.keys()}
def register(self, *objs):
for obj in objs:
self.registered_items.append(obj)
for key, accessor in self.keys.items():
self.by_key[key][accessor(obj)] = obj
def register_instance(self, *args, **kwargs):
class LogEntryTypeRegistry(EventPluginRegistry):
def new_from_dict(self, data):
def reg(clz):
obj = clz(*args, **kwargs)
self.register(obj)
for action_type, plain in data.items():
self.register(clz(action_type=action_type, plain=plain))
return reg
def find(self, **kwargs):
(key, value), = kwargs.items()
return self.by_key.get(key).get(value)
log_entry_types = Registry({'action_type': lambda o: getattr(o, 'action_type')})
log_entry_types = LogEntryTypeRegistry({'action_type': lambda o: getattr(o, 'action_type')})
class LogEntryType:
def __init__(self, action_type=None, plain=None):
assert self.__module__ != LogEntryType.__module__ # must not instantiate base classes, only derived ones
if action_type: self.action_type = action_type
if plain: self.plain = plain
def display(self, logentry):
if hasattr(self, 'plain'):
plain = str(self.plain)
@@ -289,25 +228,15 @@ class LogEntryType:
def get_object_link_info(self, logentry) -> dict:
pass
def get_object_link(self, logentry):
def get_object_link(self, logentry, is_active):
a_map = self.get_object_link_info(logentry)
if a_map:
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
return self.object_link_wrapper.format_map(a_map)
return make_link(a_map, self.object_link_wrapper, is_active, logentry.event)
object_link_wrapper = '{val}'
def shred_pii(self, logentry):
raise NotImplementedError
@classmethod
def derive_plains(cls, plains):
for action_type, plain_display in plains.items():
obj = cls()
obj.action_type = action_type
obj.plain = plain_display
yield obj
class EventLogEntryType(LogEntryType):
def get_object_link_info(self, logentry) -> dict:

View File

@@ -33,7 +33,7 @@
# License for the specific language governing permissions and limitations under the License.
import warnings
from typing import Any, Callable, List, Tuple
from typing import Any, Callable, List, Tuple, Optional
import django.dispatch
from django.apps import apps
@@ -64,23 +64,10 @@ class EventPluginSignal(django.dispatch.Signal):
# Send to all events!
return True
# If sentry packed this in a wrapper, unpack that
if "sentry" in receiver.__module__:
receiver = receiver.__wrapped__
# Find the Django application this belongs to
searchpath = receiver.__module__
# Core modules are always active
if any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
name, app = get_defining_app(receiver)
if app == 'CORE':
return True
while True:
app = app_cache.get(searchpath)
if "." not in searchpath or app:
break
searchpath, _ = searchpath.rsplit(".", 1)
# Only fire receivers from active plugins and core modules
excluded = settings.PRETIX_PLUGINS_EXCLUDE
if sender and app and app.name in sender.get_plugins() and app.name not in excluded:
@@ -204,6 +191,60 @@ class DeprecatedSignal(django.dispatch.Signal):
super().connect(receiver, sender=None, weak=True, dispatch_uid=None)
class Registry:
def __init__(self, keys):
self.registered_items = list()
self.keys = keys
self.by_key = {key: {} for key in self.keys.keys()}
def register(self, *objs):
for obj in objs:
meta = {k: accessor(obj) for k, accessor in self.keys.items()}
tup = (obj, meta)
for key, accessor in self.keys.items():
self.by_key[key][accessor(obj)] = tup
self.registered_items.append(tup)
def new(self, *args, **kwargs):
def reg(clz):
obj = clz(*args, **kwargs)
self.register(obj)
return clz
return reg
def find(self, **kwargs):
(key, value), = kwargs.items()
return self.by_key.get(key).get(value, (None, None))
def get_defining_app(o) -> Tuple[Optional[str], Optional["django.apps.AppConfig"]]:
# If sentry packed this in a wrapper, unpack that
if "sentry" in o.__module__:
o = o.__wrapped__
# Find the Django application this belongs to
searchpath = o.__module__
# Core modules are always active
if any(searchpath.startswith(cm) for cm in settings.CORE_MODULES):
return 'CORE', 'CORE'
if not app_cache:
_populate_app_cache()
while True:
app = app_cache.get(searchpath)
if "." not in searchpath or app:
break
searchpath, _ = searchpath.rsplit(".", 1)
return app and app.name, app
class EventPluginRegistry(Registry):
def __init__(self, keys):
super().__init__({"plugin": lambda o: get_defining_app(o)[0], **keys})
event_live_issues = EventPluginSignal()
"""
This signal is sent out to determine whether an event can be taken live. If you want to

View File

@@ -382,7 +382,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
return _('Blocked because of an API integration')
log_entry_types.register(*OrderLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.order.modified': _('The order details have been changed.'),
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
@@ -451,10 +451,12 @@ log_entry_types.register(*OrderLogEntryType.derive_plains({
'approval.'),
'pretix.event.order.email.resend': _('An email with a link to the order detail page has been resent to the user.'),
'pretix.event.order.email.payment_failed': _('An email has been sent to notify the user that the payment failed.'),
}))
})
class CoreOrderLogEntryType(OrderLogEntryType):
pass
log_entry_types.register(*VoucherLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.voucher.added': _('The voucher has been created.'),
'pretix.voucher.sent': _('The voucher has been sent to {recipient}.'),
'pretix.voucher.added.waitinglist': _('The voucher has been created and sent to a person on the waiting list.'),
@@ -462,10 +464,12 @@ log_entry_types.register(*VoucherLogEntryType.derive_plains({
'The voucher has been set to expire because the recipient removed themselves from the waiting list.'),
'pretix.voucher.changed': _('The voucher has been changed.'),
'pretix.voucher.deleted': _('The voucher has been deleted.'),
}))
})
class CoreVoucherLogEntryType(VoucherLogEntryType):
pass
@log_entry_types.register_instance()
@log_entry_types.new()
class VoucherRedeemedLogEntryType(VoucherLogEntryType):
action_type = 'pretix.voucher.redeemed'
plain = _('The voucher has been redeemed in order {order_code}.')
@@ -483,19 +487,23 @@ class VoucherRedeemedLogEntryType(VoucherLogEntryType):
))
log_entry_types.register(*ItemCategoryLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.category.added': _('The category has been added.'),
'pretix.event.category.deleted': _('The category has been deleted.'),
'pretix.event.category.changed': _('The category has been changed.'),
'pretix.event.category.reordered': _('The category has been reordered.'),
}))
})
class CoreItemCategoryLogEntryType(ItemCategoryLogEntryType):
pass
log_entry_types.register(*TaxRuleLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.taxrule.added': _('The tax rule has been added.'),
'pretix.event.taxrule.deleted': _('The tax rule has been deleted.'),
'pretix.event.taxrule.changed': _('The tax rule has been changed.'),
}))
})
class CoreTaxRuleLogEntryType(TaxRuleLogEntryType):
pass
class TeamMembershipLogEntryType(LogEntryType):
@@ -503,15 +511,17 @@ class TeamMembershipLogEntryType(LogEntryType):
return self.plain.format(user=logentry.parsed_data.get('email'))
log_entry_types.register(*TeamMembershipLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.team.member.added': _('{user} has been added to the team.'),
'pretix.team.member.removed': _('{user} has been removed from the team.'),
'pretix.team.invite.created': _('{user} has been invited to the team.'),
'pretix.team.invite.resent': _('Invite for {user} has been resent.'),
}))
})
class CoreTeamMembershipLogEntryType(TeamMembershipLogEntryType):
pass
@log_entry_types.register_instance()
@log_entry_types.new()
class TeamMemberJoinedLogEntryType(LogEntryType):
action_type = 'pretix.team.member.joined'
@@ -521,7 +531,7 @@ class TeamMemberJoinedLogEntryType(LogEntryType):
)
@log_entry_types.register_instance()
@log_entry_types.new()
class UserSettingsChangedLogEntryType(LogEntryType):
action_type = 'pretix.user.settings.changed'
@@ -544,13 +554,15 @@ class UserImpersonatedLogEntryType(LogEntryType):
return self.plain.format(logentry.parsed_data['other_email'])
log_entry_types.register(*UserImpersonatedLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.control.auth.user.impersonated': _('You impersonated {}.'),
'pretix.control.auth.user.impersonate_stopped': _('You stopped impersonating {}.'),
}))
})
class CoreUserImpersonatedLogEntryType(UserImpersonatedLogEntryType):
pass
log_entry_types.register(*LogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.object.cloned': _('This object has been created by cloning.'),
'pretix.organizer.changed': _('The organizer has been changed.'),
'pretix.organizer.settings': _('The organizer settings have been changed.'),
@@ -658,9 +670,11 @@ log_entry_types.register(*LogEntryType.derive_plains({
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
'pretix.team.token.created': _('The token "{name}" has been created.'),
'pretix.team.token.deleted': _('The token "{name}" has been revoked.'),
}))
})
class CoreLogEntryType(LogEntryType):
pass
log_entry_types.register(*EventLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.item_meta_property.added': _('A meta property has been added to this event.'),
'pretix.event.item_meta_property.deleted': _('A meta property has been removed from this event.'),
'pretix.event.item_meta_property.changed': _('A meta property has been changed on this event.'),
@@ -686,9 +700,11 @@ log_entry_types.register(*EventLogEntryType.derive_plains({
'pretix.event.permissions.invited': _('A user has been invited to the event team.'),
'pretix.event.permissions.changed': _('A user\'s permissions have been changed.'),
'pretix.event.permissions.deleted': _('A user has been removed from the event team.'),
}))
})
class CoreEventLogEntryType(EventLogEntryType):
pass
log_entry_types.register(*ItemLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.item.added': _('The product has been created.'),
'pretix.event.item.changed': _('The product has been changed.'),
'pretix.event.item.reordered': _('The product has been reordered.'),
@@ -699,9 +715,16 @@ log_entry_types.register(*ItemLogEntryType.derive_plains({
'pretix.event.item.bundles.added': _('A bundled item has been added to this product.'),
'pretix.event.item.bundles.removed': _('A bundled item has been removed from this product.'),
'pretix.event.item.bundles.changed': _('A bundled item has been changed on this product.'),
}))
})
class CoreItemLogEntryType(ItemLogEntryType):
pass
@log_entry_types.new_from_dict({
'pretix.event.item.variation.added': _('The variation "{value}" has been created.'),
'pretix.event.item.variation.deleted': _('The variation "{value}" has been deleted.'),
'pretix.event.item.variation.changed': _('The variation "{value}" has been changed.'),
})
class VariationLogEntryType(ItemLogEntryType):
def display(self, logentry):
if 'value' not in logentry.parsed_data:
@@ -716,13 +739,7 @@ class VariationLogEntryType(ItemLogEntryType):
return super().display(logentry_display)
log_entry_types.register(*VariationLogEntryType.derive_plains({
'pretix.event.item.variation.added': _('The variation "{value}" has been created.'),
'pretix.event.item.variation.deleted': _('The variation "{value}" has been deleted.'),
'pretix.event.item.variation.changed': _('The variation "{value}" has been changed.'),
}))
log_entry_types.register(*OrderLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.order.payment.confirmed': _('Payment {local_id} has been confirmed.'),
'pretix.event.order.payment.canceled': _('Payment {local_id} has been canceled.'),
'pretix.event.order.payment.canceled.failed': _('Canceling payment {local_id} has failed.'),
@@ -736,31 +753,42 @@ log_entry_types.register(*OrderLogEntryType.derive_plains({
'pretix.event.order.refund.done': _('Refund {local_id} has been completed.'),
'pretix.event.order.refund.canceled': _('Refund {local_id} has been canceled.'),
'pretix.event.order.refund.failed': _('Refund {local_id} has failed.'),
}))
})
class CoreOrderLogEntryType(OrderLogEntryType):
pass
log_entry_types.register(*QuotaLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.quota.added': _('The quota has been added.'),
'pretix.event.quota.deleted': _('The quota has been deleted.'),
'pretix.event.quota.changed': _('The quota has been changed.'),
'pretix.event.quota.closed': _('The quota has closed.'),
'pretix.event.quota.opened': _('The quota has been re-opened.'),
}))
})
class CoreQuotaLogEntryType(QuotaLogEntryType):
pass
log_entry_types.register(*QuestionLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.question.added': _('The question has been added.'),
'pretix.event.question.deleted': _('The question has been deleted.'),
'pretix.event.question.changed': _('The question has been changed.'),
'pretix.event.question.reordered': _('The question has been reordered.'),
}))
})
class CoreQuestionLogEntryType(QuestionLogEntryType):
pass
log_entry_types.register(*DiscountLogEntryType.derive_plains({
@log_entry_types.new_from_dict({
'pretix.event.discount.added': _('The discount has been added.'),
'pretix.event.discount.deleted': _('The discount has been deleted.'),
'pretix.event.discount.changed': _('The discount has been changed.'),
}))
})
class CoreDiscountLogEntryType(DiscountLogEntryType):
pass
@log_entry_types.register_instance()
@log_entry_types.new()
class LegacyCheckinLogEntryType(OrderLogEntryType):
action_type = 'pretix.control.views.checkin'

View File

@@ -172,15 +172,13 @@ def control_order_info(sender: Event, request, order: Order, **kwargs):
return template.render(ctx, request=request)
@log_entry_types.new_from_dict({
'pretix.plugins.badges.layout.added': _('Badge layout created.'),
'pretix.plugins.badges.layout.deleted': _('Badge layout deleted.'),
'pretix.plugins.badges.layout.changed': _('Badge layout changed.'),
})
class BadgeLogEntryType(EventLogEntryType):
object_type = BadgeLayout
object_link_wrapper = _('Badge layout {val}')
object_link_viewname = 'plugins:badges:edit'
object_link_argname = 'layout'
log_entry_types.register(*BadgeLogEntryType.derive_plains({
'pretix.plugins.badges.layout.added': _('Badge layout created.'),
'pretix.plugins.badges.layout.deleted': _('Badge layout deleted.'),
'pretix.plugins.badges.layout.changed': _('Badge layout changed.'),
}))

View File

@@ -118,7 +118,7 @@ def html_head_presale(sender, request=None, **kwargs):
return ""
@log_entry_types.register_instance()
@log_entry_types.new()
class BanktransferOrderEmailInvoiceLogEntryType(OrderLogEntryType):
action_type = 'pretix.plugins.banktransfer.order.email.invoice'
plain = _('The invoice was sent to the designated email address.')

View File

@@ -32,7 +32,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.forms import SecretKeySettingsField
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
from pretix.base.models.log import EventLogEntryType
from pretix.base.models.log import EventLogEntryType, log_entry_types
from pretix.base.settings import settings_hierarkey
from pretix.base.signals import (
register_global_settings, register_payment_providers,
@@ -47,6 +47,7 @@ def register_payment_provider(sender, **kwargs):
return [PaypalSettingsHolder, PaypalWallet, PaypalAPM]
@log_entry_types.new()
class PaypalEventLogEntryType(EventLogEntryType):
action_type = 'pretix.plugins.paypal.event'

View File

@@ -119,26 +119,23 @@ def control_nav_import(sender, request=None, **kwargs):
]
@log_entry_types.new('pretix.plugins.sendmail.sent', _('Mass email was sent to customers or attendees.'))
@log_entry_types.new('pretix.plugins.sendmail.sent.waitinglist', _('Mass email was sent to waiting list entries.'))
class SendmailPluginLogEntryType(EventLogEntryType):
pass
log_entry_types.register(*SendmailPluginLogEntryType.derive_plains({
'pretix.plugins.sendmail.sent': _('Mass email was sent to customers or attendees.'),
'pretix.plugins.sendmail.sent.waitinglist': _('Mass email was sent to waiting list entries.'),
}))
@log_entry_types.new('pretix.plugins.sendmail.order.email.sent', _('The order received a mass email.'))
@log_entry_types.new('pretix.plugins.sendmail.order.email.sent.attendee', _('A ticket holder of this order received a mass email.'))
class SendmailPluginOrderLogEntryType(OrderLogEntryType):
pass
log_entry_types.register(*SendmailPluginOrderLogEntryType.derive_plains({
'pretix.plugins.sendmail.order.email.sent': _('The order received a mass email.'),
'pretix.plugins.sendmail.order.email.sent.attendee': _('A ticket holder of this order received a mass email.'),
}))
@log_entry_types.new('pretix.plugins.sendmail.rule.added', _('An email rule was created'))
@log_entry_types.new('pretix.plugins.sendmail.rule.changed', _('An email rule was updated'))
@log_entry_types.new('pretix.plugins.sendmail.rule.order.email.sent', _('A scheduled email was sent to the order'))
@log_entry_types.new('pretix.plugins.sendmail.rule.order.position.email.sent', _('A scheduled email was sent to a ticket holder'))
@log_entry_types.new('pretix.plugins.sendmail.rule.deleted', _('An email rule was deleted'))
class SendmailPluginRuleLogEntryType(EventLogEntryType):
object_type = Rule
object_link_wrapper = _('Mail rule {val}')
@@ -146,15 +143,6 @@ class SendmailPluginRuleLogEntryType(EventLogEntryType):
object_link_argname = 'rule'
log_entry_types.register(*SendmailPluginRuleLogEntryType.derive_plains({
'pretix.plugins.sendmail.rule.added': _('An email rule was created'),
'pretix.plugins.sendmail.rule.changed': _('An email rule was updated'),
'pretix.plugins.sendmail.rule.order.email.sent': _('A scheduled email was sent to the order'),
'pretix.plugins.sendmail.rule.order.position.email.sent': _('A scheduled email was sent to a ticket holder'),
'pretix.plugins.sendmail.rule.deleted': _('An email rule was deleted'),
}))
@receiver(periodic_task)
def sendmail_run_rules(sender, **kwargs):
with scopes_disabled():

View File

@@ -133,6 +133,11 @@ def pdf_event_copy_data_receiver(sender, other, item_map, question_map, **kwargs
return layout_map
@log_entry_types.new_from_dict({
'pretix.plugins.ticketoutputpdf.layout.added': _('Ticket layout created.'),
'pretix.plugins.ticketoutputpdf.layout.deleted': _('Ticket layout deleted.'),
'pretix.plugins.ticketoutputpdf.layout.changed': _('Ticket layout changed.'),
})
class PdfTicketLayoutLogEntryType(EventLogEntryType):
object_type = TicketLayout
object_link_wrapper = _('Ticket layout {val}')
@@ -140,13 +145,6 @@ class PdfTicketLayoutLogEntryType(EventLogEntryType):
object_link_argname = 'layout'
log_entry_types.register(*PdfTicketLayoutLogEntryType.derive_plains({
'pretix.plugins.ticketoutputpdf.layout.added': _('Ticket layout created.'),
'pretix.plugins.ticketoutputpdf.layout.deleted': _('Ticket layout deleted.'),
'pretix.plugins.ticketoutputpdf.layout.changed': _('Ticket layout changed.'),
}))
def _ticket_layouts_for_item(request, item):
if not hasattr(request, '_ticket_layouts_for_item'):
request._ticket_layouts_for_item = {}