forked from CGM_Public/pretix_original
Compare commits
15 Commits
fix-missin
...
hide-zero-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
784b588aa8 | ||
|
|
0dce6464ad | ||
|
|
e1d3f16819 | ||
|
|
6c0b266260 | ||
|
|
e4692ed746 | ||
|
|
a99bb283e2 | ||
|
|
06f09dda49 | ||
|
|
da1de7d646 | ||
|
|
63a2e2e058 | ||
|
|
5d83f70f75 | ||
|
|
f8badea1d3 | ||
|
|
d727d58bc9 | ||
|
|
c8d4815c9e | ||
|
|
c25d6988a7 | ||
|
|
89f1f61b73 |
@@ -121,6 +121,7 @@ This will automatically make pretix discover this plugin as soon as it is instal
|
||||
through ``pip``. During development, you can just run ``python setup.py develop`` inside
|
||||
your plugin source directory to make it discoverable.
|
||||
|
||||
.. _`signals`:
|
||||
Signals
|
||||
-------
|
||||
|
||||
@@ -153,6 +154,25 @@ in the ``installed`` method:
|
||||
Note that ``installed`` will *not* be called if the plugin is indirectly activated for an event
|
||||
because the event is created with settings copied from another event.
|
||||
|
||||
.. _`registries`:
|
||||
Registries
|
||||
----------
|
||||
|
||||
Many signals in pretix are used so that plugins can "register" a class, e.g. a payment provider or a
|
||||
ticket renderer.
|
||||
|
||||
However, for some of them (types of :ref:`Log Entries <logging>`) we use a different method to keep track of them:
|
||||
In a ``Registry``, classes are collected at application startup, along with a unique key (in case
|
||||
of LogEntryType, the ``action_type``) as well as which plugin registered them.
|
||||
|
||||
To register a class, you can use one of several decorators provided by the Registry object:
|
||||
|
||||
.. autoclass:: pretix.base.logentrytypes.LogEntryTypeRegistry
|
||||
:members: register, new, new_from_dict
|
||||
|
||||
All files in which classes are registered need to be imported in the ``AppConfig.ready`` as explained
|
||||
in `Signals <signals>`_ above.
|
||||
|
||||
Views
|
||||
-----
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ To actually log an action, you can just call the ``log_action`` method on your o
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
order.log_action('pretix.event.order.canceled', user=user, data={})
|
||||
order.log_action('pretix.event.order.comment', user=user,
|
||||
data={"new_comment": "Hello, world."})
|
||||
|
||||
The positional ``action`` argument should represent the type of action and should be globally unique, we
|
||||
recommend to prefix it with your package name, e.g. ``paypal.payment.rejected``. The ``user`` argument is
|
||||
@@ -72,24 +73,101 @@ following ready-to-include template::
|
||||
{% include "pretixcontrol/includes/logs.html" with obj=order %}
|
||||
|
||||
We now need a way to translate the action codes like ``pretix.event.changed`` into human-readable
|
||||
strings. The :py:attr:`pretix.base.signals.logentry_display` signals allows you to do so. A simple
|
||||
strings. The :py:attr:`pretix.base.logentrytypes.log_entry_types` :ref:`registry <registries>` allows you to do so. A simple
|
||||
implementation could look like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
from pretix.base.signals import logentry_display
|
||||
from pretix.base.logentrytypes import log_entry_types
|
||||
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.order.comment': _('The order\'s internal comment has been updated to: {new_comment}'),
|
||||
'pretix.event.order.paid': _('The order has been marked as paid.'),
|
||||
# ...
|
||||
})
|
||||
class CoreOrderLogEntryType(OrderLogEntryType):
|
||||
pass
|
||||
|
||||
Please note that you always need to define your own inherited ``LogEntryType`` class in your plugin. If you would just
|
||||
register an instance of a ``LogEntryType`` class defined in pretix core, it cannot be automatically detected as belonging
|
||||
to your plugin, leading to confusing user interface situations.
|
||||
|
||||
|
||||
Customizing log entry display
|
||||
"""""""""""""""""""""""""""""
|
||||
|
||||
The base ``LogEntryType`` classes allow for varying degree of customization in their descendants.
|
||||
|
||||
If you want to add another log message for an existing core object (e.g. an :class:`Order <pretix.base.models.Order>`,
|
||||
:class:`Item <pretix.base.models.Item>`, or :class:`Voucher <pretix.base.models.Voucher>`), you can inherit
|
||||
from its predefined :class:`LogEntryType <pretix.base.logentrytypes.LogEntryType>`, e.g.
|
||||
:class:`OrderLogEntryType <pretix.base.logentrytypes.OrderLogEntryType>`, and just specify a new plaintext string.
|
||||
You can use format strings to insert information from the LogEntry's `data` object as shown in the section above.
|
||||
|
||||
If you define a new model object in your plugin, you should make sure proper object links in the user interface are
|
||||
displayed for it. If your model object belongs logically to a pretix :class:`Event <pretix.base.models.Event>`, you can inherit from :class:`EventLogEntryType <pretix.base.logentrytypes.EventLogEntryType>`,
|
||||
and set the ``object_link_*`` fields accordingly. ``object_link_viewname`` refers to a django url name, which needs to
|
||||
accept the arguments `organizer` and `event`, containing the respective slugs, and additional arguments provided by
|
||||
``object_link_args``. The default implementation of ``object_link_args`` will return an argument named by
|
||||
````object_link_argname``, with a value of ``content_object.pk`` (the primary key of the model object).
|
||||
If you want to customize the name displayed for the object (instead of the result of calling ``str()`` on it),
|
||||
overwrite ``object_link_display_name``.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class ItemLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Product {val}')
|
||||
|
||||
# link will be generated as reverse('control:event.item', {'organizer': ..., 'event': ..., 'item': item.pk})
|
||||
object_link_viewname = 'control:event.item'
|
||||
object_link_argname = 'item'
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class OrderLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Order {val}')
|
||||
|
||||
# link will be generated as reverse('control:event.order', {'organizer': ..., 'event': ..., 'code': order.code})
|
||||
object_link_viewname = 'control:event.order'
|
||||
|
||||
def object_link_args(self, order):
|
||||
return {'code': order.code}
|
||||
|
||||
def object_link_display_name(self, order):
|
||||
return order.code
|
||||
|
||||
To show more sophisticated message strings, e.g. varying the message depending on information from the :class:`LogEntry <pretix.base.models.log.LogEntry>`'s
|
||||
`data` object, override the `display` method:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@log_entry_types.new()
|
||||
class PaypalEventLogEntryType(EventLogEntryType):
|
||||
action_type = 'pretix.plugins.paypal.event'
|
||||
|
||||
def display(self, logentry):
|
||||
event_type = logentry.parsed_data.get('event_type')
|
||||
text = {
|
||||
'PAYMENT.SALE.COMPLETED': _('Payment completed.'),
|
||||
'PAYMENT.SALE.DENIED': _('Payment denied.'),
|
||||
# ...
|
||||
}.get(event_type, f"({event_type})")
|
||||
return _('PayPal reported an event: {}').format(text)
|
||||
|
||||
.. automethod:: pretix.base.logentrytypes.LogEntryType.display
|
||||
|
||||
If your new model object does not belong to an :class:`Event <pretix.base.models.Event>`, you need to inherit directly from ``LogEntryType`` instead
|
||||
of ``EventLogEntryType``, providing your own implementation of ``get_object_link_info`` if object links should be
|
||||
displayed.
|
||||
|
||||
.. autoclass:: pretix.base.logentrytypes.LogEntryType
|
||||
:members: get_object_link_info
|
||||
|
||||
|
||||
@receiver(signal=logentry_display)
|
||||
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
plains = {
|
||||
'pretix.event.order.paid': _('The order has been marked as paid.'),
|
||||
'pretix.event.order.refunded': _('The order has been refunded.'),
|
||||
'pretix.event.order.canceled': _('The order has been canceled.'),
|
||||
...
|
||||
}
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
|
||||
Sending notifications
|
||||
---------------------
|
||||
|
||||
@@ -96,6 +96,18 @@ attribute::
|
||||
<pretix-widget event="https://pretix.eu/demo/democon/" disable-iframe></pretix-widget>
|
||||
|
||||
|
||||
Always show event’s info
|
||||
------------------------
|
||||
|
||||
If you want the widget to show the event’s info such as title, location and frontpage text, you can pass the optional
|
||||
``display-event-info`` attribute with either a value of ``"false"``, ``"true"`` or ``"auto"`` – the latter being the
|
||||
default if the attribute is not present at all.
|
||||
|
||||
Note that any other value than ``"false"`` or ``"auto"`` means ``"true"``::
|
||||
|
||||
<pretix-widget event="https://pretix.eu/demo/democon/" display-event-info></pretix-widget>
|
||||
|
||||
|
||||
Pre-selecting a voucher
|
||||
-----------------------
|
||||
|
||||
|
||||
@@ -75,6 +75,14 @@ FORMAT_MODULE_PATH = [
|
||||
'pretix.helpers.formats',
|
||||
]
|
||||
|
||||
CORE_MODULES = {
|
||||
"pretix.base",
|
||||
"pretix.presale",
|
||||
"pretix.control",
|
||||
"pretix.plugins.checkinlists",
|
||||
"pretix.plugins.reports",
|
||||
}
|
||||
|
||||
ALL_LANGUAGES = [
|
||||
('en', _('English')),
|
||||
('de', _('German')),
|
||||
@@ -288,7 +296,7 @@ PILLOW_FORMATS_QUESTIONS_IMAGE = ('PNG', 'GIF', 'JPEG', 'BMP', 'TIFF')
|
||||
FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT = (
|
||||
".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
|
||||
".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
|
||||
".bmp", ".tif", ".tiff"
|
||||
".bmp", ".tif", ".tiff", ".ics",
|
||||
)
|
||||
FILE_UPLOAD_EXTENSIONS_OTHER = FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT
|
||||
|
||||
|
||||
@@ -775,6 +775,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
|
||||
for idx, gross in grossvalue_map.items():
|
||||
rate, name = idx
|
||||
if rate == 0 and gross == 0:
|
||||
continue
|
||||
tax = taxvalue_map[idx]
|
||||
tdata.append([
|
||||
Paragraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
|
||||
|
||||
253
src/pretix/base/logentrytypes.py
Normal file
253
src/pretix/base/logentrytypes.py
Normal file
@@ -0,0 +1,253 @@
|
||||
#
|
||||
# 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 django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.signals import EventPluginRegistry
|
||||
|
||||
|
||||
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
|
||||
if a_map:
|
||||
if is_active:
|
||||
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
|
||||
elif event and plugin_name:
|
||||
a_map['val'] = (
|
||||
'<i>{val}</i> <a href="{plugin_href}">'
|
||||
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>'
|
||||
).format_map({
|
||||
**a_map,
|
||||
"errmes": _("The relevant plugin is currently not active. To activate it, click here to go to the plugin settings."),
|
||||
"plugin_href": reverse('control:event.settings.plugins', kwargs={
|
||||
'organizer': event.organizer.slug,
|
||||
'event': event.slug,
|
||||
}) + '#plugin_' + plugin_name,
|
||||
})
|
||||
else:
|
||||
a_map['val'] = '<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>'.format_map({
|
||||
**a_map,
|
||||
"errmes": _("The relevant plugin is currently not active."),
|
||||
})
|
||||
return wrapper.format_map(a_map)
|
||||
|
||||
|
||||
class LogEntryTypeRegistry(EventPluginRegistry):
|
||||
def __init__(self):
|
||||
super().__init__({'action_type': lambda o: getattr(o, 'action_type')})
|
||||
|
||||
def register(self, *objs):
|
||||
for obj in objs:
|
||||
if not isinstance(obj, LogEntryType):
|
||||
raise TypeError('Entries must be derived from LogEntryType')
|
||||
|
||||
if obj.__module__ == LogEntryType.__module__:
|
||||
raise TypeError('Must not register base classes, only derived ones')
|
||||
|
||||
return super().register(*objs)
|
||||
|
||||
def new_from_dict(self, data):
|
||||
"""
|
||||
Register multiple instance of a `LogEntryType` class with different `action_type`
|
||||
and plain text strings, as given by the items of the specified data dictionary.
|
||||
|
||||
This method is designed to be used as a decorator as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@log_entry_types.new_from_dict({
|
||||
'pretix.event.item.added': _('The product has been created.'),
|
||||
'pretix.event.item.changed': _('The product has been changed.'),
|
||||
# ...
|
||||
})
|
||||
class CoreItemLogEntryType(ItemLogEntryType):
|
||||
# ...
|
||||
|
||||
:param data: action types and descriptions
|
||||
``{"some_action_type": "Plain text description", ...}``
|
||||
"""
|
||||
def reg(clz):
|
||||
for action_type, plain in data.items():
|
||||
self.register(clz(action_type=action_type, plain=plain))
|
||||
return clz
|
||||
return reg
|
||||
|
||||
|
||||
"""
|
||||
Registry for LogEntry types.
|
||||
|
||||
Each entry in this registry should be an instance of a subclass of ``LogEntryType``.
|
||||
They are annotated with their ``action_type`` and the defining ``plugin``.
|
||||
"""
|
||||
log_entry_types = LogEntryTypeRegistry()
|
||||
|
||||
|
||||
class LogEntryType:
|
||||
"""
|
||||
Base class for a type of LogEntry, identified by its action_type.
|
||||
"""
|
||||
|
||||
def __init__(self, action_type=None, plain=None):
|
||||
if action_type:
|
||||
self.action_type = action_type
|
||||
if plain:
|
||||
self.plain = plain
|
||||
|
||||
def display(self, logentry):
|
||||
"""
|
||||
Returns the message to be displayed for a given logentry of this type.
|
||||
|
||||
:return: `str` or `LazyI18nString`
|
||||
"""
|
||||
if hasattr(self, 'plain'):
|
||||
plain = str(self.plain)
|
||||
if '{' in plain:
|
||||
data = defaultdict(lambda: '?', logentry.parsed_data)
|
||||
return plain.format_map(data)
|
||||
else:
|
||||
return plain
|
||||
|
||||
def get_object_link_info(self, logentry) -> dict:
|
||||
"""
|
||||
Return information to generate a link to the `content_object` of a given log entry.
|
||||
|
||||
Not implemented in the base class, causing the object link to be omitted.
|
||||
|
||||
:return: Dictionary with the keys ``href`` (containing a URL to view/edit the object) and ``val`` (containing the
|
||||
escaped text for the anchor element)
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_object_link(self, logentry):
|
||||
a_map = self.get_object_link_info(logentry)
|
||||
return make_link(a_map, self.object_link_wrapper)
|
||||
|
||||
object_link_wrapper = '{val}'
|
||||
|
||||
def shred_pii(self, logentry):
|
||||
"""
|
||||
To be used for shredding personally identified information contained in the data field of a LogEntry of this
|
||||
type.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class EventLogEntryType(LogEntryType):
|
||||
"""
|
||||
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:
|
||||
if hasattr(self, 'object_link_viewname') and logentry.content_object:
|
||||
return {
|
||||
'href': reverse(self.object_link_viewname, kwargs={
|
||||
'event': logentry.event.slug,
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
**self.object_link_args(logentry.content_object),
|
||||
}),
|
||||
'val': escape(self.object_link_display_name(logentry.content_object)),
|
||||
}
|
||||
|
||||
def object_link_args(self, content_object):
|
||||
"""Return the kwargs for the url used in a link to content_object."""
|
||||
if hasattr(self, 'object_link_argname'):
|
||||
return {self.object_link_argname: content_object.pk}
|
||||
return {}
|
||||
|
||||
def object_link_display_name(self, content_object):
|
||||
"""Return the display name to refer to content_object in the user interface."""
|
||||
return str(content_object)
|
||||
|
||||
|
||||
class OrderLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Order {val}')
|
||||
object_link_viewname = 'control:event.order'
|
||||
|
||||
def object_link_args(self, order):
|
||||
return {'code': order.code}
|
||||
|
||||
def object_link_display_name(self, order):
|
||||
return order.code
|
||||
|
||||
|
||||
class VoucherLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Voucher {val}…')
|
||||
object_link_viewname = 'control:event.voucher'
|
||||
object_link_argname = 'voucher'
|
||||
|
||||
def object_link_display_name(self, voucher):
|
||||
if len(voucher.code) > 6:
|
||||
return voucher.code[:6] + "…"
|
||||
return voucher.code
|
||||
|
||||
|
||||
class ItemLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Product {val}')
|
||||
object_link_viewname = 'control:event.item'
|
||||
object_link_argname = 'item'
|
||||
|
||||
|
||||
class SubEventLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = pgettext_lazy('subevent', 'Date {val}')
|
||||
object_link_viewname = 'control:event.subevent'
|
||||
object_link_argname = 'subevent'
|
||||
|
||||
|
||||
class QuotaLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Quota {val}')
|
||||
object_link_viewname = 'control:event.items.quotas.show'
|
||||
object_link_argname = 'quota'
|
||||
|
||||
|
||||
class DiscountLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Discount {val}')
|
||||
object_link_viewname = 'control:event.items.discounts.edit'
|
||||
object_link_argname = 'discount'
|
||||
|
||||
|
||||
class ItemCategoryLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Category {val}')
|
||||
object_link_viewname = 'control:event.items.categories.edit'
|
||||
object_link_argname = 'category'
|
||||
|
||||
|
||||
class QuestionLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Question {val}')
|
||||
object_link_viewname = 'control:event.items.questions.show'
|
||||
object_link_argname = 'question'
|
||||
|
||||
|
||||
class TaxRuleLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Tax rule {val}')
|
||||
object_link_viewname = 'control:event.settings.tax.edit'
|
||||
object_link_argname = 'rule'
|
||||
|
||||
|
||||
class NoOpShredderMixin:
|
||||
def shred_pii(self, logentry):
|
||||
pass
|
||||
|
||||
|
||||
class ClearDataShredderMixin:
|
||||
def shred_pii(self, logentry):
|
||||
logentry.data = None
|
||||
@@ -33,16 +33,15 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
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.logentrytypes import log_entry_types, make_link
|
||||
from pretix.base.signals import is_app_active, logentry_object_link
|
||||
|
||||
|
||||
class VisibleOnlyManager(models.Manager):
|
||||
@@ -92,6 +91,10 @@ class LogEntry(models.Model):
|
||||
indexes = [models.Index(fields=["datetime", "id"])]
|
||||
|
||||
def display(self):
|
||||
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
|
||||
if log_entry_type:
|
||||
return log_entry_type.display(self)
|
||||
|
||||
from ..signals import logentry_display
|
||||
|
||||
for receiver, response in logentry_display.send(self.event, logentry=self):
|
||||
@@ -126,10 +129,18 @@ class LogEntry(models.Model):
|
||||
@cached_property
|
||||
def display_object(self):
|
||||
from . import (
|
||||
Discount, Event, Item, ItemCategory, Order, Question, Quota,
|
||||
SubEvent, TaxRule, Voucher,
|
||||
Discount, Event, Item, Order, Question, Quota, SubEvent, Voucher,
|
||||
)
|
||||
|
||||
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
|
||||
if log_entry_type:
|
||||
link_info = log_entry_type.get_object_link_info(self)
|
||||
if is_app_active(self.event, meta['plugin']):
|
||||
return make_link(link_info, log_entry_type.object_link_wrapper)
|
||||
else:
|
||||
return make_link(link_info, log_entry_type.object_link_wrapper, is_active=False,
|
||||
event=self.event, plugin_name=meta['plugin'] and getattr(meta['plugin'], 'name'))
|
||||
|
||||
try:
|
||||
if self.content_type.model_class() is Event:
|
||||
return ''
|
||||
@@ -137,110 +148,15 @@ 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, ItemCategory):
|
||||
a_text = _('Category {val}')
|
||||
a_map = {
|
||||
'href': reverse('control:event.items.categories.edit', kwargs={
|
||||
'event': self.event.slug,
|
||||
'organizer': self.event.organizer.slug,
|
||||
'category': co.id
|
||||
}),
|
||||
'val': escape(co.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),
|
||||
}
|
||||
elif isinstance(co, TaxRule):
|
||||
a_text = _('Tax rule {val}')
|
||||
a_map = {
|
||||
'href': reverse('control:event.settings.tax.edit', kwargs={
|
||||
'event': self.event.slug,
|
||||
'organizer': self.event.organizer.slug,
|
||||
'rule': co.id
|
||||
}),
|
||||
'val': escape(co.name),
|
||||
}
|
||||
for receiver, response in logentry_object_link.send(self.event, logentry=self):
|
||||
if response:
|
||||
return response
|
||||
|
||||
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)
|
||||
elif a_text:
|
||||
return a_text
|
||||
else:
|
||||
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):
|
||||
|
||||
@@ -185,48 +185,103 @@ BEFORE_AFTER_CHOICE = (
|
||||
)
|
||||
|
||||
|
||||
reldatetimeparts = namedtuple('reldatetimeparts', (
|
||||
"status", # 0
|
||||
"absolute", # 1
|
||||
"rel_days_number", # 2
|
||||
"rel_mins_relationto", # 3
|
||||
"rel_days_timeofday", # 4
|
||||
"rel_mins_number", # 5
|
||||
"rel_days_relationto", # 6
|
||||
"rel_mins_relation", # 7
|
||||
"rel_days_relation" # 8
|
||||
))
|
||||
reldatetimeparts.indizes = reldatetimeparts(*range(9))
|
||||
|
||||
|
||||
class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
template_name = 'pretixbase/forms/widgets/reldatetime.html'
|
||||
parts = reldatetimeparts
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.status_choices = kwargs.pop('status_choices')
|
||||
base_choices = kwargs.pop('base_choices')
|
||||
widgets = (
|
||||
forms.RadioSelect(choices=self.status_choices),
|
||||
forms.DateTimeInput(
|
||||
widgets = reldatetimeparts(
|
||||
status=forms.RadioSelect(choices=self.status_choices),
|
||||
absolute=forms.DateTimeInput(
|
||||
attrs={'class': 'datetimepicker'}
|
||||
),
|
||||
forms.NumberInput(),
|
||||
forms.Select(choices=base_choices),
|
||||
forms.TimeInput(attrs={'placeholder': _('Time'), 'class': 'timepickerfield'}),
|
||||
forms.NumberInput(),
|
||||
forms.Select(choices=base_choices),
|
||||
forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_number=forms.NumberInput(),
|
||||
rel_mins_relationto=forms.Select(choices=base_choices),
|
||||
rel_days_timeofday=forms.TimeInput(attrs={'placeholder': _('Time'), 'class': 'timepickerfield'}),
|
||||
rel_mins_number=forms.NumberInput(),
|
||||
rel_days_relationto=forms.Select(choices=base_choices),
|
||||
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
)
|
||||
super().__init__(widgets=widgets, *args, **kwargs)
|
||||
|
||||
def decompress(self, value):
|
||||
if isinstance(value, str):
|
||||
value = RelativeDateWrapper.from_string(value)
|
||||
if isinstance(value, reldatetimeparts):
|
||||
return value
|
||||
if not value:
|
||||
return ['unset', None, 1, 'date_from', None, 0, "date_from", "before", "before"]
|
||||
return reldatetimeparts(
|
||||
status="unset",
|
||||
absolute=None,
|
||||
rel_days_number=1,
|
||||
rel_mins_relationto="date_from",
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto="date_from",
|
||||
rel_mins_relation="before",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
elif isinstance(value.data, (datetime.datetime, datetime.date)):
|
||||
return ['absolute', value.data, 1, 'date_from', None, 0, "date_from", "before", "before"]
|
||||
return reldatetimeparts(
|
||||
status="absolute",
|
||||
absolute=value.data,
|
||||
rel_days_number=1,
|
||||
rel_mins_relationto="date_from",
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto="date_from",
|
||||
rel_mins_relation="before",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
elif value.data.minutes is not None:
|
||||
return ['relative_minutes', None, None, value.data.base_date_name, None, value.data.minutes, value.data.base_date_name,
|
||||
"after" if value.data.is_after else "before", "after" if value.data.is_after else "before"]
|
||||
return ['relative', None, value.data.days, value.data.base_date_name, value.data.time, 0, value.data.base_date_name,
|
||||
"after" if value.data.is_after else "before", "after" if value.data.is_after else "before"]
|
||||
return reldatetimeparts(
|
||||
status="relative_minutes",
|
||||
absolute=None,
|
||||
rel_days_number=None,
|
||||
rel_mins_relationto=value.data.base_date_name,
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=value.data.minutes,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_mins_relation="after" if value.data.is_after else "before",
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
return reldatetimeparts(
|
||||
status="relative",
|
||||
absolute=None,
|
||||
rel_days_number=value.data.days,
|
||||
rel_mins_relationto=value.data.base_date_name,
|
||||
rel_days_timeofday=value.data.time,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_mins_relation="after" if value.data.is_after else "before",
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
ctx = super().get_context(name, value, attrs)
|
||||
ctx['required'] = self.status_choices[0][0] == 'unset'
|
||||
|
||||
ctx['rendered_subwidgets'] = [
|
||||
ctx['rendered_subwidgets'] = self.parts(*(
|
||||
self._render(w['template_name'], {**ctx, 'widget': w})
|
||||
for w in ctx['widget']['subwidgets']
|
||||
]
|
||||
))._asdict()
|
||||
|
||||
return ctx
|
||||
|
||||
@@ -245,36 +300,36 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
choices = BASE_CHOICES
|
||||
if not kwargs.get('required', True):
|
||||
status_choices.insert(0, ('unset', _('Not set')))
|
||||
fields = (
|
||||
forms.ChoiceField(
|
||||
fields = reldatetimeparts(
|
||||
status=forms.ChoiceField(
|
||||
choices=status_choices,
|
||||
required=True
|
||||
),
|
||||
forms.DateTimeField(
|
||||
absolute=forms.DateTimeField(
|
||||
required=False
|
||||
),
|
||||
forms.IntegerField(
|
||||
rel_days_number=forms.IntegerField(
|
||||
required=False
|
||||
),
|
||||
forms.ChoiceField(
|
||||
rel_mins_relationto=forms.ChoiceField(
|
||||
choices=choices,
|
||||
required=False
|
||||
),
|
||||
forms.TimeField(
|
||||
rel_days_timeofday=forms.TimeField(
|
||||
required=False,
|
||||
),
|
||||
forms.IntegerField(
|
||||
rel_mins_number=forms.IntegerField(
|
||||
required=False
|
||||
),
|
||||
forms.ChoiceField(
|
||||
rel_days_relationto=forms.ChoiceField(
|
||||
choices=choices,
|
||||
required=False
|
||||
),
|
||||
forms.ChoiceField(
|
||||
rel_mins_relation=forms.ChoiceField(
|
||||
choices=BEFORE_AFTER_CHOICE,
|
||||
required=False
|
||||
),
|
||||
forms.ChoiceField(
|
||||
rel_days_relation=forms.ChoiceField(
|
||||
choices=BEFORE_AFTER_CHOICE,
|
||||
required=False
|
||||
),
|
||||
@@ -288,32 +343,36 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
)
|
||||
|
||||
def set_event(self, event):
|
||||
self.widget.widgets[3].choices = [
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
]
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
]
|
||||
|
||||
def compress(self, data_list):
|
||||
if not data_list:
|
||||
return None
|
||||
if data_list[0] == 'absolute':
|
||||
return RelativeDateWrapper(data_list[1])
|
||||
elif data_list[0] == 'unset':
|
||||
data = reldatetimeparts(*data_list)
|
||||
if data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'unset':
|
||||
return None
|
||||
elif data_list[0] == 'relative_minutes':
|
||||
elif data.status == 'relative_minutes':
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=0,
|
||||
base_date_name=data_list[3],
|
||||
base_date_name=data.rel_mins_relationto,
|
||||
time=None,
|
||||
minutes=data_list[5],
|
||||
is_after=data_list[7] == "after",
|
||||
minutes=data.rel_mins_number,
|
||||
is_after=data.rel_mins_relation == "after",
|
||||
))
|
||||
else:
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=data_list[2],
|
||||
base_date_name=data_list[6],
|
||||
time=data_list[4],
|
||||
days=data.rel_days_number,
|
||||
base_date_name=data.rel_days_relationto,
|
||||
time=data.rel_days_timeofday,
|
||||
minutes=None,
|
||||
is_after=data_list[8] == "after",
|
||||
is_after=data.rel_days_relation == "after",
|
||||
))
|
||||
|
||||
def has_changed(self, initial, data):
|
||||
@@ -322,29 +381,41 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
return super().has_changed(initial, data)
|
||||
|
||||
def clean(self, value):
|
||||
if value[0] == 'absolute' and not value[1]:
|
||||
data = reldatetimeparts(*value)
|
||||
if data.status == 'absolute' and not data.absolute:
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif value[0] == 'relative' and (value[2] is None or not value[3]):
|
||||
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif value[0] == 'relative_minutes' and (value[5] is None or not value[3]):
|
||||
elif data.status == 'relative_minutes' and (data.rel_mins_number is None or not data.rel_mins_relationto):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
|
||||
return super().clean(value)
|
||||
|
||||
|
||||
reldateparts = namedtuple('reldateparts', (
|
||||
"status", # 0
|
||||
"absolute", # 1
|
||||
"rel_days_number", # 2
|
||||
"rel_days_relationto", # 3
|
||||
"rel_days_relation", # 4
|
||||
))
|
||||
reldateparts.indizes = reldateparts(*range(5))
|
||||
|
||||
|
||||
class RelativeDateWidget(RelativeDateTimeWidget):
|
||||
template_name = 'pretixbase/forms/widgets/reldate.html'
|
||||
parts = reldateparts
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.status_choices = kwargs.pop('status_choices')
|
||||
widgets = (
|
||||
forms.RadioSelect(choices=self.status_choices),
|
||||
forms.DateInput(
|
||||
widgets = reldateparts(
|
||||
status=forms.RadioSelect(choices=self.status_choices),
|
||||
absolute=forms.DateInput(
|
||||
attrs={'class': 'datepickerfield'}
|
||||
),
|
||||
forms.NumberInput(),
|
||||
forms.Select(choices=kwargs.pop('base_choices')),
|
||||
forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_number=forms.NumberInput(),
|
||||
rel_days_relationto=forms.Select(choices=kwargs.pop('base_choices')),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
)
|
||||
forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
|
||||
|
||||
@@ -352,10 +423,30 @@ class RelativeDateWidget(RelativeDateTimeWidget):
|
||||
if isinstance(value, str):
|
||||
value = RelativeDateWrapper.from_string(value)
|
||||
if not value:
|
||||
return ['unset', None, 1, 'date_from', 'before']
|
||||
return reldateparts(
|
||||
status="unset",
|
||||
absolute=None,
|
||||
rel_days_number=1,
|
||||
rel_days_relationto="date_from",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
if isinstance(value, reldateparts):
|
||||
return value
|
||||
elif isinstance(value.data, (datetime.datetime, datetime.date)):
|
||||
return ['absolute', value.data, 1, 'date_from', 'before']
|
||||
return ['relative', None, value.data.days, value.data.base_date_name, "after" if value.data.is_after else "before"]
|
||||
return reldateparts(
|
||||
status="absolute",
|
||||
absolute=value.data,
|
||||
rel_days_number=1,
|
||||
rel_days_relationto="date_from",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
return reldateparts(
|
||||
status="relative",
|
||||
absolute=None,
|
||||
rel_days_number=value.data.days,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
|
||||
|
||||
class RelativeDateField(RelativeDateTimeField):
|
||||
@@ -367,22 +458,22 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
]
|
||||
if not kwargs.get('required', True):
|
||||
status_choices.insert(0, ('unset', _('Not set')))
|
||||
fields = (
|
||||
forms.ChoiceField(
|
||||
fields = reldateparts(
|
||||
status=forms.ChoiceField(
|
||||
choices=status_choices,
|
||||
required=True
|
||||
),
|
||||
forms.DateField(
|
||||
absolute=forms.DateField(
|
||||
required=False
|
||||
),
|
||||
forms.IntegerField(
|
||||
rel_days_number=forms.IntegerField(
|
||||
required=False
|
||||
),
|
||||
forms.ChoiceField(
|
||||
rel_days_relationto=forms.ChoiceField(
|
||||
choices=BASE_CHOICES,
|
||||
required=False
|
||||
),
|
||||
forms.ChoiceField(
|
||||
rel_days_relation=forms.ChoiceField(
|
||||
choices=BEFORE_AFTER_CHOICE,
|
||||
required=False
|
||||
),
|
||||
@@ -393,28 +484,35 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
self, fields=fields, require_all_fields=False, *args, **kwargs
|
||||
)
|
||||
|
||||
def set_event(self, event):
|
||||
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
]
|
||||
|
||||
def compress(self, data_list):
|
||||
if not data_list:
|
||||
return None
|
||||
if data_list[0] == 'absolute':
|
||||
return RelativeDateWrapper(data_list[1])
|
||||
elif data_list[0] == 'unset':
|
||||
data = reldateparts(*data_list)
|
||||
if data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'unset':
|
||||
return None
|
||||
else:
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=data_list[2],
|
||||
base_date_name=data_list[3],
|
||||
days=data.rel_days_number,
|
||||
base_date_name=data.rel_days_relationto,
|
||||
time=None, minutes=None,
|
||||
is_after=data_list[4] == "after"
|
||||
is_after=data.rel_days_relation == "after"
|
||||
))
|
||||
|
||||
def clean(self, value):
|
||||
if value[0] == 'absolute' and not value[1]:
|
||||
data = reldateparts(*value)
|
||||
if data.status == 'absolute' and not data.absolute:
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif value[0] == 'relative' and (value[2] is None or not value[3]):
|
||||
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
|
||||
return super().clean(value)
|
||||
return forms.MultiValueField.clean(self, value)
|
||||
|
||||
|
||||
class ModelRelativeDateTimeField(models.CharField):
|
||||
|
||||
@@ -52,6 +52,50 @@ def _populate_app_cache():
|
||||
app_cache[ac.name] = ac
|
||||
|
||||
|
||||
def get_defining_app(o):
|
||||
# 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'
|
||||
|
||||
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
|
||||
|
||||
|
||||
def is_app_active(sender, app):
|
||||
if app == 'CORE':
|
||||
return True
|
||||
|
||||
excluded = settings.PRETIX_PLUGINS_EXCLUDE
|
||||
if sender and app and app.name in sender.get_plugins() and app.name not in excluded:
|
||||
if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_receiver_active(sender, receiver):
|
||||
if sender is None:
|
||||
# Send to all events!
|
||||
return True
|
||||
|
||||
app = get_defining_app(receiver)
|
||||
|
||||
return is_app_active(sender, app)
|
||||
|
||||
|
||||
class EventPluginSignal(django.dispatch.Signal):
|
||||
"""
|
||||
This is an extension to Django's built-in signals which differs in a way that it sends
|
||||
@@ -59,33 +103,6 @@ class EventPluginSignal(django.dispatch.Signal):
|
||||
Event.
|
||||
"""
|
||||
|
||||
def _is_active(self, sender, receiver):
|
||||
if sender is None:
|
||||
# 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_module = any([searchpath.startswith(cm) for cm in settings.CORE_MODULES])
|
||||
app = None
|
||||
if not core_module:
|
||||
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 core_module or (sender and app and app.name in sender.get_plugins() and app.name not in excluded):
|
||||
if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors:
|
||||
return True
|
||||
return False
|
||||
|
||||
def send(self, sender: Event, **named) -> List[Tuple[Callable, Any]]:
|
||||
"""
|
||||
Send signal from sender to all connected receivers that belong to
|
||||
@@ -104,7 +121,7 @@ class EventPluginSignal(django.dispatch.Signal):
|
||||
_populate_app_cache()
|
||||
|
||||
for receiver in self._sorted_receivers(sender):
|
||||
if self._is_active(sender, receiver):
|
||||
if is_receiver_active(sender, receiver):
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
responses.append((receiver, response))
|
||||
return responses
|
||||
@@ -128,7 +145,7 @@ class EventPluginSignal(django.dispatch.Signal):
|
||||
_populate_app_cache()
|
||||
|
||||
for receiver in self._sorted_receivers(sender):
|
||||
if self._is_active(sender, receiver):
|
||||
if is_receiver_active(sender, receiver):
|
||||
named[chain_kwarg_name] = response
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
return response
|
||||
@@ -155,7 +172,7 @@ class EventPluginSignal(django.dispatch.Signal):
|
||||
_populate_app_cache()
|
||||
|
||||
for receiver in self._sorted_receivers(sender):
|
||||
if self._is_active(sender, receiver):
|
||||
if is_receiver_active(sender, receiver):
|
||||
try:
|
||||
response = receiver(signal=self, sender=sender, **named)
|
||||
except Exception as err:
|
||||
@@ -202,6 +219,122 @@ class DeprecatedSignal(django.dispatch.Signal):
|
||||
super().connect(receiver, sender=None, weak=True, dispatch_uid=None)
|
||||
|
||||
|
||||
class Registry:
|
||||
"""
|
||||
A Registry is a collection of objects (entries), annotated with metadata. Entries can be searched and filtered by
|
||||
metadata keys, and metadata is returned as part of the result.
|
||||
|
||||
Entry metadata is generated during registration using to the accessor functions given to the Registry
|
||||
constructor.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
animal_sound_registry = Registry({"animal": lambda s: s.animal})
|
||||
|
||||
@animal_sound_registry.new("dog", "woof")
|
||||
@animal_sound_registry.new("cricket", "chirp")
|
||||
class AnimalSound:
|
||||
def __init__(self, animal, sound):
|
||||
self.animal = animal
|
||||
self.sound = sound
|
||||
|
||||
def make_sound(self):
|
||||
return self.sound
|
||||
|
||||
@animal_sound_registry.new()
|
||||
class CatSound(AnimalSound):
|
||||
def __init__(self):
|
||||
super().__init__(animal="cat", sound=["meow", "meww", "miaou"])
|
||||
|
||||
def make_sound(self):
|
||||
return random.choice(self.sound)
|
||||
"""
|
||||
|
||||
def __init__(self, keys):
|
||||
"""
|
||||
:param keys: Dictionary with `{key: accessor_function}`
|
||||
When a new entry is registered, all accessor functions are called with the new entry as parameter.
|
||||
Their return value is stored as the metadata value for that key.
|
||||
"""
|
||||
self.registered_entries = dict()
|
||||
self.keys = keys
|
||||
self.by_key = {key: {} for key in self.keys.keys()}
|
||||
|
||||
def register(self, *objs):
|
||||
"""
|
||||
Register one or more entries in this registry.
|
||||
|
||||
Usable as a regular method or as decorator on a class or function. If used on a class, the class type object
|
||||
itself is registered, not an instance of the class. To register an instance, use the ``new`` method.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@some_registry.register
|
||||
def my_new_entry(foo):
|
||||
# ...
|
||||
"""
|
||||
for obj in objs:
|
||||
if obj in self.registered_entries:
|
||||
raise RuntimeError('Object already registered: {}'.format(obj))
|
||||
|
||||
meta = {k: accessor(obj) for k, accessor in self.keys.items()}
|
||||
tup = (obj, meta)
|
||||
for key, value in meta.items():
|
||||
self.by_key[key][value] = tup
|
||||
self.registered_entries[obj] = meta
|
||||
|
||||
if len(objs) == 1:
|
||||
return objs[0]
|
||||
|
||||
def new(self, *args, **kwargs):
|
||||
"""
|
||||
Instantiate the decorated class with the given `*args` and `**kwargs`, and register the instance in this registry.
|
||||
May be used multiple times.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@animal_sound_registry.new("meow")
|
||||
@animal_sound_registry.new("woof")
|
||||
class AnimalSound:
|
||||
def __init__(self, sound):
|
||||
# ...
|
||||
"""
|
||||
def reg(clz):
|
||||
obj = clz(*args, **kwargs)
|
||||
self.register(obj)
|
||||
return clz
|
||||
return reg
|
||||
|
||||
def get(self, **kwargs):
|
||||
(key, value), = kwargs.items()
|
||||
return self.by_key.get(key).get(value, (None, None))
|
||||
|
||||
def filter(self, **kwargs):
|
||||
return (
|
||||
(entry, meta)
|
||||
for entry, meta in self.registered_entries.items()
|
||||
if all(value == meta[key] for key, value in kwargs.items())
|
||||
)
|
||||
|
||||
|
||||
class EventPluginRegistry(Registry):
|
||||
"""
|
||||
A Registry which automatically annotates entries with a "plugin" key, specifying which plugin
|
||||
the entry is defined in. This allows the consumer of entries to determine whether an entry is
|
||||
enabled for a given event, or filter only for entries defined by enabled plugins.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
logtype, meta = my_registry.find(action_type="foo.bar.baz")
|
||||
# meta["plugin"] contains the django app name of the defining plugin
|
||||
"""
|
||||
|
||||
def __init__(self, keys):
|
||||
super().__init__({"plugin": lambda o: get_defining_app(o), **keys})
|
||||
|
||||
|
||||
event_live_issues = EventPluginSignal()
|
||||
"""
|
||||
This signal is sent out to determine whether an event can be taken live. If you want to
|
||||
@@ -507,41 +640,16 @@ logentry_display = EventPluginSignal()
|
||||
"""
|
||||
Arguments: ``logentry``
|
||||
|
||||
To display an instance of the ``LogEntry`` model to a human user,
|
||||
``pretix.base.signals.logentry_display`` will be sent out with a ``logentry`` argument.
|
||||
|
||||
The first received response that is not ``None`` will be used to display the log entry
|
||||
to the user. The receivers are expected to return plain text.
|
||||
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
**DEPRECTATION:** Please do not use this signal for new LogEntry types. Use the log_entry_types
|
||||
registry instead, as described in https://docs.pretix.eu/en/latest/development/implementation/logging.html
|
||||
"""
|
||||
|
||||
logentry_object_link = EventPluginSignal()
|
||||
"""
|
||||
Arguments: ``logentry``
|
||||
|
||||
To display the relationship of an instance of the ``LogEntry`` model to another model
|
||||
to a human user, ``pretix.base.signals.logentry_object_link`` will be sent out with a
|
||||
``logentry`` argument.
|
||||
|
||||
The first received response that is not ``None`` will be used to display the related object
|
||||
to the user. The receivers are expected to return a HTML link. The internal implementation
|
||||
builds the links like this::
|
||||
|
||||
a_text = _('Tax rule {val}')
|
||||
a_map = {
|
||||
'href': reverse('control:event.settings.tax.edit', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug,
|
||||
'rule': logentry.content_object.id
|
||||
}),
|
||||
'val': escape(logentry.content_object.name),
|
||||
}
|
||||
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
|
||||
return a_text.format_map(a_map)
|
||||
|
||||
Make sure that any user content in the HTML code you return is properly escaped!
|
||||
As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
**DEPRECTATION:** Please do not use this signal for new LogEntry types. Use the log_entry_types
|
||||
registry instead, as described in https://docs.pretix.eu/en/latest/development/implementation/logging.html
|
||||
"""
|
||||
|
||||
requiredaction_display = EventPluginSignal()
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
{{ selopt.label }}
|
||||
</label>
|
||||
{% if selopt.value == "absolute" %}
|
||||
{% include widget.subwidgets.1.template_name with widget=widget.subwidgets.1 %}
|
||||
{{ rendered_subwidgets.absolute }}
|
||||
{% elif selopt.value == "relative" %}
|
||||
{% blocktrans trimmed with number=rendered_subwidgets.2 relation=rendered_subwidgets.4 relation_to=rendered_subwidgets.3 %}
|
||||
{% blocktrans trimmed with number=rendered_subwidgets.rel_days_number relation=rendered_subwidgets.rel_days_relation relation_to=rendered_subwidgets.rel_days_relationto %}
|
||||
{{ number }} days {{ relation }} {{ relation_to }}
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
{{ selopt.label }}
|
||||
</label>
|
||||
{% if selopt.value == "absolute" %}
|
||||
{% include widget.subwidgets.1.template_name with widget=widget.subwidgets.1 %}
|
||||
{{ rendered_subwidgets.absolute }}
|
||||
{% elif selopt.value == "relative_minutes" %}
|
||||
{% blocktrans trimmed with number=rendered_subwidgets.5 relation=rendered_subwidgets.7 relation_to=rendered_subwidgets.3 %}
|
||||
{% blocktrans trimmed with number=rendered_subwidgets.rel_mins_number relation=rendered_subwidgets.rel_mins_relation relation_to=rendered_subwidgets.rel_mins_relationto %}
|
||||
{{ number }} minutes {{ relation }} {{ relation_to }}
|
||||
{% endblocktrans %}
|
||||
{% elif selopt.value == "relative" %}
|
||||
{% blocktrans trimmed with number=rendered_subwidgets.2 relation=rendered_subwidgets.8 relation_to=rendered_subwidgets.6 time_of_day=rendered_subwidgets.4 %}
|
||||
{% blocktrans trimmed with number=rendered_subwidgets.rel_days_number relation=rendered_subwidgets.rel_days_relation relation_to=rendered_subwidgets.rel_days_relationto time_of_day=rendered_subwidgets.rel_days_timeofday %}
|
||||
{{ number }} days {{ relation }} {{ relation_to }} at {{ time_of_day }}
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
||||
@@ -47,12 +47,20 @@ from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.logentrytypes import (
|
||||
DiscountLogEntryType, EventLogEntryType, ItemCategoryLogEntryType,
|
||||
ItemLogEntryType, LogEntryType, OrderLogEntryType, QuestionLogEntryType,
|
||||
QuotaLogEntryType, TaxRuleLogEntryType, VoucherLogEntryType,
|
||||
log_entry_types,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
Checkin, CheckinList, Event, ItemVariation, LogEntry, OrderPosition,
|
||||
TaxRule,
|
||||
)
|
||||
from pretix.base.models.orders import PrintLog
|
||||
from pretix.base.signals import logentry_display, orderposition_blocked_display
|
||||
from pretix.base.signals import (
|
||||
app_cache, logentry_display, orderposition_blocked_display,
|
||||
)
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
|
||||
OVERVIEW_BANLIST = [
|
||||
@@ -329,278 +337,6 @@ def _display_checkin(event, logentry):
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
|
||||
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
plains = {
|
||||
'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.'),
|
||||
'pretix.organizer.footerlinks.changed': _('The footer links have been changed.'),
|
||||
'pretix.organizer.export.schedule.added': _('A scheduled export has been added.'),
|
||||
'pretix.organizer.export.schedule.changed': _('A scheduled export has been changed.'),
|
||||
'pretix.organizer.export.schedule.deleted': _('A scheduled export has been deleted.'),
|
||||
'pretix.organizer.export.schedule.executed': _('A scheduled export has been executed.'),
|
||||
'pretix.organizer.export.schedule.failed': _('A scheduled export has failed: {reason}.'),
|
||||
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
||||
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
||||
'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),
|
||||
'pretix.giftcards.acceptance.acceptor.removed': _('A gift card acceptor has been removed.'),
|
||||
'pretix.giftcards.acceptance.issuer.removed': _('A gift card issuer has been removed or declined.'),
|
||||
'pretix.giftcards.acceptance.issuer.accepted': _('A new gift card issuer has been accepted.'),
|
||||
'pretix.webhook.created': _('The webhook has been created.'),
|
||||
'pretix.webhook.changed': _('The webhook has been changed.'),
|
||||
'pretix.webhook.retries.expedited': _('The webhook call retry jobs have been manually expedited.'),
|
||||
'pretix.webhook.retries.dropped': _('The webhook call retry jobs have been dropped.'),
|
||||
'pretix.ssoprovider.created': _('The SSO provider has been created.'),
|
||||
'pretix.ssoprovider.changed': _('The SSO provider has been changed.'),
|
||||
'pretix.ssoprovider.deleted': _('The SSO provider has been deleted.'),
|
||||
'pretix.ssoclient.created': _('The SSO client has been created.'),
|
||||
'pretix.ssoclient.changed': _('The SSO client has been changed.'),
|
||||
'pretix.ssoclient.deleted': _('The SSO client has been deleted.'),
|
||||
'pretix.membershiptype.created': _('The membership type has been created.'),
|
||||
'pretix.membershiptype.changed': _('The membership type has been changed.'),
|
||||
'pretix.membershiptype.deleted': _('The membership type has been deleted.'),
|
||||
'pretix.saleschannel.created': _('The sales channel has been created.'),
|
||||
'pretix.saleschannel.changed': _('The sales channel has been changed.'),
|
||||
'pretix.saleschannel.deleted': _('The sales channel has been deleted.'),
|
||||
'pretix.customer.created': _('The account has been created.'),
|
||||
'pretix.customer.changed': _('The account has been changed.'),
|
||||
'pretix.customer.membership.created': _('A membership for this account has been added.'),
|
||||
'pretix.customer.membership.changed': _('A membership of this account has been changed.'),
|
||||
'pretix.customer.membership.deleted': _('A membership of this account has been deleted.'),
|
||||
'pretix.customer.anonymized': _('The account has been disabled and anonymized.'),
|
||||
'pretix.customer.password.resetrequested': _('A new password has been requested.'),
|
||||
'pretix.customer.password.set': _('A new password has been set.'),
|
||||
'pretix.reusable_medium.created': _('The reusable medium has been created.'),
|
||||
'pretix.reusable_medium.created.auto': _('The reusable medium has been created automatically.'),
|
||||
'pretix.reusable_medium.changed': _('The reusable medium has been changed.'),
|
||||
'pretix.reusable_medium.linked_orderposition.changed': _('The medium has been connected to a new ticket.'),
|
||||
'pretix.reusable_medium.linked_giftcard.changed': _('The medium has been connected to a new gift card.'),
|
||||
'pretix.email.error': _('Sending of an email has failed.'),
|
||||
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
||||
'pretix.event.canceled': _('The event has been canceled.'),
|
||||
'pretix.event.deleted': _('An event has been deleted.'),
|
||||
'pretix.event.shredder.started': _('A removal process for personal data has been started.'),
|
||||
'pretix.event.shredder.completed': _('A removal process for personal data has been completed.'),
|
||||
'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.'),
|
||||
'pretix.event.order.expirychanged': _('The order\'s expiry date has been changed.'),
|
||||
'pretix.event.order.valid_if_pending.set': _('The order has been set to be usable before it is paid.'),
|
||||
'pretix.event.order.valid_if_pending.unset': _('The order has been set to require payment before use.'),
|
||||
'pretix.event.order.expired': _('The order has been marked as expired.'),
|
||||
'pretix.event.order.paid': _('The order has been marked as paid.'),
|
||||
'pretix.event.order.cancellationrequest.deleted': _('The cancellation request has been deleted.'),
|
||||
'pretix.event.order.refunded': _('The order has been refunded.'),
|
||||
'pretix.event.order.reactivated': _('The order has been reactivated.'),
|
||||
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
|
||||
'pretix.event.order.placed': _('The order has been created.'),
|
||||
'pretix.event.order.placed.require_approval': _('The order requires approval before it can continue to be processed.'),
|
||||
'pretix.event.order.approved': _('The order has been approved.'),
|
||||
'pretix.event.order.denied': _('The order has been denied (comment: "{comment}").'),
|
||||
'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" '
|
||||
'to "{new_email}".'),
|
||||
'pretix.event.order.contact.confirmed': _('The email address has been confirmed to be working (the user clicked on a link '
|
||||
'in the email for the first time).'),
|
||||
'pretix.event.order.phone.changed': _('The phone number has been changed from "{old_phone}" '
|
||||
'to "{new_phone}".'),
|
||||
'pretix.event.order.customer.changed': _('The customer account has been changed.'),
|
||||
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
|
||||
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
|
||||
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
|
||||
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
|
||||
'pretix.event.order.comment': _('The order\'s internal comment has been updated.'),
|
||||
'pretix.event.order.custom_followup_at': _('The order\'s follow-up date has been updated.'),
|
||||
'pretix.event.order.checkin_attention': _('The order\'s flag to require attention at check-in has been '
|
||||
'toggled.'),
|
||||
'pretix.event.order.checkin_text': _('The order\'s check-in text has been changed.'),
|
||||
'pretix.event.order.pretix.event.order.valid_if_pending': _('The order\'s flag to be considered valid even if '
|
||||
'unpaid has been toggled.'),
|
||||
'pretix.event.order.payment.changed': _('A new payment {local_id} has been started instead of the previous one.'),
|
||||
'pretix.event.order.email.sent': _('An unidentified type email has been sent.'),
|
||||
'pretix.event.order.email.error': _('Sending of an email has failed.'),
|
||||
'pretix.event.order.email.attachments.skipped': _('The email has been sent without attached tickets since they '
|
||||
'would have been too large to be likely to arrive.'),
|
||||
'pretix.event.order.email.custom_sent': _('A custom email has been sent.'),
|
||||
'pretix.event.order.position.email.custom_sent': _('A custom email has been sent to an attendee.'),
|
||||
'pretix.event.order.email.download_reminder_sent': _('An email has been sent with a reminder that the ticket '
|
||||
'is available for download.'),
|
||||
'pretix.event.order.email.expire_warning_sent': _('An email has been sent with a warning that the order is about '
|
||||
'to expire.'),
|
||||
'pretix.event.order.email.order_canceled': _('An email has been sent to notify the user that the order has been canceled.'),
|
||||
'pretix.event.order.email.event_canceled': _('An email has been sent to notify the user that the event has '
|
||||
'been canceled.'),
|
||||
'pretix.event.order.email.order_changed': _('An email has been sent to notify the user that the order has been changed.'),
|
||||
'pretix.event.order.email.order_free': _('An email has been sent to notify the user that the order has been received.'),
|
||||
'pretix.event.order.email.order_paid': _('An email has been sent to notify the user that payment has been received.'),
|
||||
'pretix.event.order.email.order_denied': _('An email has been sent to notify the user that the order has been denied.'),
|
||||
'pretix.event.order.email.order_approved': _('An email has been sent to notify the user that the order has '
|
||||
'been approved.'),
|
||||
'pretix.event.order.email.order_placed': _('An email has been sent to notify the user that the order has been received and requires payment.'),
|
||||
'pretix.event.order.email.order_placed_require_approval': _('An email has been sent to notify the user that '
|
||||
'the order has been received and requires '
|
||||
'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.'),
|
||||
'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.'),
|
||||
'pretix.event.order.payment.started': _('Payment {local_id} has been started.'),
|
||||
'pretix.event.order.payment.failed': _('Payment {local_id} has failed.'),
|
||||
'pretix.event.order.quotaexceeded': _('The order could not be marked as paid: {message}'),
|
||||
'pretix.event.order.overpaid': _('The order has been overpaid.'),
|
||||
'pretix.event.order.refund.created': _('Refund {local_id} has been created.'),
|
||||
'pretix.event.order.refund.created.externally': _('Refund {local_id} has been created by an external entity.'),
|
||||
'pretix.event.order.refund.requested': _('The customer requested you to issue a refund.'),
|
||||
'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.'),
|
||||
'pretix.event.export.schedule.added': _('A scheduled export has been added.'),
|
||||
'pretix.event.export.schedule.changed': _('A scheduled export has been changed.'),
|
||||
'pretix.event.export.schedule.deleted': _('A scheduled export has been deleted.'),
|
||||
'pretix.event.export.schedule.executed': _('A scheduled export has been executed.'),
|
||||
'pretix.event.export.schedule.failed': _('A scheduled export has failed: {reason}.'),
|
||||
'pretix.control.auth.user.created': _('The user has been created.'),
|
||||
'pretix.control.auth.user.new_source': _('A first login using {agent_type} on {os_type} from {country} has '
|
||||
'been detected.'),
|
||||
'pretix.user.settings.2fa.enabled': _('Two-factor authentication has been enabled.'),
|
||||
'pretix.user.settings.2fa.disabled': _('Two-factor authentication has been disabled.'),
|
||||
'pretix.user.settings.2fa.regenemergency': _('Your two-factor emergency codes have been regenerated.'),
|
||||
'pretix.user.settings.2fa.emergency': _('A two-factor emergency code has been generated.'),
|
||||
'pretix.user.settings.2fa.device.added': _('A new two-factor authentication device "{name}" has been added to '
|
||||
'your account.'),
|
||||
'pretix.user.settings.2fa.device.deleted': _('The two-factor authentication device "{name}" has been removed '
|
||||
'from your account.'),
|
||||
'pretix.user.settings.notifications.enabled': _('Notifications have been enabled.'),
|
||||
'pretix.user.settings.notifications.disabled': _('Notifications have been disabled.'),
|
||||
'pretix.user.settings.notifications.changed': _('Your notification settings have been changed.'),
|
||||
'pretix.user.anonymized': _('This user has been anonymized.'),
|
||||
'pretix.user.oauth.authorized': _('The application "{application_name}" has been authorized to access your '
|
||||
'account.'),
|
||||
'pretix.control.auth.user.forgot_password.mail_sent': _('Password reset mail sent.'),
|
||||
'pretix.control.auth.user.forgot_password.recovered': _('The password has been reset.'),
|
||||
'pretix.control.auth.user.forgot_password.denied.repeated': _('A repeated password reset has been denied, as '
|
||||
'the last request was less than 24 hours ago.'),
|
||||
'pretix.organizer.deleted': _('The organizer "{name}" has been deleted.'),
|
||||
'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.'),
|
||||
'pretix.voucher.expired.waitinglist': _('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.'),
|
||||
'pretix.voucher.redeemed': _('The voucher has been redeemed in order {order_code}.'),
|
||||
'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.'),
|
||||
'pretix.event.item.deleted': _('The product has been deleted.'),
|
||||
'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.'),
|
||||
'pretix.event.item.addons.added': _('An add-on has been added to this product.'),
|
||||
'pretix.event.item.addons.removed': _('An add-on has been removed from this product.'),
|
||||
'pretix.event.item.addons.changed': _('An add-on has been changed on this product.'),
|
||||
'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.'),
|
||||
'pretix.event.item_meta_property.added': _('A meta property has been added to this event.'),
|
||||
'pretix.event.item_meta_property.deleted': _('A meta property has been removed from this event.'),
|
||||
'pretix.event.item_meta_property.changed': _('A meta property has been changed on this event.'),
|
||||
'pretix.event.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.'),
|
||||
'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.'),
|
||||
'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.'),
|
||||
'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.'),
|
||||
'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.'),
|
||||
'pretix.event.checkinlist.added': _('The check-in list has been added.'),
|
||||
'pretix.event.checkinlist.deleted': _('The check-in list has been deleted.'),
|
||||
'pretix.event.checkinlists.deleted': _('The check-in list has been deleted.'), # backwards compatibility
|
||||
'pretix.event.checkinlist.changed': _('The check-in list has been changed.'),
|
||||
'pretix.event.settings': _('The event settings have been changed.'),
|
||||
'pretix.event.tickets.settings': _('The ticket download settings have been changed.'),
|
||||
'pretix.event.plugins.enabled': _('A plugin has been enabled.'),
|
||||
'pretix.event.plugins.disabled': _('A plugin has been disabled.'),
|
||||
'pretix.event.live.activated': _('The shop has been taken live.'),
|
||||
'pretix.event.live.deactivated': _('The shop has been taken offline.'),
|
||||
'pretix.event.testmode.activated': _('The shop has been taken into test mode.'),
|
||||
'pretix.event.testmode.deactivated': _('The test mode has been disabled.'),
|
||||
'pretix.event.added': _('The event has been created.'),
|
||||
'pretix.event.changed': _('The event details have been changed.'),
|
||||
'pretix.event.footerlinks.changed': _('The footer links have been changed.'),
|
||||
'pretix.event.question.option.added': _('An answer option has been added to the question.'),
|
||||
'pretix.event.question.option.deleted': _('An answer option has been removed from the question.'),
|
||||
'pretix.event.question.option.changed': _('An answer option has been changed.'),
|
||||
'pretix.event.permissions.added': _('A user has been added to the event team.'),
|
||||
'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.'),
|
||||
'pretix.waitinglist.voucher': _('A voucher has been sent to a person on the waiting list.'), # legacy
|
||||
'pretix.event.orders.waitinglist.voucher_assigned': _('A voucher has been sent to a person on the waiting list.'),
|
||||
'pretix.event.orders.waitinglist.deleted': _('An entry has been removed from the waiting list.'),
|
||||
'pretix.event.order.waitinglist.transferred': _('An entry has been transferred to another waiting list.'), # legacy
|
||||
'pretix.event.orders.waitinglist.changed': _('An entry has been changed on the waiting list.'),
|
||||
'pretix.event.orders.waitinglist.added': _('An entry has been added to the waiting list.'),
|
||||
'pretix.team.created': _('The team has been created.'),
|
||||
'pretix.team.changed': _('The team settings have been changed.'),
|
||||
'pretix.team.deleted': _('The team has been deleted.'),
|
||||
'pretix.gate.created': _('The gate has been created.'),
|
||||
'pretix.gate.changed': _('The gate has been changed.'),
|
||||
'pretix.gate.deleted': _('The gate has been deleted.'),
|
||||
'pretix.subevent.deleted': pgettext_lazy('subevent', 'The event date has been deleted.'),
|
||||
'pretix.subevent.canceled': pgettext_lazy('subevent', 'The event date has been canceled.'),
|
||||
'pretix.subevent.changed': pgettext_lazy('subevent', 'The event date has been changed.'),
|
||||
'pretix.subevent.added': pgettext_lazy('subevent', 'The event date has been created.'),
|
||||
'pretix.subevent.quota.added': pgettext_lazy('subevent', 'A quota has been added to the event date.'),
|
||||
'pretix.subevent.quota.changed': pgettext_lazy('subevent', 'A quota has been changed on the event date.'),
|
||||
'pretix.subevent.quota.deleted': pgettext_lazy('subevent', 'A quota has been removed from the event date.'),
|
||||
'pretix.device.created': _('The device has been created.'),
|
||||
'pretix.device.changed': _('The device has been changed.'),
|
||||
'pretix.device.revoked': _('Access of the device has been revoked.'),
|
||||
'pretix.device.initialized': _('The device has been initialized.'),
|
||||
'pretix.device.keyroll': _('The access token of the device has been regenerated.'),
|
||||
'pretix.device.updated': _('The device has notified the server of an hardware or software update.'),
|
||||
'pretix.giftcards.created': _('The gift card has been created.'),
|
||||
'pretix.giftcards.modified': _('The gift card has been changed.'),
|
||||
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
|
||||
}
|
||||
|
||||
data = json.loads(logentry.data)
|
||||
|
||||
if logentry.action_type.startswith('pretix.event.item.variation'):
|
||||
if 'value' not in data:
|
||||
# Backwards compatibility
|
||||
var = ItemVariation.objects.filter(id=data['id']).first()
|
||||
if var:
|
||||
data['value'] = str(var.value)
|
||||
else:
|
||||
data['value'] = '?'
|
||||
else:
|
||||
data['value'] = LazyI18nString(data['value'])
|
||||
|
||||
if logentry.action_type == "pretix.voucher.redeemed":
|
||||
data = defaultdict(lambda: '?', data)
|
||||
url = reverse('control:event.order', kwargs={
|
||||
'event': logentry.event.slug,
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'code': data['order_code']
|
||||
})
|
||||
return mark_safe(plains[logentry.action_type].format(
|
||||
order_code='<a href="{}">{}</a>'.format(url, data['order_code']),
|
||||
))
|
||||
|
||||
if logentry.action_type in plains:
|
||||
data = defaultdict(lambda: '?', data)
|
||||
return plains[logentry.action_type].format_map(data)
|
||||
|
||||
if logentry.action_type.startswith('pretix.event.order.changed'):
|
||||
return _display_order_changed(sender, logentry)
|
||||
@@ -624,16 +360,16 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
return _('The order has been canceled.')
|
||||
|
||||
if logentry.action_type in ('pretix.control.views.checkin.reverted', 'pretix.event.checkin.reverted'):
|
||||
if 'list' in data:
|
||||
if 'list' in logentry.parsed_data:
|
||||
try:
|
||||
checkin_list = sender.checkin_lists.get(pk=data.get('list')).name
|
||||
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=data.get('positionid'),
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
list=checkin_list,
|
||||
)
|
||||
|
||||
@@ -642,83 +378,14 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
|
||||
if logentry.action_type == 'pretix.event.order.print':
|
||||
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
|
||||
posid=data.get('positionid'),
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
datetime=date_format(
|
||||
dateutil.parser.parse(data["datetime"]).astimezone(sender.timezone),
|
||||
dateutil.parser.parse(logentry.parsed_data["datetime"]).astimezone(sender.timezone),
|
||||
"SHORT_DATETIME_FORMAT"
|
||||
),
|
||||
type=dict(PrintLog.PRINT_TYPES)[data["type"]],
|
||||
type=dict(PrintLog.PRINT_TYPES)[logentry.parsed_data["type"]],
|
||||
)
|
||||
|
||||
if logentry.action_type == 'pretix.control.views.checkin':
|
||||
# deprecated
|
||||
dt = dateutil.parser.parse(data.get('datetime'))
|
||||
tz = sender.timezone
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
if 'list' in data:
|
||||
try:
|
||||
checkin_list = sender.checkin_lists.get(pk=data.get('list')).name
|
||||
except CheckinList.DoesNotExist:
|
||||
checkin_list = _("(unknown)")
|
||||
else:
|
||||
checkin_list = _("(unknown)")
|
||||
|
||||
if data.get('first'):
|
||||
return _('Position #{posid} has been checked in manually at {datetime} on list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list,
|
||||
)
|
||||
return _('Position #{posid} has been checked in again at {datetime} on list "{list}".').format(
|
||||
posid=data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
|
||||
if logentry.action_type == 'pretix.team.member.added':
|
||||
return _('{user} has been added to the team.').format(user=data.get('email'))
|
||||
|
||||
if logentry.action_type == 'pretix.team.member.removed':
|
||||
return _('{user} has been removed from the team.').format(user=data.get('email'))
|
||||
|
||||
if logentry.action_type == 'pretix.team.member.joined':
|
||||
return _('{user} has joined the team using the invite sent to {email}.').format(
|
||||
user=data.get('email'), email=data.get('invite_email')
|
||||
)
|
||||
|
||||
if logentry.action_type == 'pretix.team.invite.created':
|
||||
return _('{user} has been invited to the team.').format(user=data.get('email'))
|
||||
|
||||
if logentry.action_type == 'pretix.team.invite.resent':
|
||||
return _('Invite for {user} has been resent.').format(user=data.get('email'))
|
||||
|
||||
if logentry.action_type == 'pretix.team.invite.deleted':
|
||||
return _('The invite for {user} has been revoked.').format(user=data.get('email'))
|
||||
|
||||
if logentry.action_type == 'pretix.team.token.created':
|
||||
return _('The token "{name}" has been created.').format(name=data.get('name'))
|
||||
|
||||
if logentry.action_type == 'pretix.team.token.deleted':
|
||||
return _('The token "{name}" has been revoked.').format(name=data.get('name'))
|
||||
|
||||
if logentry.action_type == 'pretix.user.settings.changed':
|
||||
text = str(_('Your account settings have been changed.'))
|
||||
if 'email' in data:
|
||||
text = text + ' ' + str(_('Your email address has been changed to {email}.').format(email=data['email']))
|
||||
if 'new_pw' in data:
|
||||
text = text + ' ' + str(_('Your password has been changed.'))
|
||||
if data.get('is_active') is True:
|
||||
text = text + ' ' + str(_('Your account has been enabled.'))
|
||||
elif data.get('is_active') is False:
|
||||
text = text + ' ' + str(_('Your account has been disabled.'))
|
||||
return text
|
||||
|
||||
if logentry.action_type == 'pretix.control.auth.user.impersonated':
|
||||
return str(_('You impersonated {}.')).format(data['other_email'])
|
||||
|
||||
if logentry.action_type == 'pretix.control.auth.user.impersonate_stopped':
|
||||
return str(_('You stopped impersonating {}.')).format(data['other_email'])
|
||||
|
||||
|
||||
@receiver(signal=orderposition_blocked_display, dispatch_uid="pretixcontrol_orderposition_blocked_display")
|
||||
def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, block_name, **kwargs):
|
||||
@@ -726,3 +393,459 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
|
||||
return _('Blocked manually')
|
||||
elif block_name.startswith('api:'):
|
||||
return _('Blocked because of an API integration')
|
||||
|
||||
|
||||
@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.'),
|
||||
'pretix.event.order.expirychanged': _('The order\'s expiry date has been changed.'),
|
||||
'pretix.event.order.valid_if_pending.set': _('The order has been set to be usable before it is paid.'),
|
||||
'pretix.event.order.valid_if_pending.unset': _('The order has been set to require payment before use.'),
|
||||
'pretix.event.order.expired': _('The order has been marked as expired.'),
|
||||
'pretix.event.order.paid': _('The order has been marked as paid.'),
|
||||
'pretix.event.order.cancellationrequest.deleted': _('The cancellation request has been deleted.'),
|
||||
'pretix.event.order.refunded': _('The order has been refunded.'),
|
||||
'pretix.event.order.reactivated': _('The order has been reactivated.'),
|
||||
'pretix.event.order.placed': _('The order has been created.'),
|
||||
'pretix.event.order.placed.require_approval': _(
|
||||
'The order requires approval before it can continue to be processed.'),
|
||||
'pretix.event.order.approved': _('The order has been approved.'),
|
||||
'pretix.event.order.denied': _('The order has been denied (comment: "{comment}").'),
|
||||
'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" '
|
||||
'to "{new_email}".'),
|
||||
'pretix.event.order.contact.confirmed': _(
|
||||
'The email address has been confirmed to be working (the user clicked on a link '
|
||||
'in the email for the first time).'),
|
||||
'pretix.event.order.phone.changed': _('The phone number has been changed from "{old_phone}" '
|
||||
'to "{new_phone}".'),
|
||||
'pretix.event.order.customer.changed': _('The customer account has been changed.'),
|
||||
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
|
||||
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
|
||||
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
|
||||
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
|
||||
'pretix.event.order.comment': _('The order\'s internal comment has been updated.'),
|
||||
'pretix.event.order.custom_followup_at': _('The order\'s follow-up date has been updated.'),
|
||||
'pretix.event.order.checkin_attention': _('The order\'s flag to require attention at check-in has been '
|
||||
'toggled.'),
|
||||
'pretix.event.order.checkin_text': _('The order\'s check-in text has been changed.'),
|
||||
'pretix.event.order.pretix.event.order.valid_if_pending': _('The order\'s flag to be considered valid even if '
|
||||
'unpaid has been toggled.'),
|
||||
'pretix.event.order.payment.changed': _('A new payment {local_id} has been started instead of the previous one.'),
|
||||
'pretix.event.order.email.sent': _('An unidentified type email has been sent.'),
|
||||
'pretix.event.order.email.error': _('Sending of an email has failed.'),
|
||||
'pretix.event.order.email.attachments.skipped': _('The email has been sent without attached tickets since they '
|
||||
'would have been too large to be likely to arrive.'),
|
||||
'pretix.event.order.email.custom_sent': _('A custom email has been sent.'),
|
||||
'pretix.event.order.position.email.custom_sent': _('A custom email has been sent to an attendee.'),
|
||||
'pretix.event.order.email.download_reminder_sent': _('An email has been sent with a reminder that the ticket '
|
||||
'is available for download.'),
|
||||
'pretix.event.order.email.expire_warning_sent': _('An email has been sent with a warning that the order is about '
|
||||
'to expire.'),
|
||||
'pretix.event.order.email.order_canceled': _(
|
||||
'An email has been sent to notify the user that the order has been canceled.'),
|
||||
'pretix.event.order.email.event_canceled': _('An email has been sent to notify the user that the event has '
|
||||
'been canceled.'),
|
||||
'pretix.event.order.email.order_changed': _(
|
||||
'An email has been sent to notify the user that the order has been changed.'),
|
||||
'pretix.event.order.email.order_free': _(
|
||||
'An email has been sent to notify the user that the order has been received.'),
|
||||
'pretix.event.order.email.order_paid': _(
|
||||
'An email has been sent to notify the user that payment has been received.'),
|
||||
'pretix.event.order.email.order_denied': _(
|
||||
'An email has been sent to notify the user that the order has been denied.'),
|
||||
'pretix.event.order.email.order_approved': _('An email has been sent to notify the user that the order has '
|
||||
'been approved.'),
|
||||
'pretix.event.order.email.order_placed': _(
|
||||
'An email has been sent to notify the user that the order has been received and requires payment.'),
|
||||
'pretix.event.order.email.order_placed_require_approval': _('An email has been sent to notify the user that '
|
||||
'the order has been received and requires '
|
||||
'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.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.'),
|
||||
'pretix.voucher.expired.waitinglist': _(
|
||||
'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.new()
|
||||
class VoucherRedeemedLogEntryType(VoucherLogEntryType):
|
||||
action_type = 'pretix.voucher.redeemed'
|
||||
plain = _('The voucher has been redeemed in order {order_code}.')
|
||||
|
||||
def display(self, logentry):
|
||||
data = json.loads(logentry.data)
|
||||
data = defaultdict(lambda: '?', data)
|
||||
url = reverse('control:event.order', kwargs={
|
||||
'event': logentry.event.slug,
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'code': data['order_code']
|
||||
})
|
||||
return mark_safe(self.plain.format(
|
||||
order_code='<a href="{}">{}</a>'.format(url, data['order_code']),
|
||||
))
|
||||
|
||||
|
||||
@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.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):
|
||||
def display(self, logentry):
|
||||
return self.plain.format(user=logentry.parsed_data.get('email'))
|
||||
|
||||
|
||||
@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.new()
|
||||
class TeamMemberJoinedLogEntryType(LogEntryType):
|
||||
action_type = 'pretix.team.member.joined'
|
||||
|
||||
def display(self, logentry):
|
||||
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')
|
||||
)
|
||||
|
||||
|
||||
@log_entry_types.new()
|
||||
class UserSettingsChangedLogEntryType(LogEntryType):
|
||||
action_type = 'pretix.user.settings.changed'
|
||||
|
||||
def display(self, logentry):
|
||||
text = str(_('Your account settings have been changed.'))
|
||||
if 'email' in logentry.parsed_data:
|
||||
text = text + ' ' + str(
|
||||
_('Your email address has been changed to {email}.').format(email=logentry.parsed_data['email']))
|
||||
if 'new_pw' in logentry.parsed_data:
|
||||
text = text + ' ' + str(_('Your password has been changed.'))
|
||||
if logentry.parsed_data.get('is_active') is True:
|
||||
text = text + ' ' + str(_('Your account has been enabled.'))
|
||||
elif logentry.parsed_data.get('is_active') is False:
|
||||
text = text + ' ' + str(_('Your account has been disabled.'))
|
||||
return text
|
||||
|
||||
|
||||
class UserImpersonatedLogEntryType(LogEntryType):
|
||||
def display(self, logentry):
|
||||
return self.plain.format(logentry.parsed_data['other_email'])
|
||||
|
||||
|
||||
@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.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.'),
|
||||
'pretix.organizer.footerlinks.changed': _('The footer links have been changed.'),
|
||||
'pretix.organizer.export.schedule.added': _('A scheduled export has been added.'),
|
||||
'pretix.organizer.export.schedule.changed': _('A scheduled export has been changed.'),
|
||||
'pretix.organizer.export.schedule.deleted': _('A scheduled export has been deleted.'),
|
||||
'pretix.organizer.export.schedule.executed': _('A scheduled export has been executed.'),
|
||||
'pretix.organizer.export.schedule.failed': _('A scheduled export has failed: {reason}.'),
|
||||
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
||||
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
||||
'pretix.giftcards.acceptance.acceptor.invited': _('A new gift card acceptor has been invited.'),
|
||||
'pretix.giftcards.acceptance.acceptor.removed': _('A gift card acceptor has been removed.'),
|
||||
'pretix.giftcards.acceptance.issuer.removed': _('A gift card issuer has been removed or declined.'),
|
||||
'pretix.giftcards.acceptance.issuer.accepted': _('A new gift card issuer has been accepted.'),
|
||||
'pretix.webhook.created': _('The webhook has been created.'),
|
||||
'pretix.webhook.changed': _('The webhook has been changed.'),
|
||||
'pretix.webhook.retries.expedited': _('The webhook call retry jobs have been manually expedited.'),
|
||||
'pretix.webhook.retries.dropped': _('The webhook call retry jobs have been dropped.'),
|
||||
'pretix.ssoprovider.created': _('The SSO provider has been created.'),
|
||||
'pretix.ssoprovider.changed': _('The SSO provider has been changed.'),
|
||||
'pretix.ssoprovider.deleted': _('The SSO provider has been deleted.'),
|
||||
'pretix.ssoclient.created': _('The SSO client has been created.'),
|
||||
'pretix.ssoclient.changed': _('The SSO client has been changed.'),
|
||||
'pretix.ssoclient.deleted': _('The SSO client has been deleted.'),
|
||||
'pretix.membershiptype.created': _('The membership type has been created.'),
|
||||
'pretix.membershiptype.changed': _('The membership type has been changed.'),
|
||||
'pretix.membershiptype.deleted': _('The membership type has been deleted.'),
|
||||
'pretix.saleschannel.created': _('The sales channel has been created.'),
|
||||
'pretix.saleschannel.changed': _('The sales channel has been changed.'),
|
||||
'pretix.saleschannel.deleted': _('The sales channel has been deleted.'),
|
||||
'pretix.customer.created': _('The account has been created.'),
|
||||
'pretix.customer.changed': _('The account has been changed.'),
|
||||
'pretix.customer.membership.created': _('A membership for this account has been added.'),
|
||||
'pretix.customer.membership.changed': _('A membership of this account has been changed.'),
|
||||
'pretix.customer.membership.deleted': _('A membership of this account has been deleted.'),
|
||||
'pretix.customer.anonymized': _('The account has been disabled and anonymized.'),
|
||||
'pretix.customer.password.resetrequested': _('A new password has been requested.'),
|
||||
'pretix.customer.password.set': _('A new password has been set.'),
|
||||
'pretix.reusable_medium.created': _('The reusable medium has been created.'),
|
||||
'pretix.reusable_medium.created.auto': _('The reusable medium has been created automatically.'),
|
||||
'pretix.reusable_medium.changed': _('The reusable medium has been changed.'),
|
||||
'pretix.reusable_medium.linked_orderposition.changed': _('The medium has been connected to a new ticket.'),
|
||||
'pretix.reusable_medium.linked_giftcard.changed': _('The medium has been connected to a new gift card.'),
|
||||
'pretix.email.error': _('Sending of an email has failed.'),
|
||||
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
||||
'pretix.event.canceled': _('The event has been canceled.'),
|
||||
'pretix.event.deleted': _('An event has been deleted.'),
|
||||
'pretix.event.shredder.started': _('A removal process for personal data has been started.'),
|
||||
'pretix.event.shredder.completed': _('A removal process for personal data has been completed.'),
|
||||
'pretix.event.export.schedule.added': _('A scheduled export has been added.'),
|
||||
'pretix.event.export.schedule.changed': _('A scheduled export has been changed.'),
|
||||
'pretix.event.export.schedule.deleted': _('A scheduled export has been deleted.'),
|
||||
'pretix.event.export.schedule.executed': _('A scheduled export has been executed.'),
|
||||
'pretix.event.export.schedule.failed': _('A scheduled export has failed: {reason}.'),
|
||||
'pretix.control.auth.user.created': _('The user has been created.'),
|
||||
'pretix.control.auth.user.new_source': _('A first login using {agent_type} on {os_type} from {country} has '
|
||||
'been detected.'),
|
||||
'pretix.user.settings.2fa.enabled': _('Two-factor authentication has been enabled.'),
|
||||
'pretix.user.settings.2fa.disabled': _('Two-factor authentication has been disabled.'),
|
||||
'pretix.user.settings.2fa.regenemergency': _('Your two-factor emergency codes have been regenerated.'),
|
||||
'pretix.user.settings.2fa.emergency': _('A two-factor emergency code has been generated.'),
|
||||
'pretix.user.settings.2fa.device.added': _('A new two-factor authentication device "{name}" has been added to '
|
||||
'your account.'),
|
||||
'pretix.user.settings.2fa.device.deleted': _('The two-factor authentication device "{name}" has been removed '
|
||||
'from your account.'),
|
||||
'pretix.user.settings.notifications.enabled': _('Notifications have been enabled.'),
|
||||
'pretix.user.settings.notifications.disabled': _('Notifications have been disabled.'),
|
||||
'pretix.user.settings.notifications.changed': _('Your notification settings have been changed.'),
|
||||
'pretix.user.anonymized': _('This user has been anonymized.'),
|
||||
'pretix.user.oauth.authorized': _('The application "{application_name}" has been authorized to access your '
|
||||
'account.'),
|
||||
'pretix.control.auth.user.forgot_password.mail_sent': _('Password reset mail sent.'),
|
||||
'pretix.control.auth.user.forgot_password.recovered': _('The password has been reset.'),
|
||||
'pretix.control.auth.user.forgot_password.denied.repeated': _('A repeated password reset has been denied, as '
|
||||
'the last request was less than 24 hours ago.'),
|
||||
'pretix.organizer.deleted': _('The organizer "{name}" has been deleted.'),
|
||||
'pretix.waitinglist.voucher': _('A voucher has been sent to a person on the waiting list.'), # legacy
|
||||
'pretix.event.orders.waitinglist.voucher_assigned': _('A voucher has been sent to a person on the waiting list.'),
|
||||
'pretix.event.orders.waitinglist.deleted': _('An entry has been removed from the waiting list.'),
|
||||
'pretix.event.order.waitinglist.transferred': _('An entry has been transferred to another waiting list.'), # legacy
|
||||
'pretix.event.orders.waitinglist.changed': _('An entry has been changed on the waiting list.'),
|
||||
'pretix.event.orders.waitinglist.added': _('An entry has been added to the waiting list.'),
|
||||
'pretix.team.created': _('The team has been created.'),
|
||||
'pretix.team.changed': _('The team settings have been changed.'),
|
||||
'pretix.team.deleted': _('The team has been deleted.'),
|
||||
'pretix.gate.created': _('The gate has been created.'),
|
||||
'pretix.gate.changed': _('The gate has been changed.'),
|
||||
'pretix.gate.deleted': _('The gate has been deleted.'),
|
||||
'pretix.subevent.deleted': pgettext_lazy('subevent', 'The event date has been deleted.'),
|
||||
'pretix.subevent.canceled': pgettext_lazy('subevent', 'The event date has been canceled.'),
|
||||
'pretix.subevent.changed': pgettext_lazy('subevent', 'The event date has been changed.'),
|
||||
'pretix.subevent.added': pgettext_lazy('subevent', 'The event date has been created.'),
|
||||
'pretix.subevent.quota.added': pgettext_lazy('subevent', 'A quota has been added to the event date.'),
|
||||
'pretix.subevent.quota.changed': pgettext_lazy('subevent', 'A quota has been changed on the event date.'),
|
||||
'pretix.subevent.quota.deleted': pgettext_lazy('subevent', 'A quota has been removed from the event date.'),
|
||||
'pretix.device.created': _('The device has been created.'),
|
||||
'pretix.device.changed': _('The device has been changed.'),
|
||||
'pretix.device.revoked': _('Access of the device has been revoked.'),
|
||||
'pretix.device.initialized': _('The device has been initialized.'),
|
||||
'pretix.device.keyroll': _('The access token of the device has been regenerated.'),
|
||||
'pretix.device.updated': _('The device has notified the server of an hardware or software update.'),
|
||||
'pretix.giftcards.created': _('The gift card has been created.'),
|
||||
'pretix.giftcards.modified': _('The gift card has been changed.'),
|
||||
'pretix.giftcards.transaction.manual': _('A manual transaction has been performed.'),
|
||||
'pretix.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.new_from_dict({
|
||||
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
|
||||
'pretix.event.item_meta_property.added': _('A meta property has been added to this event.'),
|
||||
'pretix.event.item_meta_property.deleted': _('A meta property has been removed from this event.'),
|
||||
'pretix.event.item_meta_property.changed': _('A meta property has been changed on this event.'),
|
||||
'pretix.event.checkinlist.added': _('The check-in list has been added.'),
|
||||
'pretix.event.checkinlist.deleted': _('The check-in list has been deleted.'),
|
||||
'pretix.event.checkinlists.deleted': _('The check-in list has been deleted.'), # backwards compatibility
|
||||
'pretix.event.checkinlist.changed': _('The check-in list has been changed.'),
|
||||
'pretix.event.settings': _('The event settings have been changed.'),
|
||||
'pretix.event.tickets.settings': _('The ticket download settings have been changed.'),
|
||||
'pretix.event.live.activated': _('The shop has been taken live.'),
|
||||
'pretix.event.live.deactivated': _('The shop has been taken offline.'),
|
||||
'pretix.event.testmode.activated': _('The shop has been taken into test mode.'),
|
||||
'pretix.event.testmode.deactivated': _('The test mode has been disabled.'),
|
||||
'pretix.event.added': _('The event has been created.'),
|
||||
'pretix.event.changed': _('The event details have been changed.'),
|
||||
'pretix.event.footerlinks.changed': _('The footer links have been changed.'),
|
||||
'pretix.event.question.option.added': _('An answer option has been added to the question.'),
|
||||
'pretix.event.question.option.deleted': _('An answer option has been removed from the question.'),
|
||||
'pretix.event.question.option.changed': _('An answer option has been changed.'),
|
||||
'pretix.event.permissions.added': _('A user has been added to the event team.'),
|
||||
'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.new_from_dict({
|
||||
'pretix.event.plugins.enabled': _('The plugin has been enabled.'),
|
||||
'pretix.event.plugins.disabled': _('The plugin has been disabled.'),
|
||||
})
|
||||
class EventPluginStateLogEntryType(EventLogEntryType):
|
||||
object_link_wrapper = _('Plugin {val}')
|
||||
|
||||
def get_object_link_info(self, logentry) -> dict:
|
||||
if 'plugin' in logentry.parsed_data:
|
||||
app = app_cache.get(logentry.parsed_data['plugin'])
|
||||
if app and hasattr(app, 'PretixPluginMeta'):
|
||||
return {
|
||||
'href': reverse('control:event.settings.plugins', kwargs={
|
||||
'organizer': logentry.event.organizer.slug,
|
||||
'event': logentry.event.slug,
|
||||
}) + '#plugin_' + logentry.parsed_data['plugin'],
|
||||
'val': app.PretixPluginMeta.name
|
||||
}
|
||||
|
||||
|
||||
@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.'),
|
||||
'pretix.event.item.deleted': _('The product has been deleted.'),
|
||||
'pretix.event.item.addons.added': _('An add-on has been added to this product.'),
|
||||
'pretix.event.item.addons.removed': _('An add-on has been removed from this product.'),
|
||||
'pretix.event.item.addons.changed': _('An add-on has been changed on this product.'),
|
||||
'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:
|
||||
# Backwards compatibility
|
||||
var = ItemVariation.objects.filter(id=logentry.parsed_data['id']).first()
|
||||
if var:
|
||||
logentry.parsed_data['value'] = str(var.value)
|
||||
else:
|
||||
logentry.parsed_data['value'] = '?'
|
||||
else:
|
||||
logentry.parsed_data['value'] = LazyI18nString(logentry.parsed_data['value'])
|
||||
return super().display(logentry)
|
||||
|
||||
|
||||
@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.'),
|
||||
'pretix.event.order.payment.started': _('Payment {local_id} has been started.'),
|
||||
'pretix.event.order.payment.failed': _('Payment {local_id} has failed.'),
|
||||
'pretix.event.order.quotaexceeded': _('The order could not be marked as paid: {message}'),
|
||||
'pretix.event.order.overpaid': _('The order has been overpaid.'),
|
||||
'pretix.event.order.refund.created': _('Refund {local_id} has been created.'),
|
||||
'pretix.event.order.refund.created.externally': _('Refund {local_id} has been created by an external entity.'),
|
||||
'pretix.event.order.refund.requested': _('The customer requested you to issue a refund.'),
|
||||
'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 CoreOrderPaymentLogEntryType(OrderLogEntryType):
|
||||
pass
|
||||
|
||||
|
||||
@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.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.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.new()
|
||||
class LegacyCheckinLogEntryType(OrderLogEntryType):
|
||||
action_type = 'pretix.control.views.checkin'
|
||||
|
||||
def display(self, logentry):
|
||||
# deprecated
|
||||
dt = dateutil.parser.parse(logentry.parsed_data.get('datetime'))
|
||||
tz = logentry.event.timezone
|
||||
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
||||
if 'list' in logentry.parsed_data:
|
||||
try:
|
||||
checkin_list = logentry.event.checkin_lists.get(pk=logentry.parsed_data.get('list')).name
|
||||
except CheckinList.DoesNotExist:
|
||||
checkin_list = _("(unknown)")
|
||||
else:
|
||||
checkin_list = _("(unknown)")
|
||||
|
||||
if logentry.parsed_data.get('first'):
|
||||
return _('Position #{posid} has been checked in manually at {datetime} on list "{list}".').format(
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list,
|
||||
)
|
||||
return _('Position #{posid} has been checked in again at {datetime} on list "{list}".').format(
|
||||
posid=logentry.parsed_data.get('positionid'),
|
||||
datetime=dt_formatted,
|
||||
list=checkin_list
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<legend>{{ catlabel }}</legend>
|
||||
<div class="plugin-list">
|
||||
{% for plugin in plist %}
|
||||
<div class="plugin-container {% if plugin.featured %}featured-plugin{% endif %}">
|
||||
<div class="plugin-container {% if plugin.featured %}featured-plugin{% endif %}" id="plugin_{{ plugin.module }}">
|
||||
{% if plugin.featured %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
|
||||
@@ -5,8 +5,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:45+0000\n"
|
||||
"PO-Revision-Date: 2025-01-10 10:00+0000\n"
|
||||
"Last-Translator: Martin Gross <gross@rami.io>\n"
|
||||
"PO-Revision-Date: 2025-01-15 16:59+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix/de/"
|
||||
">\n"
|
||||
"Language: de\n"
|
||||
@@ -91,7 +91,7 @@ msgstr "Italienisch"
|
||||
|
||||
#: pretix/_base_settings.py:97
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
msgstr "Japanisch"
|
||||
|
||||
#: pretix/_base_settings.py:98
|
||||
msgid "Latvian"
|
||||
@@ -9689,10 +9689,8 @@ msgid "Automatically on user request"
|
||||
msgstr "Automatisch auf Anfrage des Kunden"
|
||||
|
||||
#: pretix/base/settings.py:1043 pretix/base/settings.py:1056
|
||||
#, fuzzy
|
||||
#| msgid "Automatically on user request"
|
||||
msgid "Automatically on user request for paid orders"
|
||||
msgstr "Automatisch auf Anfrage des Kunden"
|
||||
msgstr "Automatisch auf Anfrage des Kunden für bezahlte Bestellungen"
|
||||
|
||||
#: pretix/base/settings.py:1044
|
||||
msgid "Automatically for all created orders"
|
||||
@@ -12528,17 +12526,17 @@ msgstr "Foto hochladen"
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldate.html:14
|
||||
#, python-format
|
||||
msgid "%(number)s days %(relation)s %(relation_to)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s Tage %(relation)s %(relation_to)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:14
|
||||
#, python-format
|
||||
msgid "%(number)s minutes %(relation)s %(relation_to)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s Minuten %(relation)s %(relation_to)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:18
|
||||
#, python-format
|
||||
msgid "%(number)s days %(relation)s %(relation_to)s at %(time_of_day)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s Tage %(relation)s %(relation_to)s um %(time_of_day)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/redirect.html:5
|
||||
#: pretix/base/templates/pretixbase/redirect.html:9
|
||||
@@ -14976,6 +14974,8 @@ msgid ""
|
||||
"This affects both the ticket secret (often used as a QR code) as well as the "
|
||||
"link used to individually access the ticket."
|
||||
msgstr ""
|
||||
"Dies betrifft sowohl den Ticketcode (oft als QR-Code verwendet) als auch den "
|
||||
"Link zum individuellen Zugriff auf das Ticket."
|
||||
|
||||
#: pretix/control/forms/orders.py:499
|
||||
msgid "Cancel this position"
|
||||
@@ -31215,7 +31215,7 @@ msgstr "Kreditkarte"
|
||||
#: pretix/plugins/stripe/payment.py:1186
|
||||
#, python-brace-format
|
||||
msgid "expires {month}/{year}"
|
||||
msgstr ""
|
||||
msgstr "läuft ab {month}/{year}"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1191
|
||||
msgid "SEPA Debit via Stripe"
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:45+0000\n"
|
||||
"PO-Revision-Date: 2025-01-10 10:00+0000\n"
|
||||
"Last-Translator: Martin Gross <gross@rami.io>\n"
|
||||
"PO-Revision-Date: 2025-01-16 01:00+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/de_Informal/>\n"
|
||||
"Language: de_Informal\n"
|
||||
@@ -93,7 +93,7 @@ msgstr "Italienisch"
|
||||
|
||||
#: pretix/_base_settings.py:97
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
msgstr "Japanisch"
|
||||
|
||||
#: pretix/_base_settings.py:98
|
||||
msgid "Latvian"
|
||||
@@ -9675,10 +9675,8 @@ msgid "Automatically on user request"
|
||||
msgstr "Automatisch auf Anfrage des Kunden"
|
||||
|
||||
#: pretix/base/settings.py:1043 pretix/base/settings.py:1056
|
||||
#, fuzzy
|
||||
#| msgid "Automatically on user request"
|
||||
msgid "Automatically on user request for paid orders"
|
||||
msgstr "Automatisch auf Anfrage des Kunden"
|
||||
msgstr "Automatisch auf Anfrage des Kunden für bezahlte Bestellungen"
|
||||
|
||||
#: pretix/base/settings.py:1044
|
||||
msgid "Automatically for all created orders"
|
||||
@@ -12506,17 +12504,17 @@ msgstr "Foto hochladen"
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldate.html:14
|
||||
#, python-format
|
||||
msgid "%(number)s days %(relation)s %(relation_to)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s Tage %(relation)s %(relation_to)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:14
|
||||
#, python-format
|
||||
msgid "%(number)s minutes %(relation)s %(relation_to)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s Minuten %(relation)s %(relation_to)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:18
|
||||
#, python-format
|
||||
msgid "%(number)s days %(relation)s %(relation_to)s at %(time_of_day)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s Tage %(relation)s %(relation_to)s um %(time_of_day)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/redirect.html:5
|
||||
#: pretix/base/templates/pretixbase/redirect.html:9
|
||||
@@ -14951,6 +14949,8 @@ msgid ""
|
||||
"This affects both the ticket secret (often used as a QR code) as well as the "
|
||||
"link used to individually access the ticket."
|
||||
msgstr ""
|
||||
"Dies betrifft sowohl den Ticketcode (oft als QR-Code verwendet) als auch den "
|
||||
"Link zum individuellen Zugriff auf das Ticket."
|
||||
|
||||
#: pretix/control/forms/orders.py:499
|
||||
msgid "Cancel this position"
|
||||
@@ -31163,7 +31163,7 @@ msgstr "Kreditkarte"
|
||||
#: pretix/plugins/stripe/payment.py:1186
|
||||
#, python-brace-format
|
||||
msgid "expires {month}/{year}"
|
||||
msgstr ""
|
||||
msgstr "läuft ab {month}/{year}"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1191
|
||||
msgid "SEPA Debit via Stripe"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,16 +8,16 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:46+0000\n"
|
||||
"PO-Revision-Date: 2024-11-18 15:48+0000\n"
|
||||
"PO-Revision-Date: 2025-01-16 10:32+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/es/>\n"
|
||||
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/"
|
||||
"pretix-js/es/>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.8.3\n"
|
||||
"X-Generator: Weblate 5.9.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -775,13 +775,13 @@ msgstr "Precio"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr ""
|
||||
msgstr "Prix initial : %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "New price: %s"
|
||||
msgstr ""
|
||||
msgstr "Nuevo precio: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
@@ -834,7 +834,7 @@ msgstr "desde %(currency)s %(price)s"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr ""
|
||||
msgstr "Imagen de %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
|
||||
@@ -4,7 +4,7 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:45+0000\n"
|
||||
"PO-Revision-Date: 2024-12-17 18:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-16 10:32+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix/fr/"
|
||||
">\n"
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.8.4\n"
|
||||
"X-Generator: Weblate 5.9.2\n"
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
@@ -89,7 +89,7 @@ msgstr "Italien"
|
||||
|
||||
#: pretix/_base_settings.py:97
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
msgstr "japonais"
|
||||
|
||||
#: pretix/_base_settings.py:98
|
||||
msgid "Latvian"
|
||||
@@ -9755,10 +9755,8 @@ msgid "Automatically on user request"
|
||||
msgstr "Automatiquement à la demande de l'utilisateur"
|
||||
|
||||
#: pretix/base/settings.py:1043 pretix/base/settings.py:1056
|
||||
#, fuzzy
|
||||
#| msgid "Automatically on user request"
|
||||
msgid "Automatically on user request for paid orders"
|
||||
msgstr "Automatiquement à la demande de l'utilisateur"
|
||||
msgstr "Automatiquement à la demande de l'utilisateur pour les commandes payées"
|
||||
|
||||
#: pretix/base/settings.py:1044
|
||||
msgid "Automatically for all created orders"
|
||||
@@ -12630,17 +12628,17 @@ msgstr "Télécharger une photo"
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldate.html:14
|
||||
#, python-format
|
||||
msgid "%(number)s days %(relation)s %(relation_to)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s jours %(relation)s %(relation_to)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:14
|
||||
#, python-format
|
||||
msgid "%(number)s minutes %(relation)s %(relation_to)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s minutes %(relation)s %(relation_to)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/reldatetime.html:18
|
||||
#, python-format
|
||||
msgid "%(number)s days %(relation)s %(relation_to)s at %(time_of_day)s"
|
||||
msgstr ""
|
||||
msgstr "%(number)s jours %(relation)s %(relation_to)s à %(time_of_day)s"
|
||||
|
||||
#: pretix/base/templates/pretixbase/redirect.html:5
|
||||
#: pretix/base/templates/pretixbase/redirect.html:9
|
||||
@@ -15085,6 +15083,8 @@ msgid ""
|
||||
"This affects both the ticket secret (often used as a QR code) as well as the "
|
||||
"link used to individually access the ticket."
|
||||
msgstr ""
|
||||
"Cela concerne à la fois le secret du billet (souvent utilisé comme un code "
|
||||
"QR) et le lien utilisé pour accéder individuellement au billet."
|
||||
|
||||
#: pretix/control/forms/orders.py:499
|
||||
msgid "Cancel this position"
|
||||
@@ -31465,7 +31465,7 @@ msgstr "Carte de crédit"
|
||||
#: pretix/plugins/stripe/payment.py:1186
|
||||
#, python-brace-format
|
||||
msgid "expires {month}/{year}"
|
||||
msgstr ""
|
||||
msgstr "expire {month}/{year}"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:1191
|
||||
msgid "SEPA Debit via Stripe"
|
||||
|
||||
@@ -7,7 +7,7 @@ msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:46+0000\n"
|
||||
"PO-Revision-Date: 2024-12-03 20:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-16 10:32+0000\n"
|
||||
"Last-Translator: CVZ-es <damien.bremont@casadevelazquez.org>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"fr/>\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 5.8.4\n"
|
||||
"X-Generator: Weblate 5.9.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -774,13 +774,13 @@ msgstr "Prix"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr ""
|
||||
msgstr "Prix initial : %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "New price: %s"
|
||||
msgstr ""
|
||||
msgstr "Nouveau prix : %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
@@ -833,7 +833,7 @@ msgstr "de %(currency)s %(price)s"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr ""
|
||||
msgstr "Image de %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:45+0000\n"
|
||||
"PO-Revision-Date: 2025-01-11 21:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-17 00:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"ja/>\n"
|
||||
@@ -22412,7 +22412,7 @@ msgstr "フィルターを除去"
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:146
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:207
|
||||
msgid "Order paid / total"
|
||||
msgstr "注文は支払い済みです / 合計です"
|
||||
msgstr "支払済の注文 / 合計"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:177
|
||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html:109
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-01-15 16:45+0000\n"
|
||||
"PO-Revision-Date: 2025-01-11 21:00+0000\n"
|
||||
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
|
||||
"PO-Revision-Date: 2025-01-16 12:06+0000\n"
|
||||
"Last-Translator: deborahfoell <deborah.foell@om.org>\n"
|
||||
"Language-Team: Korean <https://translate.pretix.eu/projects/pretix/pretix/ko/"
|
||||
">\n"
|
||||
"Language: ko\n"
|
||||
@@ -21,11 +21,11 @@ msgstr ""
|
||||
|
||||
#: pretix/_base_settings.py:79
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
msgstr "영어"
|
||||
|
||||
#: pretix/_base_settings.py:80
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
msgstr "독일어"
|
||||
|
||||
#: pretix/_base_settings.py:81
|
||||
msgid "German (informal)"
|
||||
@@ -44,8 +44,9 @@ msgid "Catalan"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/_base_settings.py:85
|
||||
#, fuzzy
|
||||
msgid "Chinese (simplified)"
|
||||
msgstr ""
|
||||
msgstr "중국어"
|
||||
|
||||
#: pretix/_base_settings.py:86
|
||||
msgid "Chinese (traditional)"
|
||||
@@ -93,7 +94,7 @@ msgstr ""
|
||||
|
||||
#: pretix/_base_settings.py:97
|
||||
msgid "Japanese"
|
||||
msgstr ""
|
||||
msgstr "일본어"
|
||||
|
||||
#: pretix/_base_settings.py:98
|
||||
msgid "Latvian"
|
||||
|
||||
@@ -26,13 +26,12 @@ from collections import defaultdict
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import get_template
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.logentrytypes import EventLogEntryType, log_entry_types
|
||||
from pretix.base.models import Event, Order
|
||||
from pretix.base.signals import (
|
||||
event_copy_data, item_copy_data, logentry_display, logentry_object_link,
|
||||
register_data_exporters,
|
||||
event_copy_data, item_copy_data, register_data_exporters,
|
||||
)
|
||||
from pretix.control.signals import (
|
||||
item_forms, nav_event, order_info, order_position_buttons,
|
||||
@@ -173,35 +172,13 @@ def control_order_info(sender: Event, request, order: Order, **kwargs):
|
||||
return template.render(ctx, request=request)
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="badges_logentry_display")
|
||||
def badges_logentry_display(sender, logentry, **kwargs):
|
||||
if not logentry.action_type.startswith('pretix.plugins.badges'):
|
||||
return
|
||||
|
||||
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.'),
|
||||
}
|
||||
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
|
||||
|
||||
@receiver(signal=logentry_object_link, dispatch_uid="badges_logentry_object_link")
|
||||
def badges_logentry_object_link(sender, logentry, **kwargs):
|
||||
if not logentry.action_type.startswith('pretix.plugins.badges.layout') or not isinstance(logentry.content_object,
|
||||
BadgeLayout):
|
||||
return
|
||||
|
||||
a_text = _('Badge layout {val}')
|
||||
a_map = {
|
||||
'href': reverse('plugins:badges:edit', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug,
|
||||
'layout': logentry.content_object.id
|
||||
}),
|
||||
'val': escape(logentry.content_object.name),
|
||||
}
|
||||
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
|
||||
return a_text.format_map(a_map)
|
||||
@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'
|
||||
|
||||
@@ -25,9 +25,12 @@ from django.urls import resolve, reverse
|
||||
from django.utils.translation import gettext_lazy as _, gettext_noop
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.signals import logentry_display, register_payment_providers
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.control.signals import html_head, nav_event, nav_organizer
|
||||
|
||||
from ...base.logentrytypes import (
|
||||
ClearDataShredderMixin, OrderLogEntryType, log_entry_types,
|
||||
)
|
||||
from ...base.settings import settings_hierarkey
|
||||
from .payment import BankTransfer
|
||||
|
||||
@@ -117,13 +120,10 @@ def html_head_presale(sender, request=None, **kwargs):
|
||||
return ""
|
||||
|
||||
|
||||
@receiver(signal=logentry_display)
|
||||
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
plains = {
|
||||
'pretix.plugins.banktransfer.order.email.invoice': _('The invoice was sent to the designated email address.'),
|
||||
}
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
@log_entry_types.new()
|
||||
class BanktransferOrderEmailInvoiceLogEntryType(OrderLogEntryType, ClearDataShredderMixin):
|
||||
action_type = 'pretix.plugins.banktransfer.order.email.invoice'
|
||||
plain = _('The invoice was sent to the designated email address.')
|
||||
|
||||
|
||||
settings_hierarkey.add_default(
|
||||
|
||||
@@ -22,17 +22,10 @@
|
||||
|
||||
from django.dispatch import receiver
|
||||
|
||||
from pretix.base.signals import logentry_display, register_payment_providers
|
||||
from pretix.base.signals import register_payment_providers
|
||||
|
||||
|
||||
@receiver(register_payment_providers, dispatch_uid="payment_paypal")
|
||||
def register_payment_provider(sender, **kwargs):
|
||||
from .payment import Paypal
|
||||
return Paypal
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="paypal_logentry_display")
|
||||
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
from pretix.plugins.paypal2.signals import pretixcontrol_logentry_display
|
||||
|
||||
return pretixcontrol_logentry_display(sender, logentry, **kwargs)
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
# 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/>.
|
||||
#
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from django import forms
|
||||
@@ -32,10 +31,11 @@ from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
|
||||
from pretix.base.forms import SecretKeySettingsField
|
||||
from pretix.base.logentrytypes import EventLogEntryType, log_entry_types
|
||||
from pretix.base.middleware import _merge_csp, _parse_csp, _render_csp
|
||||
from pretix.base.settings import settings_hierarkey
|
||||
from pretix.base.signals import (
|
||||
logentry_display, register_global_settings, register_payment_providers,
|
||||
register_global_settings, register_payment_providers,
|
||||
)
|
||||
from pretix.plugins.paypal2.payment import PaypalMethod
|
||||
from pretix.presale.signals import html_head, process_response
|
||||
@@ -47,33 +47,32 @@ def register_payment_provider(sender, **kwargs):
|
||||
return [PaypalSettingsHolder, PaypalWallet, PaypalAPM]
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="paypal2_logentry_display")
|
||||
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
if logentry.action_type != 'pretix.plugins.paypal.event':
|
||||
return
|
||||
@log_entry_types.new()
|
||||
class PaypalEventLogEntryType(EventLogEntryType):
|
||||
action_type = 'pretix.plugins.paypal.event'
|
||||
|
||||
data = json.loads(logentry.data)
|
||||
event_type = data.get('event_type')
|
||||
text = None
|
||||
plains = {
|
||||
'PAYMENT.SALE.COMPLETED': _('Payment completed.'),
|
||||
'PAYMENT.SALE.DENIED': _('Payment denied.'),
|
||||
'PAYMENT.SALE.REFUNDED': _('Payment refunded.'),
|
||||
'PAYMENT.SALE.REVERSED': _('Payment reversed.'),
|
||||
'PAYMENT.SALE.PENDING': _('Payment pending.'),
|
||||
'CHECKOUT.ORDER.APPROVED': pgettext_lazy('paypal', 'Order approved.'),
|
||||
'CHECKOUT.ORDER.COMPLETED': pgettext_lazy('paypal', 'Order completed.'),
|
||||
'PAYMENT.CAPTURE.COMPLETED': pgettext_lazy('paypal', 'Capture completed.'),
|
||||
'PAYMENT.CAPTURE.PENDING': pgettext_lazy('paypal', 'Capture pending.'),
|
||||
}
|
||||
def display(self, logentry):
|
||||
event_type = logentry.parsed_data.get('event_type')
|
||||
text = None
|
||||
plains = {
|
||||
'PAYMENT.SALE.COMPLETED': _('Payment completed.'),
|
||||
'PAYMENT.SALE.DENIED': _('Payment denied.'),
|
||||
'PAYMENT.SALE.REFUNDED': _('Payment refunded.'),
|
||||
'PAYMENT.SALE.REVERSED': _('Payment reversed.'),
|
||||
'PAYMENT.SALE.PENDING': _('Payment pending.'),
|
||||
'CHECKOUT.ORDER.APPROVED': pgettext_lazy('paypal', 'Order approved.'),
|
||||
'CHECKOUT.ORDER.COMPLETED': pgettext_lazy('paypal', 'Order completed.'),
|
||||
'PAYMENT.CAPTURE.COMPLETED': pgettext_lazy('paypal', 'Capture completed.'),
|
||||
'PAYMENT.CAPTURE.PENDING': pgettext_lazy('paypal', 'Capture pending.'),
|
||||
}
|
||||
|
||||
if event_type in plains:
|
||||
text = plains[event_type]
|
||||
else:
|
||||
text = event_type
|
||||
if event_type in plains:
|
||||
text = plains[event_type]
|
||||
else:
|
||||
text = event_type
|
||||
|
||||
if text:
|
||||
return _('PayPal reported an event: {}').format(text)
|
||||
if text:
|
||||
return _('PayPal reported an event: {}').format(text)
|
||||
|
||||
|
||||
@receiver(register_global_settings, dispatch_uid='paypal2_global_settings')
|
||||
|
||||
@@ -46,13 +46,16 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
from pretix.base.logentrytypes import (
|
||||
EventLogEntryType, OrderLogEntryType, log_entry_types,
|
||||
)
|
||||
from pretix.base.models import SubEvent
|
||||
from pretix.base.signals import (
|
||||
EventPluginSignal, event_copy_data, logentry_display, periodic_task,
|
||||
EventPluginSignal, event_copy_data, periodic_task,
|
||||
)
|
||||
from pretix.control.signals import nav_event
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.plugins.sendmail.models import ScheduledMail
|
||||
from pretix.plugins.sendmail.models import Rule, ScheduledMail
|
||||
from pretix.plugins.sendmail.views import OrderSendView, WaitinglistSendView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -115,21 +118,28 @@ def control_nav_import(sender, request=None, **kwargs):
|
||||
]
|
||||
|
||||
|
||||
@receiver(signal=logentry_display)
|
||||
def pretixcontrol_logentry_display(sender, logentry, **kwargs):
|
||||
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.'),
|
||||
'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.'),
|
||||
'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'),
|
||||
}
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
@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.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.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}')
|
||||
object_link_viewname = 'plugins:sendmail:rule.update'
|
||||
object_link_argname = 'rule'
|
||||
|
||||
|
||||
@receiver(periodic_task)
|
||||
|
||||
@@ -24,10 +24,9 @@ import json
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import get_template
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from pretix.base.logentrytypes import EventLogEntryType, log_entry_types
|
||||
from pretix.base.models import Event, SalesChannel
|
||||
from pretix.base.signals import ( # NOQA: legacy import
|
||||
EventPluginSignal, event_copy_data, item_copy_data, layout_text_variables,
|
||||
@@ -134,38 +133,16 @@ def pdf_event_copy_data_receiver(sender, other, item_map, question_map, **kwargs
|
||||
return layout_map
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="pretix_ticketoutputpdf_logentry_display")
|
||||
def pdf_logentry_display(sender, logentry, **kwargs):
|
||||
if not logentry.action_type.startswith('pretix.plugins.ticketoutputpdf'):
|
||||
return
|
||||
|
||||
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.'),
|
||||
}
|
||||
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
|
||||
|
||||
@receiver(signal=logentry_object_link, dispatch_uid="pretix_ticketoutputpdf_logentry_object_link")
|
||||
def pdf_logentry_object_link(sender, logentry, **kwargs):
|
||||
if not logentry.action_type.startswith('pretix.plugins.ticketoutputpdf.layout') or not isinstance(
|
||||
logentry.content_object, TicketLayout):
|
||||
return
|
||||
|
||||
a_text = _('Ticket layout {val}')
|
||||
a_map = {
|
||||
'href': reverse('plugins:ticketoutputpdf:edit', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug,
|
||||
'layout': logentry.content_object.id
|
||||
}),
|
||||
'val': escape(logentry.content_object.name),
|
||||
}
|
||||
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
|
||||
return a_text.format_map(a_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}')
|
||||
object_link_viewname = 'plugins:ticketoutputpdf:edit'
|
||||
object_link_argname = 'layout'
|
||||
|
||||
|
||||
def _ticket_layouts_for_item(request, item):
|
||||
|
||||
@@ -435,14 +435,6 @@ REST_FRAMEWORK = {
|
||||
}
|
||||
|
||||
|
||||
CORE_MODULES = {
|
||||
"pretix.base",
|
||||
"pretix.presale",
|
||||
"pretix.control",
|
||||
"pretix.plugins.checkinlists",
|
||||
"pretix.plugins.reports",
|
||||
}
|
||||
|
||||
MIDDLEWARE = [
|
||||
'pretix.helpers.logs.RequestIdMiddleware',
|
||||
'pretix.api.middleware.IdempotencyMiddleware',
|
||||
|
||||
@@ -999,20 +999,20 @@ Vue.component('pretix-widget-event-form', {
|
||||
+ '</div>'
|
||||
|
||||
// Event name
|
||||
+ '<div class="pretix-widget-event-header" v-if="$root.events || $root.weeks || $root.days">'
|
||||
+ '<div class="pretix-widget-event-header" v-if="display_event_info">'
|
||||
+ '<strong>{{ $root.name }}</strong>'
|
||||
+ '</div>'
|
||||
|
||||
// Date range
|
||||
+ '<div class="pretix-widget-event-details" v-if="($root.events || $root.weeks || $root.days) && $root.date_range">'
|
||||
+ '<div class="pretix-widget-event-details" v-if="display_event_info && $root.date_range">'
|
||||
+ '{{ $root.date_range }}'
|
||||
+ '</div>'
|
||||
|
||||
// Date range
|
||||
+ '<div class="pretix-widget-event-location" v-if="($root.events || $root.weeks || $root.days) && $root.location" v-html="$root.location"></div>'
|
||||
// Location
|
||||
+ '<div class="pretix-widget-event-location" v-if="display_event_info && $root.location" v-html="$root.location"></div>'
|
||||
|
||||
// Form start
|
||||
+ '<div class="pretix-widget-event-description" v-if="($root.events || $root.weeks || $root.days) && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
|
||||
+ '<div class="pretix-widget-event-description" v-if="display_event_info && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
|
||||
+ '<form method="post" :action="$root.formAction" ref="form" :target="$root.formTarget">'
|
||||
+ '<input type="hidden" name="_voucher_code" :value="$root.voucher_code" v-if="$root.voucher_code">'
|
||||
+ '<input type="hidden" name="subevent" :value="$root.subevent" />'
|
||||
@@ -1097,6 +1097,9 @@ Vue.component('pretix-widget-event-form', {
|
||||
this.$root.$off('focus_voucher_field', this.focus_voucher_field)
|
||||
},
|
||||
computed: {
|
||||
display_event_info: function () {
|
||||
return this.$root.display_event_info || (this.$root.display_event_info === null && (this.$root.events || this.$root.weeks || this.$root.days));
|
||||
},
|
||||
id_cart_exists_msg: function () {
|
||||
return this.$root.html_id + '-cart-exists';
|
||||
},
|
||||
@@ -1259,14 +1262,19 @@ Vue.component('pretix-widget-event-list', {
|
||||
+ strings['back']
|
||||
+ '</a>'
|
||||
+ '</div>'
|
||||
+ '<div class="pretix-widget-event-header" v-if="$root.parent_stack.length > 0">'
|
||||
+ '<div class="pretix-widget-event-header" v-if="display_event_info">'
|
||||
+ '<strong>{{ $root.name }}</strong>'
|
||||
+ '</div>'
|
||||
+ '<div class="pretix-widget-event-description" v-if="$root.parent_stack.length > 0 && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
|
||||
+ '<div class="pretix-widget-event-description" v-if="display_event_info && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
|
||||
+ '<pretix-widget-event-list-filter-form v-if="!$root.disable_filters && $root.meta_filter_fields.length > 0"></pretix-widget-event-list-filter-form>'
|
||||
+ '<pretix-widget-event-list-entry v-for="event in $root.events" :event="event" :key="event.url"></pretix-widget-event-list-entry>'
|
||||
+ '<p class="pretix-widget-event-list-load-more" v-if="$root.has_more_events"><button @click.prevent.stop="load_more">'+strings.load_more+'</button></p>'
|
||||
+ '</div>'),
|
||||
computed: {
|
||||
display_event_info: function () {
|
||||
return this.$root.display_event_info || (this.$root.display_event_info === null && this.$root.parent_stack.length > 0);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
back_to_calendar: function () {
|
||||
this.$root.offset = 0;
|
||||
@@ -1477,10 +1485,10 @@ Vue.component('pretix-widget-event-calendar', {
|
||||
+ '</div>'
|
||||
|
||||
// Headline
|
||||
+ '<div class="pretix-widget-event-header" v-if="$root.parent_stack.length > 0">'
|
||||
+ '<div class="pretix-widget-event-header" v-if="display_event_info">'
|
||||
+ '<strong>{{ $root.name }}</strong>'
|
||||
+ '</div>'
|
||||
+ '<div class="pretix-widget-event-description" v-if="$root.parent_stack.length > 0 && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
|
||||
+ '<div class="pretix-widget-event-description" v-if="display_event_info && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
|
||||
|
||||
// Filter
|
||||
+ '<pretix-widget-event-list-filter-form v-if="!$root.disable_filters && $root.meta_filter_fields.length > 0"></pretix-widget-event-list-filter-form>'
|
||||
@@ -1515,6 +1523,9 @@ Vue.component('pretix-widget-event-calendar', {
|
||||
+ '</table>'
|
||||
+ '</div>'),
|
||||
computed: {
|
||||
display_event_info: function () {
|
||||
return this.$root.display_event_info || (this.$root.display_event_info === null && this.$root.parent_stack.length > 0);
|
||||
},
|
||||
monthname: function () {
|
||||
return strings['months'][this.$root.date.substr(5, 2)] + ' ' + this.$root.date.substr(0, 4);
|
||||
}
|
||||
@@ -1563,7 +1574,7 @@ Vue.component('pretix-widget-event-week-calendar', {
|
||||
+ '</div>'
|
||||
|
||||
// Event header
|
||||
+ '<div class="pretix-widget-event-header" v-if="$root.parent_stack.length > 0">'
|
||||
+ '<div class="pretix-widget-event-header" v-if="display_event_info">'
|
||||
+ '<strong>{{ $root.name }}</strong>'
|
||||
+ '</div>'
|
||||
|
||||
@@ -1571,7 +1582,7 @@ Vue.component('pretix-widget-event-week-calendar', {
|
||||
+ '<pretix-widget-event-list-filter-form v-if="!$root.disable_filters && $root.meta_filter_fields.length > 0"></pretix-widget-event-list-filter-form>'
|
||||
|
||||
// Calendar navigation
|
||||
+ '<div class="pretix-widget-event-description" v-if="$root.parent_stack.length > 0 && $root.frontpage_text" v-html="$root.frontpage_text"></div>'
|
||||
+ '<div class="pretix-widget-event-description" v-if="$root.frontpage_text && display_event_info" v-html="$root.frontpage_text"></div>'
|
||||
+ '<div class="pretix-widget-event-calendar-head">'
|
||||
+ '<a class="pretix-widget-event-calendar-previous-month" href="#" @click.prevent.stop="prevweek" role="button">« '
|
||||
+ strings['previous_week']
|
||||
@@ -1593,11 +1604,14 @@ Vue.component('pretix-widget-event-week-calendar', {
|
||||
+ '</div>'
|
||||
+ '</div>'),
|
||||
computed: {
|
||||
display_event_info: function () {
|
||||
return this.$root.display_event_info || (this.$root.display_event_info === null && this.$root.parent_stack.length > 0);
|
||||
},
|
||||
weekname: function () {
|
||||
var curWeek = this.$root.week[1];
|
||||
var curYear = this.$root.week[0];
|
||||
return curWeek + ' / ' + curYear;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
back_to_list: function () {
|
||||
@@ -2089,6 +2103,12 @@ var create_widget = function (element, html_id=null) {
|
||||
var disable_iframe = element.attributes["disable-iframe"] ? true : false;
|
||||
var disable_vouchers = element.attributes["disable-vouchers"] ? true : false;
|
||||
var disable_filters = element.attributes["disable-filters"] ? true : false;
|
||||
var display_event_info = element.getAttribute("display-event-info"); // null means "auto" (as before), everything other than "false" is true
|
||||
if (display_event_info !== null && display_event_info !== "auto") {
|
||||
display_event_info = display_event_info !== "false";
|
||||
} else {
|
||||
display_event_info = null;
|
||||
}
|
||||
var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data));
|
||||
var filter = element.attributes.filter ? element.attributes.filter.value : null;
|
||||
var items = element.attributes.items ? element.attributes.items.value : null;
|
||||
@@ -2165,6 +2185,7 @@ var create_widget = function (element, html_id=null) {
|
||||
vouchers_exist: false,
|
||||
disable_vouchers: disable_vouchers,
|
||||
disable_filters: disable_filters,
|
||||
display_event_info: display_event_info,
|
||||
cart_exists: false,
|
||||
itemcount: 0,
|
||||
overlay: null,
|
||||
|
||||
179
src/tests/base/test_registry.py
Normal file
179
src/tests/base/test_registry.py
Normal file
@@ -0,0 +1,179 @@
|
||||
#
|
||||
# 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 unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from pretix.base.logentrytypes import (
|
||||
ItemLogEntryType, LogEntryType, LogEntryTypeRegistry,
|
||||
)
|
||||
from pretix.base.signals import Registry
|
||||
|
||||
|
||||
def test_registry_classes():
|
||||
animal_type_registry = Registry({"type": lambda s: s.__name__, "classis": lambda s: s.classis})
|
||||
|
||||
@animal_type_registry.register
|
||||
class Cat:
|
||||
classis = 'mammalia'
|
||||
|
||||
def make_sound(self):
|
||||
return "meow"
|
||||
|
||||
@animal_type_registry.register
|
||||
class Dog:
|
||||
classis = 'mammalia'
|
||||
|
||||
def make_sound(self):
|
||||
return "woof"
|
||||
|
||||
@animal_type_registry.register
|
||||
class Cricket:
|
||||
classis = 'insecta'
|
||||
|
||||
def make_sound(self):
|
||||
return "chirp"
|
||||
|
||||
# test retrieving and instantiating a class based on metadata value
|
||||
clz, meta = animal_type_registry.get(type="Cat")
|
||||
assert clz().make_sound() == "meow"
|
||||
assert meta.get('type') == "Cat"
|
||||
|
||||
clz, meta = animal_type_registry.get(type="Dog")
|
||||
assert clz().make_sound() == "woof"
|
||||
assert meta.get('type') == "Dog"
|
||||
|
||||
# check that None is returned when no class exists with the specified metadata value
|
||||
clz, meta = animal_type_registry.get(type="Unicorn")
|
||||
assert clz is None
|
||||
assert meta is None
|
||||
|
||||
# check that an error is raised when trying to retrieve by an undefined metadata key
|
||||
with pytest.raises(Exception):
|
||||
_, _ = animal_type_registry.get(whatever="hello")
|
||||
|
||||
# test finding all entries with a given metadata value
|
||||
mammals = animal_type_registry.filter(classis='mammalia')
|
||||
assert set(cls for cls, meta in mammals) == {Cat, Dog}
|
||||
assert all(meta['classis'] == 'mammalia' for cls, meta in mammals)
|
||||
|
||||
insects = animal_type_registry.filter(classis='insecta')
|
||||
assert set(cls for cls, meta in insects) == {Cricket}
|
||||
|
||||
fantasy = animal_type_registry.filter(classis='fantasia')
|
||||
assert set(cls for cls, meta in fantasy) == set()
|
||||
|
||||
# check normal object instantiation still works with our decorator
|
||||
assert Cat().make_sound() == "meow"
|
||||
|
||||
|
||||
def test_registry_instances():
|
||||
animal_sound_registry = Registry({"animal": lambda s: s.animal})
|
||||
|
||||
@animal_sound_registry.new("dog", "woof")
|
||||
@animal_sound_registry.new("cricket", "chirp")
|
||||
class AnimalSound:
|
||||
def __init__(self, animal, sound):
|
||||
self.animal = animal
|
||||
self.sound = sound
|
||||
|
||||
def make_sound(self):
|
||||
return self.sound
|
||||
|
||||
@animal_sound_registry.new()
|
||||
class CatSound(AnimalSound):
|
||||
def __init__(self):
|
||||
super().__init__(animal="cat", sound=["meow", "meww", "miaou"])
|
||||
self.i = 0
|
||||
|
||||
def make_sound(self):
|
||||
self.i += 1
|
||||
return self.sound[self.i % len(self.sound)]
|
||||
|
||||
# test registry
|
||||
assert animal_sound_registry.get(animal='dog')[0].make_sound() == "woof"
|
||||
assert animal_sound_registry.get(animal='dog')[0].make_sound() == "woof"
|
||||
assert animal_sound_registry.get(animal='cricket')[0].make_sound() == "chirp"
|
||||
assert animal_sound_registry.get(animal='cat')[0].make_sound() == "meww"
|
||||
assert animal_sound_registry.get(animal='cat')[0].make_sound() == "miaou"
|
||||
assert animal_sound_registry.get(animal='cat')[0].make_sound() == "meow"
|
||||
|
||||
# check normal object instantiation still works with our decorator
|
||||
assert AnimalSound("test", "test").make_sound() == "test"
|
||||
|
||||
|
||||
def test_registry_prevent_duplicates():
|
||||
my_registry = Registry({"animal": lambda s: s.animal})
|
||||
|
||||
class AnimalSound:
|
||||
def __init__(self, animal, sound):
|
||||
self.animal = animal
|
||||
self.sound = sound
|
||||
|
||||
cat = AnimalSound("cat", "meow")
|
||||
my_registry.register(cat)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
my_registry.register(cat)
|
||||
|
||||
|
||||
def test_logentrytype_registry():
|
||||
reg = LogEntryTypeRegistry()
|
||||
|
||||
with mock.patch('pretix.base.signals.get_defining_app') as mock_get_defining_app:
|
||||
mock_get_defining_app.return_value = 'my_plugin'
|
||||
|
||||
@reg.new("foo.mytype")
|
||||
class MyType(LogEntryType):
|
||||
pass
|
||||
|
||||
@reg.new("foo.myothertype")
|
||||
class MyOtherType(LogEntryType):
|
||||
pass
|
||||
|
||||
typ, meta = reg.get(action_type="foo.mytype")
|
||||
assert isinstance(typ, MyType)
|
||||
assert meta['action_type'] == "foo.mytype"
|
||||
assert meta['plugin'] == 'my_plugin'
|
||||
|
||||
typ, meta = reg.get(action_type="foo.myothertype")
|
||||
assert isinstance(typ, MyOtherType)
|
||||
assert meta['action_type'] == "foo.myothertype"
|
||||
assert meta['plugin'] is None
|
||||
|
||||
by_my_plugin = reg.filter(plugin='my_plugin')
|
||||
assert set(type(typ) for typ, meta in by_my_plugin) == {MyType}
|
||||
|
||||
|
||||
def test_logentrytype_registry_validation():
|
||||
reg = LogEntryTypeRegistry()
|
||||
|
||||
with pytest.raises(TypeError, match='Must not register base classes, only derived ones'):
|
||||
reg.register(LogEntryType("foo.mytype"))
|
||||
|
||||
with pytest.raises(TypeError, match='Must not register base classes, only derived ones'):
|
||||
reg.new_from_dict({"foo.mytype": "My Log Entry"})(ItemLogEntryType)
|
||||
|
||||
with pytest.raises(TypeError, match='Entries must be derived from LogEntryType'):
|
||||
@reg.new()
|
||||
class MyType:
|
||||
pass
|
||||
Reference in New Issue
Block a user