mirror of
https://github.com/pretix/pretix.git
synced 2026-04-08 20:52:28 +00:00
Compare commits
12 Commits
exporter-a
...
fix-csv-im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7388aa0a0 | ||
|
|
166aa33b1b | ||
|
|
c037fd865b | ||
|
|
12171e0665 | ||
|
|
444963e952 | ||
|
|
a57810cf41 | ||
|
|
2e2e57d231 | ||
|
|
fc7e8ea67a | ||
|
|
23d1673403 | ||
|
|
92d1830f3b | ||
|
|
d411c36414 | ||
|
|
84e12fea32 |
@@ -1,4 +1,4 @@
|
||||
FROM python:3.11-bookworm
|
||||
FROM python:3.13-trixie
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -47,6 +47,7 @@ from django.utils.formats import localize
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.models.auth import PermissionHolder
|
||||
from pretix.helpers.safe_openpyxl import ( # NOQA: backwards compatibility for plugins using excel_safe
|
||||
SafeWorkbook, remove_invalid_excel_chars as excel_safe,
|
||||
)
|
||||
@@ -59,11 +60,20 @@ class BaseExporter:
|
||||
This is the base class for all data exporters
|
||||
"""
|
||||
|
||||
def __init__(self, event, organizer, progress_callback=lambda v: None):
|
||||
def __init__(self, event, organizer, permission_holder: PermissionHolder=None, progress_callback=lambda v: None):
|
||||
"""
|
||||
:param event: Event context, can also be a queryset of events for multi-event exports
|
||||
:param organizer: Organizer context
|
||||
:param user: The user who triggered the export (or None).
|
||||
:param token: The API token that triggered the export (or None).
|
||||
:param device: The device that triggered the export (or None)
|
||||
:param progress_callback: Callback function with progress
|
||||
"""
|
||||
self.event = event
|
||||
self.organizer = organizer
|
||||
self.progress_callback = progress_callback
|
||||
self.is_multievent = isinstance(event, QuerySet)
|
||||
self.permission_holder = permission_holder
|
||||
if isinstance(event, QuerySet):
|
||||
self.events = event
|
||||
self.event = None
|
||||
@@ -180,7 +190,7 @@ class BaseExporter:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def get_required_event_permission(cls) -> str:
|
||||
def get_required_event_permission(cls) -> Optional[str]:
|
||||
"""
|
||||
The permission level required to use this exporter for events. For multi-event-exports, this will be used
|
||||
to limit the selection of events. Will be ignored if the ``OrganizerLevelExportMixin`` mixin is used.
|
||||
@@ -195,7 +205,7 @@ class OrganizerLevelExportMixin:
|
||||
raise TypeError("required_event_permission may not be called on OrganizerLevelExportMixin")
|
||||
|
||||
@classmethod
|
||||
def get_required_organizer_permission(cls) -> str:
|
||||
def get_required_organizer_permission(cls) -> Optional[str]:
|
||||
"""
|
||||
The permission level required to use this exporter. Must be set for organizer-level exports. Set to `None` to
|
||||
allow everyone with any access to the organizer.
|
||||
|
||||
@@ -70,6 +70,10 @@ def parse_csv(file, length=None, mode="strict", charset=None):
|
||||
except ImportError:
|
||||
charset = file.charset
|
||||
data = data.decode(charset or "utf-8", mode)
|
||||
|
||||
# remove stray linebreaks from the end of the file
|
||||
data = data.rstrip("\n")
|
||||
|
||||
# If the file was modified on a Mac, it only contains \r as line breaks
|
||||
if '\r' in data and '\n' not in data:
|
||||
data = data.replace('\r', '\n')
|
||||
|
||||
@@ -29,7 +29,9 @@ import inspect
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
|
||||
@@ -74,10 +76,14 @@ def _transactions_mark_order_dirty(order_id, using=None):
|
||||
if "PYTEST_CURRENT_TEST" in os.environ:
|
||||
# We don't care about Order.objects.create() calls in test code so let's try to figure out if this is test code
|
||||
# or not.
|
||||
for frame in inspect.stack():
|
||||
if 'pretix/base/models/orders' in frame.filename:
|
||||
for frame in inspect.stack()[1:]:
|
||||
if (
|
||||
'pretix/base/models/orders' in frame.filename
|
||||
or Path(frame.filename).is_relative_to(Path(django.__file__).parent)
|
||||
):
|
||||
# Ignore model- and django-internal code
|
||||
continue
|
||||
elif 'test_' in frame.filename or 'conftest.py in frame.filename':
|
||||
elif 'test_' in frame.filename or 'conftest.py' in frame.filename:
|
||||
return
|
||||
elif 'pretix/' in frame.filename or 'pretix_' in frame.filename:
|
||||
# This went through non-test code, let's consider it non-test
|
||||
|
||||
@@ -38,6 +38,7 @@ import operator
|
||||
import secrets
|
||||
from datetime import timedelta
|
||||
from functools import reduce
|
||||
from typing import Protocol
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import (
|
||||
@@ -67,6 +68,14 @@ class EmailAddressTakenError(IntegrityError):
|
||||
pass
|
||||
|
||||
|
||||
class PermissionHolder(Protocol):
|
||||
def has_event_permission(self, organizer, event, perm_name=None, request=None, session_key=None) -> bool:
|
||||
...
|
||||
|
||||
def has_organizer_permission(self, organizer, perm_name=None, request=None):
|
||||
...
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
"""
|
||||
This is the user manager for our custom user model. See the User
|
||||
@@ -696,6 +705,18 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
return self.teams.exists()
|
||||
|
||||
|
||||
class UserWithStaffSession:
|
||||
# Wrapper around a User object with a staff session, implementing the PermissionHolder Protocol
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
|
||||
def has_event_permission(self, organizer, event, perm_name=None, request=None, session_key=None) -> bool:
|
||||
return True
|
||||
|
||||
def has_organizer_permission(self, organizer, perm_name=None, request=None):
|
||||
return True
|
||||
|
||||
|
||||
class UserKnownLoginSource(models.Model):
|
||||
user = models.ForeignKey('User', on_delete=models.CASCADE, related_name="known_login_sources")
|
||||
agent_type = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
@@ -229,7 +229,7 @@ class Device(LoggedModel):
|
||||
"""
|
||||
return self._organizer_permission_set() if self.organizer == organizer else set()
|
||||
|
||||
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
|
||||
def has_event_permission(self, organizer, event, perm_name=None, request=None, session_key=None) -> bool:
|
||||
"""
|
||||
Checks if this token is part of a team that grants access of type ``perm_name``
|
||||
to the event ``event``.
|
||||
@@ -238,6 +238,7 @@ class Device(LoggedModel):
|
||||
:param event: The event to check
|
||||
:param perm_name: The permission, e.g. ``event.orders:read``
|
||||
:param request: This parameter is ignored and only defined for compatibility reasons.
|
||||
:param session_key: This parameter is ignored and only defined for compatibility reasons.
|
||||
:return: bool
|
||||
"""
|
||||
has_event_access = (self.all_events and organizer == self.organizer) or (
|
||||
|
||||
@@ -590,7 +590,7 @@ class Order(LockModel, LoggedModel):
|
||||
not kwargs.get('force_save_with_deferred_fields', None) and
|
||||
(not update_fields or ('require_approval' not in update_fields and 'status' not in update_fields))
|
||||
):
|
||||
_fail("It is unsafe to call save() on an OrderFee with deferred fields since we can't check if you missed "
|
||||
_fail("It is unsafe to call save() on an Order with deferred fields since we can't check if you missed "
|
||||
"creating a transaction. Call save(force_save_with_deferred_fields=True) if you really want to do "
|
||||
"this.")
|
||||
|
||||
@@ -2841,7 +2841,7 @@ class OrderPosition(AbstractPosition):
|
||||
if Transaction.key(self) != self.__initial_transaction_key or self.canceled != self.__initial_canceled or not self.pk:
|
||||
_transactions_mark_order_dirty(self.order_id, using=kwargs.get('using', None))
|
||||
elif not kwargs.get('force_save_with_deferred_fields', None):
|
||||
_fail("It is unsafe to call save() on an OrderFee with deferred fields since we can't check if you missed "
|
||||
_fail("It is unsafe to call save() on an OrderPosition with deferred fields since we can't check if you missed "
|
||||
"creating a transaction. Call save(force_save_with_deferred_fields=True) if you really want to do "
|
||||
"this.")
|
||||
|
||||
|
||||
@@ -319,6 +319,9 @@ class TeamQuerySet(models.QuerySet):
|
||||
def event_permission_q(cls, perm_name):
|
||||
from ..permissions import assert_valid_event_permission
|
||||
|
||||
if perm_name is None:
|
||||
return Q()
|
||||
|
||||
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_EVENT_COMPAT: # legacy
|
||||
return reduce(operator.and_, [cls.event_permission_q(p) for p in OLD_TO_NEW_EVENT_COMPAT[perm_name]])
|
||||
assert_valid_event_permission(perm_name, allow_legacy=False)
|
||||
@@ -331,6 +334,9 @@ class TeamQuerySet(models.QuerySet):
|
||||
def organizer_permission_q(cls, perm_name):
|
||||
from ..permissions import assert_valid_organizer_permission
|
||||
|
||||
if perm_name is None:
|
||||
return Q()
|
||||
|
||||
if perm_name.startswith('can_') and perm_name in OLD_TO_NEW_ORGANIZER_COMPAT: # legacy
|
||||
return reduce(operator.and_, [cls.organizer_permission_q(p) for p in OLD_TO_NEW_ORGANIZER_COMPAT[perm_name]])
|
||||
assert_valid_organizer_permission(perm_name, allow_legacy=False)
|
||||
@@ -550,7 +556,7 @@ class TeamAPIToken(models.Model):
|
||||
"""
|
||||
return self.team.organizer_permission_set() if self.team.organizer == organizer else set()
|
||||
|
||||
def has_event_permission(self, organizer, event, perm_name=None, request=None) -> bool:
|
||||
def has_event_permission(self, organizer, event, perm_name=None, request=None, session_key=None) -> bool:
|
||||
"""
|
||||
Checks if this token is part of a team that grants access of type ``perm_name``
|
||||
to the event ``event``.
|
||||
@@ -559,6 +565,7 @@ class TeamAPIToken(models.Model):
|
||||
:param event: The event to check
|
||||
:param perm_name: The permission, e.g. ``event.orders:read``
|
||||
:param request: This parameter is ignored and only defined for compatibility reasons.
|
||||
:param session_key: This parameter is ignored and only defined for compatibility reasons.
|
||||
:return: bool
|
||||
"""
|
||||
has_event_access = (self.team.all_events and organizer == self.team.organizer) or (
|
||||
|
||||
@@ -40,6 +40,7 @@ from pretix.base.models import (
|
||||
CachedFile, Device, Event, Organizer, ScheduledEventExport, TeamAPIToken,
|
||||
User, cachedfile_name,
|
||||
)
|
||||
from pretix.base.models.auth import UserWithStaffSession
|
||||
from pretix.base.models.exports import ScheduledOrganizerExport
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.base.services.tasks import (
|
||||
@@ -211,7 +212,12 @@ def init_event_exporters(event, user=None, token=None, device=None, request=None
|
||||
if not perm_holder.has_event_permission(event.organizer, event, permission_name, request) and not staff_session:
|
||||
continue
|
||||
|
||||
exporter: BaseExporter = response(event=event, organizer=event.organizer, **kwargs)
|
||||
exporter: BaseExporter = response(
|
||||
event=event,
|
||||
organizer=event.organizer,
|
||||
permission_holder=token or device or (UserWithStaffSession(user) if staff_session else user),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if not exporter.available_for_user(user if user and user.is_authenticated else None):
|
||||
continue
|
||||
@@ -243,7 +249,12 @@ def init_organizer_exporters(
|
||||
continue
|
||||
|
||||
if issubclass(response, OrganizerLevelExportMixin):
|
||||
exporter: BaseExporter = response(event=Event.objects.none(), organizer=organizer, **kwargs)
|
||||
exporter: BaseExporter = response(
|
||||
event=Event.objects.none(),
|
||||
organizer=organizer,
|
||||
permission_holder=token or device or (UserWithStaffSession(user) if staff_session else user),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
try:
|
||||
if not perm_holder.has_organizer_permission(organizer, response.get_required_organizer_permission(), request) and not staff_session:
|
||||
@@ -295,7 +306,12 @@ def init_organizer_exporters(
|
||||
if not _has_permission_on_any_team_cache[permission_name] and not staff_session:
|
||||
continue
|
||||
|
||||
exporter: BaseExporter = response(event=_event_list_cache[permission_name], organizer=organizer, **kwargs)
|
||||
exporter: BaseExporter = response(
|
||||
event=_event_list_cache[permission_name],
|
||||
organizer=organizer,
|
||||
permission_holder=token or device or (UserWithStaffSession(user) if staff_session else user),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if not exporter.available_for_user(user if user and user.is_authenticated else None):
|
||||
continue
|
||||
|
||||
@@ -436,7 +436,7 @@ class OrderPositionAddForm(forms.Form):
|
||||
d['used_membership'] = [m for m in self.memberships if str(m.pk) == d['used_membership']][0]
|
||||
else:
|
||||
d['used_membership'] = None
|
||||
if d.get("count", 1) and d.get("seat"):
|
||||
if d.get("count", 1) > 1 and d.get("seat"):
|
||||
raise ValidationError({
|
||||
"seat": _("You can not choose a seat when adding multiple products at once.")
|
||||
})
|
||||
|
||||
@@ -1322,7 +1322,7 @@ class DeviceUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixi
|
||||
def form_valid(self, form):
|
||||
if form.has_changed():
|
||||
self.object.log_action('pretix.device.changed', user=self.request.user, data={
|
||||
k: getattr(self.object, k) if k != 'limit_events' else [e.id for e in getattr(self.object, k).all()]
|
||||
k: form.cleaned_data[k] if k != 'limit_events' else [e.id for e in form.cleaned_data[k]]
|
||||
for k in form.changed_data
|
||||
})
|
||||
|
||||
|
||||
@@ -991,3 +991,30 @@ def test_import_mixed_order_size_consistency(user, event, item):
|
||||
).get()
|
||||
assert ('Inconsistent data in row 2: Column Email address contains value "a2@example.com", but for this order, '
|
||||
'the value has already been set to "a1@example.com".') in str(excinfo.value)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@scopes_disabled()
|
||||
def test_import_line_endings_mix(event, item, user):
|
||||
# Ensures import works with mixed file endings.
|
||||
# See Ticket#23230806 where a file to import ends with \r\n
|
||||
settings = dict(DEFAULT_SETTINGS)
|
||||
settings['item'] = 'static:{}'.format(item.pk)
|
||||
|
||||
cf = inputfile_factory()
|
||||
file = cf.file
|
||||
file.seek(0)
|
||||
data = file.read()
|
||||
data = data.replace(b'\n', b'\r')
|
||||
data = data.rstrip(b'\r\r')
|
||||
data = data + b'\r\n'
|
||||
|
||||
print(data)
|
||||
cf.file.save("input.csv", ContentFile(data))
|
||||
cf.save()
|
||||
|
||||
import_orders.apply(
|
||||
args=(event.pk, cf.id, settings, 'en', user.pk)
|
||||
)
|
||||
assert event.orders.count() == 3
|
||||
assert OrderPosition.objects.count() == 3
|
||||
|
||||
@@ -24,7 +24,6 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from django.core import mail as djmail
|
||||
from django.db import transaction
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope
|
||||
|
||||
@@ -75,47 +74,42 @@ def user(team):
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def monkeypatch_on_commit(monkeypatch):
|
||||
monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notification_trigger_event_specific(event, order, user, monkeypatch_on_commit):
|
||||
def test_notification_trigger_event_specific(event, order, user, django_capture_on_commit_callbacks):
|
||||
djmail.outbox = []
|
||||
user.notification_settings.create(
|
||||
method='mail', event=event, action_type='pretix.event.order.paid', enabled=True
|
||||
)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.paid', {})
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].subject.endswith("DUMMY: Order FOO has been marked as paid.")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notification_trigger_global(event, order, user, monkeypatch_on_commit):
|
||||
def test_notification_trigger_global(event, order, user, django_capture_on_commit_callbacks):
|
||||
djmail.outbox = []
|
||||
user.notification_settings.create(
|
||||
method='mail', event=None, action_type='pretix.event.order.paid', enabled=True
|
||||
)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.paid', {})
|
||||
assert len(djmail.outbox) == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notification_trigger_global_wildcard(event, order, user, monkeypatch_on_commit):
|
||||
def test_notification_trigger_global_wildcard(event, order, user, django_capture_on_commit_callbacks):
|
||||
djmail.outbox = []
|
||||
user.notification_settings.create(
|
||||
method='mail', event=None, action_type='pretix.event.order.changed.*', enabled=True
|
||||
)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.changed.item', {})
|
||||
assert len(djmail.outbox) == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notification_enabled_global_ignored_specific(event, order, user, monkeypatch_on_commit):
|
||||
def test_notification_enabled_global_ignored_specific(event, order, user, django_capture_on_commit_callbacks):
|
||||
djmail.outbox = []
|
||||
user.notification_settings.create(
|
||||
method='mail', event=None, action_type='pretix.event.order.paid', enabled=True
|
||||
@@ -123,24 +117,24 @@ def test_notification_enabled_global_ignored_specific(event, order, user, monkey
|
||||
user.notification_settings.create(
|
||||
method='mail', event=event, action_type='pretix.event.order.paid', enabled=False
|
||||
)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.paid', {})
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notification_ignore_same_user(event, order, user, monkeypatch_on_commit):
|
||||
def test_notification_ignore_same_user(event, order, user, django_capture_on_commit_callbacks):
|
||||
djmail.outbox = []
|
||||
user.notification_settings.create(
|
||||
method='mail', event=event, action_type='pretix.event.order.paid', enabled=True
|
||||
)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.paid', {}, user=user)
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_notification_ignore_insufficient_permissions(event, order, user, team, monkeypatch_on_commit):
|
||||
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}
|
||||
@@ -148,7 +142,7 @@ def test_notification_ignore_insufficient_permissions(event, order, user, team,
|
||||
user.notification_settings.create(
|
||||
method='mail', event=event, action_type='pretix.event.order.paid', enabled=True
|
||||
)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.paid', {})
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ from zoneinfo import ZoneInfo
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.core import mail as djmail
|
||||
from django.db import transaction
|
||||
from django.db.models import F, Sum
|
||||
from django.test import TestCase, override_settings
|
||||
from django.test import TestCase, TransactionTestCase, override_settings
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django_countries.fields import Country
|
||||
from django_scopes import scope
|
||||
@@ -1225,12 +1226,6 @@ class DownloadReminderTests(TestCase):
|
||||
assert len(djmail.outbox) == 0
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def class_monkeypatch(request, monkeypatch):
|
||||
request.cls.monkeypatch = monkeypatch
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("class_monkeypatch")
|
||||
class OrderCancelTests(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -1258,7 +1253,6 @@ class OrderCancelTests(TestCase):
|
||||
self.order.create_transactions()
|
||||
generate_invoice(self.order)
|
||||
djmail.outbox = []
|
||||
self.monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_cancel_canceled(self):
|
||||
@@ -1351,14 +1345,14 @@ class OrderCancelTests(TestCase):
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
djmail.outbox = []
|
||||
cancel_order(self.order.pk, send_mail=True)
|
||||
print([s.subject for s in djmail.outbox])
|
||||
print([s.to for s in djmail.outbox])
|
||||
with self.captureOnCommitCallbacks(execute=True):
|
||||
cancel_order(self.order.pk, send_mail=True)
|
||||
|
||||
assert len(djmail.outbox) == 2
|
||||
assert ["invoice@example.org"] == djmail.outbox[0].to
|
||||
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
|
||||
assert ["dummy@dummy.test"] == djmail.outbox[1].to
|
||||
assert not any(["Invoice_" in a[0] for a in djmail.outbox[1].attachments])
|
||||
assert ["dummy@dummy.test"] == djmail.outbox[0].to
|
||||
assert not any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
|
||||
assert ["invoice@example.org"] == djmail.outbox[1].to
|
||||
assert any(["Invoice_" in a[0] for a in djmail.outbox[1].attachments])
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_cancel_paid_with_too_high_fee(self):
|
||||
@@ -1488,8 +1482,7 @@ class OrderCancelTests(TestCase):
|
||||
assert self.order.all_logentries().filter(action_type='pretix.event.order.refund.requested').exists()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("class_monkeypatch")
|
||||
class OrderChangeManagerTests(TestCase):
|
||||
class BaseOrderChangeManagerTestCase:
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.o = Organizer.objects.create(name='Dummy', slug='dummy', plugins='pretix.plugins.banktransfer')
|
||||
@@ -1552,7 +1545,6 @@ class OrderChangeManagerTests(TestCase):
|
||||
self.seat_a1 = self.event.seats.create(seat_number="A1", product=self.stalls, seat_guid="A1")
|
||||
self.seat_a2 = self.event.seats.create(seat_number="A2", product=self.stalls, seat_guid="A2")
|
||||
self.seat_a3 = self.event.seats.create(seat_number="A3", product=self.stalls, seat_guid="A3")
|
||||
self.monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
|
||||
|
||||
def _enable_reverse_charge(self):
|
||||
self.tr7.eu_reverse_charge = True
|
||||
@@ -1566,6 +1558,8 @@ class OrderChangeManagerTests(TestCase):
|
||||
country=Country('AT')
|
||||
)
|
||||
|
||||
|
||||
class OrderChangeManagerTests(BaseOrderChangeManagerTestCase, TestCase):
|
||||
@classscope(attr='o')
|
||||
def test_multiple_commits_forbidden(self):
|
||||
self.ocm.change_price(self.op1, Decimal('10.00'))
|
||||
@@ -3904,15 +3898,16 @@ class OrderChangeManagerTests(TestCase):
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_set_valid_until(self):
|
||||
self.event.settings.ticket_secret_generator = "pretix_sig1"
|
||||
assign_ticket_secret(self.event, self.op1, force_invalidate=True, save=True)
|
||||
old_secret = self.op1.secret
|
||||
with transaction.atomic():
|
||||
self.event.settings.ticket_secret_generator = "pretix_sig1"
|
||||
assign_ticket_secret(self.event, self.op1, force_invalidate=True, save=True)
|
||||
old_secret = self.op1.secret
|
||||
|
||||
dt = make_aware(datetime(2022, 9, 20, 15, 0, 0, 0))
|
||||
self.ocm.change_valid_until(self.op1, dt)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.secret != old_secret
|
||||
dt = make_aware(datetime(2022, 9, 20, 15, 0, 0, 0))
|
||||
self.ocm.change_valid_until(self.op1, dt)
|
||||
self.ocm.commit()
|
||||
self.op1.refresh_from_db()
|
||||
assert self.op1.secret != old_secret
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_unset_valid_from_until(self):
|
||||
@@ -3937,6 +3932,8 @@ class OrderChangeManagerTests(TestCase):
|
||||
assert len(djmail.outbox) == 1
|
||||
assert len(["Invoice_" in a[0] for a in djmail.outbox[0].attachments]) == 2
|
||||
|
||||
|
||||
class OrderChangeManagerTransactionalTests(BaseOrderChangeManagerTestCase, TransactionTestCase):
|
||||
@classscope(attr='o')
|
||||
def test_new_invoice_send_somewhere_else(self):
|
||||
generate_invoice(self.order)
|
||||
|
||||
@@ -25,7 +25,6 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from django.db import transaction
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
@@ -82,14 +81,9 @@ def force_str(v):
|
||||
return v.decode() if isinstance(v, bytes) else str(v)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def monkeypatch_on_commit(monkeypatch):
|
||||
monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@responses.activate
|
||||
def test_webhook_trigger_event_specific(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_trigger_event_specific(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
responses.add_callback(
|
||||
responses.POST, 'https://google.com',
|
||||
callback=lambda r: (200, {}, 'ok'),
|
||||
@@ -97,7 +91,7 @@ def test_webhook_trigger_event_specific(event, order, webhook, monkeypatch_on_co
|
||||
match_querystring=None, # https://github.com/getsentry/responses/issues/464
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
le = order.log_action('pretix.event.order.paid', {})
|
||||
assert len(responses.calls) == 1
|
||||
assert json.loads(force_str(responses.calls[0].request.body)) == {
|
||||
@@ -119,12 +113,12 @@ def test_webhook_trigger_event_specific(event, order, webhook, monkeypatch_on_co
|
||||
|
||||
@pytest.mark.django_db
|
||||
@responses.activate
|
||||
def test_webhook_trigger_global(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_trigger_global(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
webhook.limit_events.clear()
|
||||
webhook.all_events = True
|
||||
webhook.save()
|
||||
responses.add(responses.POST, 'https://google.com', status=200)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
le = order.log_action('pretix.event.order.paid', {})
|
||||
assert len(responses.calls) == 1
|
||||
assert json.loads(force_str(responses.calls[0].request.body)) == {
|
||||
@@ -138,13 +132,13 @@ def test_webhook_trigger_global(event, order, webhook, monkeypatch_on_commit):
|
||||
|
||||
@pytest.mark.django_db
|
||||
@responses.activate
|
||||
def test_webhook_trigger_global_wildcard(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_trigger_global_wildcard(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
webhook.listeners.create(action_type="pretix.event.order.changed.*")
|
||||
webhook.limit_events.clear()
|
||||
webhook.all_events = True
|
||||
webhook.save()
|
||||
responses.add(responses.POST, 'https://google.com', status=200)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
le = order.log_action('pretix.event.order.changed.item', {})
|
||||
assert len(responses.calls) == 1
|
||||
assert json.loads(force_str(responses.calls[0].request.body)) == {
|
||||
@@ -158,30 +152,30 @@ def test_webhook_trigger_global_wildcard(event, order, webhook, monkeypatch_on_c
|
||||
|
||||
@pytest.mark.django_db
|
||||
@responses.activate
|
||||
def test_webhook_ignore_wrong_action_type(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_ignore_wrong_action_type(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
responses.add(responses.POST, 'https://google.com', status=200)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.changed.item', {})
|
||||
assert len(responses.calls) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@responses.activate
|
||||
def test_webhook_ignore_disabled(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_ignore_disabled(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
webhook.enabled = False
|
||||
webhook.save()
|
||||
responses.add(responses.POST, 'https://google.com', status=200)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.changed.item', {})
|
||||
assert len(responses.calls) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@responses.activate
|
||||
def test_webhook_ignore_wrong_event(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_ignore_wrong_event(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
webhook.limit_events.clear()
|
||||
responses.add(responses.POST, 'https://google.com', status=200)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.changed.item', {})
|
||||
assert len(responses.calls) == 0
|
||||
|
||||
@@ -189,10 +183,10 @@ def test_webhook_ignore_wrong_event(event, order, webhook, monkeypatch_on_commit
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.xfail(reason="retries can't be tested with celery_always_eager")
|
||||
@responses.activate
|
||||
def test_webhook_retry(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_retry(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
responses.add(responses.POST, 'https://google.com', status=500)
|
||||
responses.add(responses.POST, 'https://google.com', status=200)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.paid', {})
|
||||
assert len(responses.calls) == 2
|
||||
with scopes_disabled():
|
||||
@@ -216,9 +210,9 @@ def test_webhook_retry(event, order, webhook, monkeypatch_on_commit):
|
||||
|
||||
@pytest.mark.django_db
|
||||
@responses.activate
|
||||
def test_webhook_disable_gone(event, order, webhook, monkeypatch_on_commit):
|
||||
def test_webhook_disable_gone(event, order, webhook, django_capture_on_commit_callbacks):
|
||||
responses.add(responses.POST, 'https://google.com', status=410)
|
||||
with transaction.atomic():
|
||||
with django_capture_on_commit_callbacks(execute=True):
|
||||
order.log_action('pretix.event.order.paid', {})
|
||||
assert len(responses.calls) == 1
|
||||
webhook.refresh_from_db()
|
||||
|
||||
@@ -131,3 +131,8 @@ def set_lock_namespaces(request):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def class_monkeypatch(request, monkeypatch):
|
||||
request.cls.monkeypatch = monkeypatch
|
||||
|
||||
@@ -385,11 +385,6 @@ class RegistrationFormTest(TestCase):
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def class_monkeypatch(request, monkeypatch):
|
||||
request.cls.monkeypatch = monkeypatch
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("class_monkeypatch")
|
||||
class Login2FAFormTest(TestCase):
|
||||
|
||||
|
||||
@@ -49,11 +49,6 @@ from tests.base import SoupTest, extract_form_fields
|
||||
from pretix.base.models import Event, LogEntry, Order, Organizer, Team, User
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def class_monkeypatch(request, monkeypatch):
|
||||
request.cls.monkeypatch = monkeypatch
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("class_monkeypatch")
|
||||
class EventsTest(SoupTest):
|
||||
@scopes_disabled()
|
||||
|
||||
@@ -33,11 +33,6 @@ from tests.base import SoupTest, extract_form_fields
|
||||
from pretix.base.models import Event, Organizer, OutgoingMail, Team, User
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def class_monkeypatch(request, monkeypatch):
|
||||
request.cls.monkeypatch = monkeypatch
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("class_monkeypatch")
|
||||
class OrganizerTest(SoupTest):
|
||||
@scopes_disabled()
|
||||
|
||||
@@ -286,11 +286,6 @@ class UserPasswordChangeTest(SoupTest):
|
||||
assert self.user.needs_password_change is False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def class_monkeypatch(request, monkeypatch):
|
||||
request.cls.monkeypatch = monkeypatch
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("class_monkeypatch")
|
||||
class UserSettings2FATest(SoupTest):
|
||||
def setUp(self):
|
||||
|
||||
@@ -33,7 +33,7 @@ from django.conf import settings
|
||||
from django.core import mail as djmail
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.core.signing import dumps
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django_countries.fields import Country
|
||||
@@ -60,12 +60,6 @@ from pretix.testutils.sessions import get_cart_session_key
|
||||
from .test_timemachine import TimemachineTestMixin
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def class_monkeypatch(request, monkeypatch):
|
||||
request.cls.monkeypatch = monkeypatch
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("class_monkeypatch")
|
||||
class BaseCheckoutTestCase:
|
||||
@scopes_disabled()
|
||||
def setUp(self):
|
||||
@@ -104,7 +98,6 @@ class BaseCheckoutTestCase:
|
||||
self.workshopquota.items.add(self.workshop2)
|
||||
self.workshopquota.variations.add(self.workshop2a)
|
||||
self.workshopquota.variations.add(self.workshop2b)
|
||||
self.monkeypatch.setattr("django.db.transaction.on_commit", lambda t: t())
|
||||
|
||||
def _set_session(self, key, value):
|
||||
session = self.client.session
|
||||
@@ -4420,6 +4413,8 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase):
|
||||
assert len(djmail.outbox) == 1
|
||||
assert any(["Invoice_" in a[0] for a in djmail.outbox[0].attachments])
|
||||
|
||||
|
||||
class CheckoutTransactionTestCase(BaseCheckoutTestCase, TransactionTestCase):
|
||||
def test_order_confirmation_mail_invoice_sent_somewhere_else(self):
|
||||
self.event.settings.invoice_address_asked = True
|
||||
self.event.settings.invoice_address_required = True
|
||||
|
||||
Reference in New Issue
Block a user