Compare commits

..
17 changed files with 57 additions and 252 deletions
+1 -2
View File
@@ -11,8 +11,7 @@ Core
---- ----
.. automodule:: pretix.base.signals .. automodule:: pretix.base.signals
:members: periodic_task, event_live_issues, event_copy_data, email_filter, :members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, notification,
register_notification_types, event_notification_sent, organizer_notification_sent,
item_copy_data, register_sales_channel_types, register_global_settings, quota_availability, global_email_filter, item_copy_data, register_sales_channel_types, register_global_settings, quota_availability, global_email_filter,
register_ticket_secret_generators, gift_card_transaction_display, register_ticket_secret_generators, gift_card_transaction_display,
register_text_placeholders, register_mail_placeholders, device_info_updated, register_text_placeholders, register_mail_placeholders, device_info_updated,
+1 -1
View File
@@ -1,6 +1,6 @@
sphinx==9.1.* sphinx==9.1.*
sphinx-rtd-theme~=3.1.0 sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.1.0 sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-images~=1.0.1 sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1 sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-spelling~=8.0.2
+1 -1
View File
@@ -1,7 +1,7 @@
-e ../ -e ../
sphinx==9.1.* sphinx==9.1.*
sphinx-rtd-theme~=3.1.0 sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.1.0 sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-images~=1.0.1 sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1 sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2 sphinxcontrib-spelling~=8.0.2
+4 -4
View File
@@ -34,7 +34,7 @@ dependencies = [
"celery==5.6.*", "celery==5.6.*",
"chardet==5.2.*", "chardet==5.2.*",
"cryptography>=50.0.2", "cryptography>=50.0.2",
"css-inline==0.22.*", "css-inline==0.21.*",
"defusedcsv>=3.0.0", "defusedcsv>=3.0.0",
"dnspython==2.*", "dnspython==2.*",
"Django[argon2]==5.2.*,>=5.2.17", "Django[argon2]==5.2.*,>=5.2.17",
@@ -83,7 +83,7 @@ dependencies = [
"psycopg2-binary", "psycopg2-binary",
"pycountry", "pycountry",
"pycparser==3.0", "pycparser==3.0",
"pycryptodome==3.24.*", "pycryptodome==3.23.*",
"pypdf==6.19.*", "pypdf==6.19.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab "python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*", "python-dateutil==2.9.*",
@@ -112,7 +112,7 @@ dev = [
"aiohttp==3.14.*", "aiohttp==3.14.*",
"coverage", "coverage",
"coveralls", "coveralls",
"fakeredis==2.39.*", "fakeredis==2.38.*",
"flake8==7.4.*", "flake8==7.4.*",
"freezegun", "freezegun",
"isort==9.0.*", "isort==9.0.*",
@@ -122,7 +122,7 @@ dev = [
"pytest-cache", "pytest-cache",
"pytest-cov", "pytest-cov",
"pytest-django==4.*", "pytest-django==4.*",
"pytest-mock==3.16.*", "pytest-mock==3.15.*",
"pytest-sugar", "pytest-sugar",
"pytest-xdist==3.8.*", "pytest-xdist==3.8.*",
"pytest-playwright", "pytest-playwright",
+1 -1
View File
@@ -1683,7 +1683,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
@action(detail=True, methods=['POST']) @action(detail=True, methods=['POST'])
def confirm(self, request, **kwargs): def confirm(self, request, **kwargs):
payment = self.get_object() payment = self.get_object()
force = request.data.get('force', False) if request.data else False force = request.data.get('force', False)
send_mail = request.data.get('send_email', True) if request.data else True send_mail = request.data.get('send_email', True) if request.data else True
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED): if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
+1 -20
View File
@@ -42,7 +42,7 @@ from django.conf import settings
from django.core.mail import get_connection from django.core.mail import get_connection
from django.core.validators import MinLengthValidator, RegexValidator from django.core.validators import MinLengthValidator, RegexValidator
from django.db import models from django.db import models
from django.db.models import Exists, OuterRef, Q from django.db.models import Q
from django.urls import reverse from django.urls import reverse
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.functional import cached_property from django.utils.functional import cached_property
@@ -312,25 +312,6 @@ class Organizer(LoggedModel):
) )
i += 1 i += 1
def get_users_with_permission(self, permission):
"""
Returns a queryset of users who have a specific permission to this organizer.
:return: Iterable of User
"""
from .auth import User
if permission:
qs = Team.objects.with_organizer_permission(permission)
else:
qs = Team.objects.all()
team_with_perm = qs.filter(
members__pk=OuterRef('pk'),
organizer=self,
)
return User.objects.filter(Exists(team_with_perm))
def generate_invite_token(): def generate_invite_token():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits) return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
+11 -34
View File
@@ -35,13 +35,12 @@
import logging import logging
from collections import OrderedDict, namedtuple from collections import OrderedDict, namedtuple
from itertools import groupby from itertools import groupby
from typing import Optional, Union
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import Event, LogEntry, Organizer from pretix.base.models import Event, LogEntry
from pretix.base.signals import register_notification_types from pretix.base.signals import register_notification_types
from pretix.base.templatetags.money import money_filter from pretix.base.templatetags.money import money_filter
from pretix.helpers.urls import mainreverse_absolute from pretix.helpers.urls import mainreverse_absolute
@@ -58,8 +57,7 @@ class Notification:
""" """
Represents a notification that is sent/shown to a user. A notification consists of: Represents a notification that is sent/shown to a user. A notification consists of:
* one ``event`` reference (can be ``Ǹone``) * one ``event`` reference
* one ``organizer`` reference
* one ``title`` text that is shown e.g. in the email subject or in a headline * one ``title`` text that is shown e.g. in the email subject or in a headline
* optionally one ``detail`` text that may or may not be shown depending on the notification method * optionally one ``detail`` text that may or may not be shown depending on the notification method
* optionally one ``url`` that should be absolute and point to the context of an notification (e.g. an order) * optionally one ``url`` that should be absolute and point to the context of an notification (e.g. an order)
@@ -69,12 +67,8 @@ class Notification:
each consisting of a button label and an absolute URL to point to. each consisting of a button label and an absolute URL to point to.
""" """
def __init__(self, event: Optional[Event], title: str, detail: Optional[str]=None, url: Optional[str]=None, organizer: Optional[Organizer]=None): def __init__(self, event: Event, title: str, detail: str=None, url: str=None):
assert event or organizer, "You need to pass either event or organizer"
if event and organizer:
assert event.organizer_id == organizer.id, "Organizer and event mismatch"
self.title = title self.title = title
self.organizer = organizer or event.organizer
self.event = event self.event = event
self.detail = detail self.detail = detail
self.url = url self.url = url
@@ -97,16 +91,8 @@ class Notification:
class NotificationType: class NotificationType:
def __init__(self, event_or_organizer: Union[Event, Organizer] = None): def __init__(self, event: Event = None):
if isinstance(event_or_organizer, Event): self.event = event
self.event = event_or_organizer
self.organizer = event_or_organizer.organizer
elif isinstance(event_or_organizer, Organizer):
self.event = None
self.organizer = event_or_organizer
else:
self.event = None
self.organizer = None
def __repr__(self): def __repr__(self):
return '<NotificationType: {}>'.format(self.action_type) return '<NotificationType: {}>'.format(self.action_type)
@@ -135,39 +121,31 @@ class NotificationType:
""" """
raise NotImplementedError() # NOQA raise NotImplementedError() # NOQA
@property
def is_event_level(self) -> bool:
"""
Return `True` if this notification type can be configured per event (the default).
"""
return True
def build_notification(self, logentry: LogEntry) -> Notification: def build_notification(self, logentry: LogEntry) -> Notification:
""" """
This is the main function that you should override. It is supposed to turn a log entry This is the main function that you should override. It is supposed to turn a log entry
object into a notification object that can then be rendered e.g. into an email. object into a notification object that can then be rendered e.g. into an email.
""" """
return Notification( return Notification(
event=logentry.event, logentry.event,
title=logentry.display(), logentry.display()
organizer=logentry.organizer,
) )
def get_all_notification_types(event_or_organizer=None): def get_all_notification_types(event=None):
global _ALL_TYPES global _ALL_TYPES
if event_or_organizer is None and _ALL_TYPES: if event is None and _ALL_TYPES:
return _ALL_TYPES return _ALL_TYPES
types = OrderedDict() types = OrderedDict()
for recv, ret in register_notification_types.send(event_or_organizer): for recv, ret in register_notification_types.send(event):
if isinstance(ret, (list, tuple)): if isinstance(ret, (list, tuple)):
for r in ret: for r in ret:
types[r.action_type] = r types[r.action_type] = r
else: else:
types[ret.action_type] = ret types[ret.action_type] = ret
if event_or_organizer is None: if event is None:
_ALL_TYPES = types _ALL_TYPES = types
return types return types
@@ -203,7 +181,6 @@ class ParametrizedOrderNotificationType(NotificationType):
n = Notification( n = Notification(
event=logentry.event, event=logentry.event,
organizer=logentry.organizer,
title=self._title.format(order=order, event=logentry.event), title=self._title.format(order=order, event=logentry.event),
url=order_url url=order_url
) )
+18 -37
View File
@@ -34,9 +34,7 @@ from pretix.base.models import (
from pretix.base.notifications import Notification, get_all_notification_types from pretix.base.notifications import Notification, get_all_notification_types
from pretix.base.services.mail import mail_send_task from pretix.base.services.mail import mail_send_task
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import ( from pretix.base.signals import notification
event_notification_sent, organizer_notification_sent,
)
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers.celery import get_task_priority from pretix.helpers.celery import get_task_priority
from pretix.helpers.urls import mainreverse_absolute from pretix.helpers.urls import mainreverse_absolute
@@ -49,47 +47,40 @@ def notify(logentry_ids: list):
logentry_ids = [logentry_ids] logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related( qs = LogEntry.all.select_related(
'event', 'event__organizer', 'organizer' 'event', 'event__organizer'
).order_by( ).order_by(
'action_type', 'event_id', 'organizer_id', 'action_type', 'event_id',
).filter(id__in=logentry_ids) ).filter(id__in=logentry_ids)
_event, _organizer, _at, notify_specific, notify_global = None, None, None, None, None _event, _at, notify_specific, notify_global = None, None, None, None
for logentry in qs: for logentry in qs:
if not logentry.event and not logentry.organizer: if not logentry.event:
break # Ignore, we only have event- or organizer-related notifications right now break # Ignore, we only have event-related notifications right now
notification_type = logentry.notification_type notification_type = logentry.notification_type
if not notification_type: if not notification_type:
break # No suitable plugin break # No suitable plugin
if _event != logentry.event or _organizer != logentry.organizer or _at != logentry.action_type or notify_global is None: if _event != logentry.event or _at != logentry.action_type or notify_global is None:
_event = logentry.event _event = logentry.event
_organizer = logentry.organizer
_at = logentry.action_type _at = logentry.action_type
event_or_organizer = logentry.event or logentry.organizer
# All users that have the permission to get the notification # All users that have the permission to get the notification
users = event_or_organizer.get_users_with_permission( users = logentry.event.get_users_with_permission(
notification_type.required_permission notification_type.required_permission
).filter(notifications_send=True, is_active=True) ).filter(notifications_send=True, is_active=True)
if logentry.user: if logentry.user:
users = users.exclude(pk=logentry.user.pk) users = users.exclude(pk=logentry.user.pk)
# Get all notification settings, both specific to this event as well as global # Get all notification settings, both specific to this event as well as global
if logentry.event: notify_specific = {
notify_specific = { (ns.user, ns.method): ns.enabled
(ns.user, ns.method): ns.enabled for ns in NotificationSetting.objects.filter(
for ns in NotificationSetting.objects.filter( event=logentry.event,
event=logentry.event, action_type=notification_type.action_type,
action_type=notification_type.action_type, user__pk__in=users.values_list('pk', flat=True)
user__pk__in=users.values_list('pk', flat=True) )
) }
}
else:
notify_specific = {}
notify_global = { notify_global = {
(ns.user, ns.method): ns.enabled (ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter( for ns in NotificationSetting.objects.filter(
@@ -115,10 +106,7 @@ def notify(logentry_ids: list):
priority=get_task_priority("notifications", logentry.organizer_id), priority=get_task_priority("notifications", logentry.organizer_id),
) )
if logentry.event: notification.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)
event_notification_sent.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)
elif logentry.organizer:
organizer_notification_sent.send(logentry.organizer, logentry_id=logentry.id, notification_type=notification_type.action_type)
@app.task(base=ProfiledTask, acks_late=True, max_retries=9, default_retry_delay=900) @app.task(base=ProfiledTask, acks_late=True, max_retries=9, default_retry_delay=900)
@@ -170,20 +158,13 @@ def send_notification_mail(notification: Notification, user: User):
body_plain = tpl_plain.render(ctx) body_plain = tpl_plain.render(ctx)
guid = uuid.uuid4() guid = uuid.uuid4()
settings_holder = notification.event or notification.organizer
prefix = settings_holder.settings.mail_prefix
if not prefix:
if notification.event:
prefix = notification.event.slug.upper()
elif notification.organizer:
prefix = notification.organizer.name
m = OutgoingMail.objects.create( m = OutgoingMail.objects.create(
guid=guid, guid=guid,
user=user, user=user,
to=[user.email], to=[user.email],
subject='[{}] {}: {}'.format( subject='[{}] {}: {}'.format(
settings.PRETIX_INSTANCE_NAME, settings.PRETIX_INSTANCE_NAME,
prefix, notification.event.settings.mail_prefix or notification.event.slug.upper(),
notification.title notification.title
), ),
body_plain=body_plain, body_plain=body_plain,
+9 -35
View File
@@ -2474,11 +2474,7 @@ class OrderChangeManager:
self.order.log_action('pretix.event.order.changed.feevalue', user=self.user, auth=self.auth, data={ self.order.log_action('pretix.event.order.changed.feevalue', user=self.user, auth=self.auth, data={
'fee': fee.pk, 'fee': fee.pk,
'old_price': fee.value, 'old_price': fee.value,
'new_price': op.value.gross, 'new_price': op.value.gross
'old_tax_rate': fee.tax_rate,
'new_tax_rate': op.value.rate,
'old_tax_value': fee.tax_value,
'new_tax_value': op.value.tax,
}) })
fee.value = op.value.gross fee.value = op.value.gross
fee._calculate_tax() fee._calculate_tax()
@@ -2488,13 +2484,9 @@ class OrderChangeManager:
self.order.log_action('pretix.event.order.changed.price', user=self.user, auth=self.auth, data={ self.order.log_action('pretix.event.order.changed.price', user=self.user, auth=self.auth, data={
'position': position.pk, 'position': position.pk,
'positionid': position.positionid, 'positionid': position.positionid,
'addon_to': position.addon_to_id,
'old_price': position.price, 'old_price': position.price,
'new_price': op.price.gross, 'addon_to': position.addon_to_id,
'old_tax_rate': position.tax_rate, 'new_price': op.price.gross
'new_tax_rate': op.price.rate,
'old_tax_value': position.tax_value,
'new_tax_value': op.price.tax,
}) })
position.price = op.price.gross position.price = op.price.gross
position.price_includes_rounding_correction = Decimal("0.00") position.price_includes_rounding_correction = Decimal("0.00")
@@ -2509,43 +2501,25 @@ class OrderChangeManager:
elif isinstance(op, self.TaxRuleOperation): elif isinstance(op, self.TaxRuleOperation):
if isinstance(op.position, OrderPosition): if isinstance(op.position, OrderPosition):
position = position_cache.setdefault(op.position.pk, op.position) position = position_cache.setdefault(op.position.pk, op.position)
log = { self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data={
'position': position.pk, 'position': position.pk,
'positionid': position.positionid, 'positionid': position.positionid,
'addon_to': position.addon_to_id, 'addon_to': position.addon_to_id,
'old_taxrule': position.tax_rule.pk if position.tax_rule else None, 'old_taxrule': position.tax_rule.pk if position.tax_rule else None,
'new_taxrule': op.tax_rule.pk, 'new_taxrule': op.tax_rule.pk
'old_price': position.price, })
'old_tax_rate': position.tax_rate,
'old_tax_value': position.tax_value,
}
position._calculate_tax(op.tax_rule) position._calculate_tax(op.tax_rule)
position.save() position.save()
log.update({
'new_price': position.price,
'new_tax_rate': position.tax_rate,
'new_tax_value': position.tax_value,
})
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data=log)
elif isinstance(op.position, OrderFee): elif isinstance(op.position, OrderFee):
fee = fee_cache.setdefault(op.position.pk, op.position) fee = fee_cache.setdefault(op.position.pk, op.position)
log = { self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data={
'fee': fee.pk, 'fee': fee.pk,
'fee_type': fee.fee_type, 'fee_type': fee.fee_type,
'old_taxrule': fee.tax_rule.pk if fee.tax_rule else None, 'old_taxrule': fee.tax_rule.pk if fee.tax_rule else None,
'new_taxrule': op.tax_rule.pk, 'new_taxrule': op.tax_rule.pk
'old_price': fee.value, })
'old_tax_rate': fee.tax_rate,
'old_tax_value': fee.tax_value,
}
fee._calculate_tax(op.tax_rule) fee._calculate_tax(op.tax_rule)
fee.save() fee.save()
log.update({
'new_price': fee.value,
'new_tax_rate': fee.tax_rate,
'new_tax_value': fee.tax_value,
})
self.order.log_action('pretix.event.order.changed.tax_rule', user=self.user, auth=self.auth, data=log)
elif isinstance(op, self.CancelFeeOperation): elif isinstance(op, self.CancelFeeOperation):
fee = fee_cache.setdefault(op.fee.pk, op.fee) fee = fee_cache.setdefault(op.fee.pk, op.fee)
self.order.log_action('pretix.event.order.changed.cancelfee', user=self.user, auth=self.auth, data={ self.order.log_action('pretix.event.order.changed.cancelfee', user=self.user, auth=self.auth, data={
+5 -13
View File
@@ -605,14 +605,15 @@ subclass of pretix.base.ticketoutput.BaseTicketOutput
As with all event-plugin signals, the ``sender`` keyword argument will contain the event. As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
""" """
register_notification_types = GlobalSignal() register_notification_types = EventPluginSignal()
""" """
This signal is sent out to get all known notification types. Receivers should return an This signal is sent out to get all known notification types. Receivers should return an
instance of a subclass of pretix.base.notifications.NotificationType or a list of such instance of a subclass of pretix.base.notifications.NotificationType or a list of such
instances. instances.
When called for actually sending notifications, ``sender`` will be the event or organizer, As with all event-plugin signals, the ``sender`` keyword argument will contain the event,
depending on context. however for this signal, the ``sender`` **may also be None** to allow creating the general
notification settings!
""" """
register_event_permission_groups = GlobalSignal() register_event_permission_groups = GlobalSignal()
@@ -627,7 +628,7 @@ This signal is sent out to get all known permissions. Receivers should return an
instance of pretix.base.permissions.PermissionGroup or a list of such instances. instance of pretix.base.permissions.PermissionGroup or a list of such instances.
""" """
event_notification_sent = EventPluginSignal() notification = EventPluginSignal()
""" """
Arguments: ``logentry_id``, ``notification_type`` Arguments: ``logentry_id``, ``notification_type``
@@ -636,15 +637,6 @@ This signal is sent out when a notification is sent.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event. As with all event-plugin signals, the ``sender`` keyword argument will contain the event.
""" """
organizer_notification_sent = OrganizerPluginSignal()
"""
Arguments: ``logentry_id``, ``notification_type``
This signal is sent out when a notification is sent.
As with all organizer-plugin signals, the ``sender`` keyword argument will contain the organizer.
"""
register_sales_channel_types = GlobalSignal() register_sales_channel_types = GlobalSignal()
""" """
This signal is sent out to get all known sales channels types. Receivers should return an This signal is sent out to get all known sales channels types. Receivers should return an
-1
View File
@@ -761,7 +761,6 @@ class UserNotificationsEditView(TemplateView):
{k: a.get(t) for k, a in self.global_set.items()}, {k: a.get(t) for k, a in self.global_set.items()},
) )
for t, tv in self.types.items() for t, tv in self.types.items()
if tv.is_event_level or not self.event
] ]
ctx['event'] = self.event ctx['event'] = self.event
if self.event: if self.event:
-3
View File
@@ -40,6 +40,3 @@ class SendMailApp(AppConfig):
def ready(self): def ready(self):
from . import signals # NOQA from . import signals # NOQA
from . import tasks # NOQA from . import tasks # NOQA
def uninstalled(self, event):
event.sendmail_rules.update(enabled=False)
@@ -1,35 +0,0 @@
from django.db import migrations
from django.db.models import Exists, OuterRef
def activate_plugin(apps, schema_editor):
Event = apps.get_model("pretixbase", "Event")
ScheduledMail = apps.get_model("sendmail", "ScheduledMail")
Rule = apps.get_model("sendmail", "Rule")
events = (
Event.objects
.exclude(plugins__icontains="pretix.plugins.sendmail")
.filter(
Exists(
ScheduledMail.objects.filter(event=OuterRef('pk'), rule__enabled=True).exclude(
state__in=['completed', 'missed'])
)
)
)
for event in events:
plugins_active = event.plugins.split(',')
plugins_active.append('pretix.plugins.sendmail')
event.plugins = ','.join(plugins_active)
event.save(update_fields=['plugins'])
class Migration(migrations.Migration):
dependencies = [
("sendmail", "0012_remove_cross_event_scheduled_mails"),
]
operations = [
migrations.RunPython(activate_plugin),
]
-1
View File
@@ -181,7 +181,6 @@ def sendmail_run_rules(sender, **kwargs):
state=ScheduledMail.STATE_MISSED state=ScheduledMail.STATE_MISSED
) )
for m_id in mails.filter( for m_id in mails.filter(
event__plugins__contains="pretix.plugins.sendmail",
state__in=(ScheduledMail.STATE_SCHEDULED, ScheduledMail.STATE_FAILED), state__in=(ScheduledMail.STATE_SCHEDULED, ScheduledMail.STATE_FAILED),
rule__enabled=True, rule__enabled=True,
event__live=True, event__live=True,
+2 -27
View File
@@ -64,7 +64,7 @@ def order(event):
@pytest.fixture @pytest.fixture
def team(event): def team(event):
return event.organizer.teams.create(all_events=True, all_event_permissions=True, all_organizer_permissions=True) return event.organizer.teams.create(all_events=True, all_event_permissions=True)
@pytest.fixture @pytest.fixture
@@ -108,17 +108,6 @@ def test_notification_trigger_global_wildcard(event, order, user, django_capture
assert len(djmail.outbox) == 1 assert len(djmail.outbox) == 1
@pytest.mark.django_db
def test_notification_trigger_organizer(event, user, django_capture_on_commit_callbacks):
djmail.outbox = []
user.notification_settings.create(
method='mail', event=None, action_type='pretix.organizer.changed', enabled=True
)
with django_capture_on_commit_callbacks(execute=True):
event.organizer.log_action('pretix.organizer.changed', {})
assert len(djmail.outbox) == 1
@pytest.mark.django_db @pytest.mark.django_db
def test_notification_enabled_global_ignored_specific(event, order, user, django_capture_on_commit_callbacks): def test_notification_enabled_global_ignored_specific(event, order, user, django_capture_on_commit_callbacks):
djmail.outbox = [] djmail.outbox = []
@@ -145,7 +134,7 @@ def test_notification_ignore_same_user(event, order, user, django_capture_on_com
@pytest.mark.django_db @pytest.mark.django_db
def test_notification_ignore_insufficient_event_permissions(event, order, user, team, django_capture_on_commit_callbacks): def test_notification_ignore_insufficient_permissions(event, order, user, team, django_capture_on_commit_callbacks):
djmail.outbox = [] djmail.outbox = []
team.all_event_permissions = False team.all_event_permissions = False
team.limit_event_permissions = {"event.vouchers:read": True} team.limit_event_permissions = {"event.vouchers:read": True}
@@ -157,18 +146,4 @@ def test_notification_ignore_insufficient_event_permissions(event, order, user,
order.log_action('pretix.event.order.paid', {}) order.log_action('pretix.event.order.paid', {})
assert len(djmail.outbox) == 0 assert len(djmail.outbox) == 0
@pytest.mark.django_db
def test_notification_ignore_insufficient_organizer_permissions(event, team, user, django_capture_on_commit_callbacks):
djmail.outbox = []
team.all_organizer_permissions = False
team.limit_organizer_permissions = {"organizer.giftcards:read": True}
team.save()
user.notification_settings.create(
method='mail', event=None, action_type='pretix.organizer.changed', enabled=True
)
with django_capture_on_commit_callbacks(execute=True):
event.organizer.log_action('pretix.organizer.changed', {})
assert len(djmail.outbox) == 0
# TODO: Test email content # TODO: Test email content
-6
View File
@@ -219,14 +219,8 @@ def test_specific_organizer_permission(event, user):
def test_organizer_permissions_multiple_teams(event, user): def test_organizer_permissions_multiple_teams(event, user):
team1 = Team.objects.create(organizer=event.organizer, limit_organizer_permissions={"organizer.settings.general:write": True}) team1 = Team.objects.create(organizer=event.organizer, limit_organizer_permissions={"organizer.settings.general:write": True})
team2 = Team.objects.create(organizer=event.organizer, limit_organizer_permissions={"organizer.events:create": True}) team2 = Team.objects.create(organizer=event.organizer, limit_organizer_permissions={"organizer.events:create": True})
assert set(event.organizer.get_users_with_permission('organizer.settings.general:write')) == set()
assert set(event.organizer.get_users_with_permission(None)) == set()
team1.members.add(user) team1.members.add(user)
assert set(event.organizer.get_users_with_permission('organizer.settings.general:write')) == {user}
assert set(event.organizer.get_users_with_permission(None)) == {user}
assert set(event.organizer.get_users_with_permission('organizer.events:create')) == set()
team2.members.add(user) team2.members.add(user)
assert set(event.organizer.get_users_with_permission('organizer.events:create')) == {user}
orga2 = Organizer.objects.create(slug='d2', name='d2') orga2 = Organizer.objects.create(slug='d2', name='d2')
team3 = Team.objects.create(organizer=orga2, limit_organizer_permissions={"organizer.teams:write": True}) team3 = Team.objects.create(organizer=orga2, limit_organizer_permissions={"organizer.teams:write": True})
team3.members.add(user) team3.members.add(user)
+3 -31
View File
@@ -26,12 +26,11 @@ from pretix.base.exporter import BaseExporter
from pretix.base.invoicing.transmission import ( from pretix.base.invoicing.transmission import (
TransmissionProvider, transmission_providers, TransmissionProvider, transmission_providers,
) )
from pretix.base.models import Invoice, LogEntry from pretix.base.models import Invoice
from pretix.base.notifications import Notification, NotificationType
from pretix.base.signals import ( from pretix.base.signals import (
register_data_exporters, register_multievent_data_exporters, register_data_exporters, register_multievent_data_exporters,
register_notification_types, register_payment_providers, register_payment_providers, register_sales_channel_types,
register_sales_channel_types, register_ticket_outputs, register_ticket_outputs,
) )
from pretix.presale.signals import html_head from pretix.presale.signals import html_head
@@ -145,30 +144,3 @@ class TestPeppolTransmissionProvider(TransmissionProvider):
def transmit(self, invoice): def transmit(self, invoice):
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED invoice.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
invoice.save() invoice.save()
class OrgLevelTestNotificationType(NotificationType):
required_permission = "organizer.settings.general:write" # does not make much sense, just for tests
is_event_level = False
action_type = "pretix.organizer.changed"
verbose_name = "Organizer changed"
def __init__(self, organizer):
super().__init__(organizer)
def build_notification(self, logentry: LogEntry):
n = Notification(
event=None,
organizer=logentry.organizer,
title="Organizer Changed",
url="https://example.com"
)
n.add_attribute('Organizer', logentry.organizer.name)
return n
@receiver(register_notification_types, dispatch_uid="testdummy_register_notification_types")
def testdummy_register_notification_types(sender, **kwargs):
return (
OrgLevelTestNotificationType(sender)
)