Compare commits

..
21 changed files with 57 additions and 337 deletions
+1 -2
View File
@@ -11,8 +11,7 @@ Core
----
.. automodule:: pretix.base.signals
:members: periodic_task, event_live_issues, event_copy_data, email_filter,
register_notification_types, event_notification_sent, organizer_notification_sent,
:members: periodic_task, event_live_issues, event_copy_data, email_filter, register_notification_types, notification,
item_copy_data, register_sales_channel_types, register_global_settings, quota_availability, global_email_filter,
register_ticket_secret_generators, gift_card_transaction_display,
register_text_placeholders, register_mail_placeholders, device_info_updated,
+1 -1
View File
@@ -1,6 +1,6 @@
sphinx==9.1.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2
+1 -1
View File
@@ -1,7 +1,7 @@
-e ../
sphinx==9.1.*
sphinx-rtd-theme~=3.1.0
sphinxcontrib-httpdomain~=2.1.0
sphinxcontrib-httpdomain~=2.0.0
sphinxcontrib-images~=1.0.1
sphinxcontrib-jquery~=4.1
sphinxcontrib-spelling~=8.0.2
+4 -4
View File
@@ -34,7 +34,7 @@ dependencies = [
"celery==5.6.*",
"chardet==5.2.*",
"cryptography>=50.0.2",
"css-inline==0.22.*",
"css-inline==0.21.*",
"defusedcsv>=3.0.0",
"dnspython==2.*",
"Django[argon2]==5.2.*,>=5.2.17",
@@ -83,7 +83,7 @@ dependencies = [
"psycopg2-binary",
"pycountry",
"pycparser==3.0",
"pycryptodome==3.24.*",
"pycryptodome==3.23.*",
"pypdf==6.19.*",
"python-bidi==0.6.*", # Support for Arabic in reportlab
"python-dateutil==2.9.*",
@@ -112,7 +112,7 @@ dev = [
"aiohttp==3.14.*",
"coverage",
"coveralls",
"fakeredis==2.39.*",
"fakeredis==2.38.*",
"flake8==7.4.*",
"freezegun",
"isort==9.0.*",
@@ -122,7 +122,7 @@ dev = [
"pytest-cache",
"pytest-cov",
"pytest-django==4.*",
"pytest-mock==3.16.*",
"pytest-mock==3.15.*",
"pytest-sugar",
"pytest-xdist==3.8.*",
"pytest-playwright",
+1 -1
View File
@@ -1683,7 +1683,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
@action(detail=True, methods=['POST'])
def confirm(self, request, **kwargs):
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
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.validators import MinLengthValidator, RegexValidator
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.utils.crypto import get_random_string
from django.utils.functional import cached_property
@@ -312,25 +312,6 @@ class Organizer(LoggedModel):
)
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():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)
+11 -34
View File
@@ -35,13 +35,12 @@
import logging
from collections import OrderedDict, namedtuple
from itertools import groupby
from typing import Optional, Union
from django.dispatch import receiver
from django.utils.formats import date_format
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.templatetags.money import money_filter
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:
* one ``event`` reference (can be ``Ǹone``)
* one ``organizer`` reference
* one ``event`` reference
* 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 ``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.
"""
def __init__(self, event: Optional[Event], title: str, detail: Optional[str]=None, url: Optional[str]=None, organizer: Optional[Organizer]=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"
def __init__(self, event: Event, title: str, detail: str=None, url: str=None):
self.title = title
self.organizer = organizer or event.organizer
self.event = event
self.detail = detail
self.url = url
@@ -97,16 +91,8 @@ class Notification:
class NotificationType:
def __init__(self, event_or_organizer: Union[Event, Organizer] = None):
if isinstance(event_or_organizer, 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 __init__(self, event: Event = None):
self.event = event
def __repr__(self):
return '<NotificationType: {}>'.format(self.action_type)
@@ -135,39 +121,31 @@ class NotificationType:
"""
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:
"""
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.
"""
return Notification(
event=logentry.event,
title=logentry.display(),
organizer=logentry.organizer,
logentry.event,
logentry.display()
)
def get_all_notification_types(event_or_organizer=None):
def get_all_notification_types(event=None):
global _ALL_TYPES
if event_or_organizer is None and _ALL_TYPES:
if event is None and _ALL_TYPES:
return _ALL_TYPES
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)):
for r in ret:
types[r.action_type] = r
else:
types[ret.action_type] = ret
if event_or_organizer is None:
if event is None:
_ALL_TYPES = types
return types
@@ -203,7 +181,6 @@ class ParametrizedOrderNotificationType(NotificationType):
n = Notification(
event=logentry.event,
organizer=logentry.organizer,
title=self._title.format(order=order, event=logentry.event),
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.services.mail import mail_send_task
from pretix.base.services.tasks import ProfiledTask, TransactionAwareTask
from pretix.base.signals import (
event_notification_sent, organizer_notification_sent,
)
from pretix.base.signals import notification
from pretix.celery_app import app
from pretix.helpers.celery import get_task_priority
from pretix.helpers.urls import mainreverse_absolute
@@ -49,47 +47,40 @@ def notify(logentry_ids: list):
logentry_ids = [logentry_ids]
qs = LogEntry.all.select_related(
'event', 'event__organizer', 'organizer'
'event', 'event__organizer'
).order_by(
'action_type', 'event_id', 'organizer_id',
'action_type', 'event_id',
).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:
if not logentry.event and not logentry.organizer:
break # Ignore, we only have event- or organizer-related notifications right now
if not logentry.event:
break # Ignore, we only have event-related notifications right now
notification_type = logentry.notification_type
if not notification_type:
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
_organizer = logentry.organizer
_at = logentry.action_type
event_or_organizer = logentry.event or logentry.organizer
# 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
).filter(notifications_send=True, is_active=True)
if logentry.user:
users = users.exclude(pk=logentry.user.pk)
# Get all notification settings, both specific to this event as well as global
if logentry.event:
notify_specific = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
else:
notify_specific = {}
notify_specific = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
event=logentry.event,
action_type=notification_type.action_type,
user__pk__in=users.values_list('pk', flat=True)
)
}
notify_global = {
(ns.user, ns.method): ns.enabled
for ns in NotificationSetting.objects.filter(
@@ -115,10 +106,7 @@ def notify(logentry_ids: list):
priority=get_task_priority("notifications", logentry.organizer_id),
)
if logentry.event:
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)
notification.send(logentry.event, logentry_id=logentry.id, notification_type=notification_type.action_type)
@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)
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(
guid=guid,
user=user,
to=[user.email],
subject='[{}] {}: {}'.format(
settings.PRETIX_INSTANCE_NAME,
prefix,
notification.event.settings.mail_prefix or notification.event.slug.upper(),
notification.title
),
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={
'fee': fee.pk,
'old_price': fee.value,
'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,
'new_price': op.value.gross
})
fee.value = op.value.gross
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={
'position': position.pk,
'positionid': position.positionid,
'addon_to': position.addon_to_id,
'old_price': position.price,
'new_price': op.price.gross,
'old_tax_rate': position.tax_rate,
'new_tax_rate': op.price.rate,
'old_tax_value': position.tax_value,
'new_tax_value': op.price.tax,
'addon_to': position.addon_to_id,
'new_price': op.price.gross
})
position.price = op.price.gross
position.price_includes_rounding_correction = Decimal("0.00")
@@ -2509,43 +2501,25 @@ class OrderChangeManager:
elif isinstance(op, self.TaxRuleOperation):
if isinstance(op.position, OrderPosition):
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,
'positionid': position.positionid,
'addon_to': position.addon_to_id,
'old_taxrule': position.tax_rule.pk if position.tax_rule else None,
'new_taxrule': op.tax_rule.pk,
'old_price': position.price,
'old_tax_rate': position.tax_rate,
'old_tax_value': position.tax_value,
}
'new_taxrule': op.tax_rule.pk
})
position._calculate_tax(op.tax_rule)
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):
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_type': fee.fee_type,
'old_taxrule': fee.tax_rule.pk if fee.tax_rule else None,
'new_taxrule': op.tax_rule.pk,
'old_price': fee.value,
'old_tax_rate': fee.tax_rate,
'old_tax_value': fee.tax_value,
}
'new_taxrule': op.tax_rule.pk
})
fee._calculate_tax(op.tax_rule)
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):
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={
+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.
"""
register_notification_types = GlobalSignal()
register_notification_types = EventPluginSignal()
"""
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
instances.
When called for actually sending notifications, ``sender`` will be the event or organizer,
depending on context.
As with all event-plugin signals, the ``sender`` keyword argument will contain the event,
however for this signal, the ``sender`` **may also be None** to allow creating the general
notification settings!
"""
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.
"""
event_notification_sent = EventPluginSignal()
notification = EventPluginSignal()
"""
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.
"""
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()
"""
This signal is sent out to get all known sales channels types. Receivers should return an
@@ -1,27 +1,11 @@
{% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %}
{% load eventurl %}
{% load bootstrap3 %}
{% load static %}
{% block inner %}
<h1>
{% blocktrans with name=request.organizer.name %}Organizer: {{ name }}{% endblocktrans %}
</h1>
<div class="helper-space-below">
{% trans "Public profile" %}:
<span id="organizer_url" class="text-muted">{% abseventurl request.organizer "presale:organizer.index" %}</span>
<button type="button" class="btn btn-default btn-xs btn-clipboard js-only" data-clipboard-target="#organizer_url">
<i class="fa fa-clipboard" aria-hidden="true"></i>
<span class="sr-only">{% trans "Copy to clipboard" %}</span>
</button>
<div class="btn-group helper-display-inline-block">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" title="{% trans "Create QR code" %}" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-qrcode" aria-hidden="true"></i>
</button>
{% include "pretixcontrol/organizers/fragment_qr_dropdown.html" with url=0 %}
</div>
<div class="clearfix"></div>
</div>
{% if events|length == 0 and not filter_form.filtered %}
{% if "organizer.events:create" in request.orgapermset %}
<p>
@@ -1,27 +0,0 @@
{% load i18n %}
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a href="{% url "control:organizer.qrcode" organizer=request.organizer.slug filetype="png" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="PNG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:organizer.qrcode" organizer=request.organizer.slug filetype="svg" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="SVG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:organizer.qrcode" organizer=request.organizer.slug filetype="jpeg" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="JPG" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
<li>
<a href="{% url "control:organizer.qrcode" organizer=request.organizer.slug filetype="gif" %}{% if url %}?url={{ url|urlencode }}{% endif %}"
target="_blank" download>
{% blocktrans with filetype="GIF" %}Download QR code as {{ filetype }} image{% endblocktrans %}
</a>
</li>
</ul>
-1
View File
@@ -263,7 +263,6 @@ urlpatterns = [
name='organizer.export.scheduled.delete'),
re_path(r'^organizer/(?P<organizer>[^/]+)/ticket_select2$', typeahead.ticket_select2, name='organizer.ticket_select2'),
re_path(r'^organizer/(?P<organizer>[^/]+)/datasync/failedjobs/$', datasync.OrganizerFailedSyncJobsView.as_view(), name='organizer.datasync.failedjobs'),
re_path(r'^organizer/(?P<organizer>[^/]+)/qrcode.(?P<filetype>(png|jpeg|gif|svg))$', organizer.OrganizerQRCode.as_view(), name='organizer.qrcode'),
re_path(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
re_path(r'^events/$', main.EventList.as_view(), name='events'),
re_path(r'^events/add$', main.EventWizard.as_view(), name='events.add'),
-41
View File
@@ -40,14 +40,11 @@ from collections import Counter
from datetime import time, timedelta
from decimal import Decimal
from hashlib import sha1
from io import BytesIO
from itertools import groupby
from json import JSONDecodeError
import bleach
import dateutil
import qrcode
import qrcode.image.svg
from django import forms
from django.conf import settings
from django.contrib import messages
@@ -3761,41 +3758,3 @@ def reorder_channels(request, organizer):
)
return HttpResponse()
class OrganizerQRCode(OrganizerPermissionRequiredMixin, View):
permission = None
def get(self, request, *args, filetype, **kwargs):
url = eventreverse_absolute(request.organizer, 'presale:organizer.index')
if "url" in request.GET:
if request.GET["url"].startswith(url):
url = request.GET["url"]
else:
raise PermissionDenied("Untrusted URL")
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=10,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
if filetype == 'svg':
factory = qrcode.image.svg.SvgPathImage
img = qr.make_image(image_factory=factory)
r = HttpResponse(img.to_string(), content_type='image/svg+xml')
r['Content-Disposition'] = f'inline; filename="qrcode-{request.organizer.slug}.{filetype}"'
return r
elif filetype in ('jpeg', 'png', 'gif'):
img = qr.make_image(fill_color="black", back_color="white")
byte_io = BytesIO()
img.save(byte_io, filetype.upper())
byte_io.seek(0)
r = HttpResponse(byte_io.read(), content_type='image/' + filetype)
r['Content-Disposition'] = f'inline; filename="qrcode-{request.organizer.slug}.{filetype}"'
return r
-1
View File
@@ -761,7 +761,6 @@ class UserNotificationsEditView(TemplateView):
{k: a.get(t) for k, a in self.global_set.items()},
)
for t, tv in self.types.items()
if tv.is_event_level or not self.event
]
ctx['event'] = self.event
if self.event:
-3
View File
@@ -40,6 +40,3 @@ class SendMailApp(AppConfig):
def ready(self):
from . import signals # 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
)
for m_id in mails.filter(
event__plugins__contains="pretix.plugins.sendmail",
state__in=(ScheduledMail.STATE_SCHEDULED, ScheduledMail.STATE_FAILED),
rule__enabled=True,
event__live=True,
+2 -27
View File
@@ -64,7 +64,7 @@ def order(event):
@pytest.fixture
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
@@ -108,17 +108,6 @@ def test_notification_trigger_global_wildcard(event, order, user, django_capture
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
def test_notification_enabled_global_ignored_specific(event, order, user, django_capture_on_commit_callbacks):
djmail.outbox = []
@@ -145,7 +134,7 @@ def test_notification_ignore_same_user(event, order, user, django_capture_on_com
@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 = []
team.all_event_permissions = False
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', {})
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
-6
View File
@@ -219,14 +219,8 @@ def test_specific_organizer_permission(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})
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)
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)
assert set(event.organizer.get_users_with_permission('organizer.events:create')) == {user}
orga2 = Organizer.objects.create(slug='d2', name='d2')
team3 = Team.objects.create(organizer=orga2, limit_organizer_permissions={"organizer.teams:write": True})
team3.members.add(user)
+3 -31
View File
@@ -26,12 +26,11 @@ from pretix.base.exporter import BaseExporter
from pretix.base.invoicing.transmission import (
TransmissionProvider, transmission_providers,
)
from pretix.base.models import Invoice, LogEntry
from pretix.base.notifications import Notification, NotificationType
from pretix.base.models import Invoice
from pretix.base.signals import (
register_data_exporters, register_multievent_data_exporters,
register_notification_types, register_payment_providers,
register_sales_channel_types, register_ticket_outputs,
register_payment_providers, register_sales_channel_types,
register_ticket_outputs,
)
from pretix.presale.signals import html_head
@@ -145,30 +144,3 @@ class TestPeppolTransmissionProvider(TransmissionProvider):
def transmit(self, invoice):
invoice.transmission_status = Invoice.TRANSMISSION_STATUS_COMPLETED
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)
)