diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index 7bafddd3a3..1eb05b4940 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -139,6 +139,7 @@ class CheckinListViewSet(viewsets.ModelViewSet): ) return qs + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -153,6 +154,7 @@ class CheckinListViewSet(viewsets.ModelViewSet): ctx['event'] = self.request.event return ctx + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( diff --git a/src/pretix/api/views/discount.py b/src/pretix/api/views/discount.py index 53386d3d04..92bbd671e0 100644 --- a/src/pretix/api/views/discount.py +++ b/src/pretix/api/views/discount.py @@ -32,6 +32,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under the License. +from django.db import transaction from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_scopes import scopes_disabled from rest_framework import viewsets @@ -64,6 +65,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet): 'limit_sales_channels', ) + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -78,6 +80,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet): ctx['event'] = self.request.event return ctx + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -87,6 +90,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied('You cannot delete this discount because it already has ' diff --git a/src/pretix/api/views/event.py b/src/pretix/api/views/event.py index 630988aacd..e92f99ed8b 100644 --- a/src/pretix/api/views/event.py +++ b/src/pretix/api/views/event.py @@ -257,6 +257,7 @@ class EventViewSet(viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_create(self, serializer): copy_from = None if 'clone_from' in self.request.GET: @@ -320,6 +321,7 @@ class EventViewSet(viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied('The event can not be deleted as it already contains orders. Please set \'live\'' @@ -355,6 +357,7 @@ class CloneEventViewSet(viewsets.ModelViewSet): ctx['organizer'] = self.request.organizer return ctx + @transaction.atomic() def perform_create(self, serializer): # Weird edge case: Requires settings permission on the event (to read) but also on the organizer (two write) perm_holder = (self.request.auth if isinstance(self.request.auth, (Device, TeamAPIToken)) @@ -513,6 +516,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): resp['X-Page-Generated'] = date return resp + @transaction.atomic() def perform_update(self, serializer): original_data = self.get_serializer(instance=serializer.instance).data super().perform_update(serializer) @@ -529,6 +533,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -538,6 +543,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied('The sub-event can not be deleted as it has already been used in orders. Please set' @@ -566,6 +572,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet): def get_queryset(self): return self.request.event.tax_rules.all() + @transaction.atomic() def perform_update(self, serializer): super().perform_update(serializer) serializer.instance.log_action( @@ -575,6 +582,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -584,6 +592,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied('This tax rule can not be deleted as it is currently in use.') @@ -757,6 +766,7 @@ class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelV } return ctx + @transaction.atomic() def perform_update(self, serializer): super().perform_update(serializer) serializer.instance.event.log_action( @@ -766,6 +776,7 @@ class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelV data={"seats": [serializer.instance.pk]}, ) + @transaction.atomic() def bulk_change_blocked(self, blocked): s = SeatBulkBlockInputSerializer( data=self.request.data, diff --git a/src/pretix/api/views/exporters.py b/src/pretix/api/views/exporters.py index 26aca588f8..71931afcc1 100644 --- a/src/pretix/api/views/exporters.py +++ b/src/pretix/api/views/exporters.py @@ -23,6 +23,7 @@ from datetime import timedelta from celery.result import AsyncResult from django.conf import settings +from django.db import transaction from django.http import Http404 from django.shortcuts import get_object_or_404 from django.utils.functional import cached_property @@ -227,6 +228,7 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet): qs = self.request.event.scheduled_exports return qs.select_related("owner") + @transaction.atomic() def perform_create(self, serializer): if not self.request.user.is_authenticated: raise PermissionDenied('Creation of exports requires user-specific API access.') @@ -257,6 +259,7 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet): )) return {e.identifier: e for e in exporters} + @transaction.atomic() def perform_update(self, serializer): if not self.request.user.is_authenticated or self.request.user != serializer.instance.owner: # This is to prevent a possible privilege escalation where user A creates a scheduled export and @@ -282,6 +285,7 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): self.request.event.log_action( 'pretix.event.export.schedule.deleted', @@ -309,6 +313,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet): qs = self.request.organizer.scheduled_exports return qs.select_related("owner") + @transaction.atomic() def perform_create(self, serializer): if not self.request.user.is_authenticated: raise PermissionDenied('Creation of exports requires user-specific API access.') @@ -339,6 +344,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet): )) return {e.identifier: e for e in exporters} + @transaction.atomic() def perform_update(self, serializer): if not self.request.user.is_authenticated or self.request.user != serializer.instance.owner: # This is to prevent a possible privilege escalation where user A creates a scheduled export and @@ -389,6 +395,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): self.request.organizer.log_action( 'pretix.organizer.export.schedule.deleted', diff --git a/src/pretix/api/views/item.py b/src/pretix/api/views/item.py index f2f0949392..8250600a0a 100644 --- a/src/pretix/api/views/item.py +++ b/src/pretix/api/views/item.py @@ -33,6 +33,7 @@ # License for the specific language governing permissions and limitations under the License. import django_filters +from django.db import transaction from django.db.models import Q from django.shortcuts import get_object_or_404 from django.utils.functional import cached_property @@ -109,6 +110,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet): 'limit_sales_channels', 'variations__limit_sales_channels', 'program_times' ).all() + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -123,6 +125,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet): ctx['event'] = self.request.event return ctx + @transaction.atomic() def perform_update(self, serializer): original_data = self.get_serializer(instance=serializer.instance).data @@ -139,6 +142,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied('This item cannot be deleted because it has already been ordered ' @@ -183,6 +187,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet): ctx['event'] = self.request.event return ctx + @transaction.atomic() def perform_create(self, serializer): item = self.item if not item.has_variations: @@ -197,6 +202,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet): {'value': serializer.instance.value}) ) + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.item.log_action( @@ -207,6 +213,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet): {'value': serializer.instance.value}) ) + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied('This variation cannot be deleted because it has already been ordered ' @@ -249,6 +256,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet): ctx['item'] = self.item return ctx + @transaction.atomic() def perform_create(self, serializer): item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event) serializer.save(base_item=item) @@ -259,6 +267,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.base_item.log_action( @@ -268,6 +277,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_destroy(self, instance): super().perform_destroy(instance) instance.base_item.log_action( @@ -303,6 +313,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet): ctx['item'] = self.item return ctx + @transaction.atomic() def perform_create(self, serializer): item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event) serializer.save(item=item) @@ -313,6 +324,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.item.log_action( @@ -322,6 +334,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_destroy(self, instance): super().perform_destroy(instance) instance.item.log_action( @@ -354,6 +367,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet): ctx['item'] = self.item return ctx + @transaction.atomic() def perform_create(self, serializer): item = self.item category = get_object_or_404(ItemCategory, pk=self.request.data['addon_category']) @@ -365,6 +379,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.base_item.log_action( @@ -374,6 +389,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_destroy(self, instance): super().perform_destroy(instance) instance.base_item.log_action( @@ -403,6 +419,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet): def get_queryset(self): return self.request.event.categories.all() + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -417,6 +434,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet): ctx['event'] = self.request.event return ctx + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -426,6 +444,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): for item in instance.items.all(): item.category = None @@ -458,6 +477,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet): def get_queryset(self): return self.request.event.questions.prefetch_related('options').all() + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -472,6 +492,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet): ctx['event'] = self.request.event return ctx + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -481,6 +502,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): instance.log_action( 'pretix.event.question.deleted', @@ -509,6 +531,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet): ctx['question'] = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event) return ctx + @transaction.atomic() def perform_create(self, serializer): q = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event) serializer.save(question=q) @@ -519,6 +542,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_update(self, serializer): serializer.save(event=self.request.event) serializer.instance.question.log_action( @@ -528,6 +552,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk}) ) + @transaction.atomic() def perform_destroy(self, instance): instance.question.log_action( 'pretix.event.question.option.deleted', @@ -586,6 +611,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet): serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -608,6 +634,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet): ctx['request'] = self.request return ctx + @transaction.atomic() def perform_update(self, serializer): original_data = self.get_serializer(instance=serializer.instance).data @@ -663,6 +690,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet): ) serializer.instance.rebuild_cache() + @transaction.atomic() def perform_destroy(self, instance): instance.log_action( 'pretix.event.quota.deleted', diff --git a/src/pretix/api/views/organizer.py b/src/pretix/api/views/organizer.py index 3551437b6f..db28044b30 100644 --- a/src/pretix/api/views/organizer.py +++ b/src/pretix/api/views/organizer.py @@ -394,6 +394,7 @@ class TeamViewSet(viewsets.ModelViewSet): ) return inst + @transaction.atomic() def perform_destroy(self, instance): instance.log_action('pretix.team.deleted', user=self.request.user, auth=self.request.auth) instance.delete() @@ -693,6 +694,7 @@ class MembershipTypeViewSet(viewsets.ModelViewSet): ctx['organizer'] = self.request.organizer return ctx + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied("Can only be deleted if unused.") @@ -833,6 +835,7 @@ class SalesChannelViewSet(viewsets.ModelViewSet): ) return inst + @transaction.atomic() def perform_destroy(self, instance): if not instance.allow_delete(): raise PermissionDenied("Can only be deleted if unused.") diff --git a/src/pretix/api/views/waitinglist.py b/src/pretix/api/views/waitinglist.py index 569c64b569..c2fd0b30ba 100644 --- a/src/pretix/api/views/waitinglist.py +++ b/src/pretix/api/views/waitinglist.py @@ -20,6 +20,7 @@ # . # import django_filters +from django.db import transaction from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_scopes import scopes_disabled from rest_framework import viewsets @@ -62,6 +63,7 @@ class WaitingListViewSet(viewsets.ModelViewSet): ctx['event'] = self.request.event return ctx + @transaction.atomic() def perform_create(self, serializer): serializer.save(event=self.request.event) serializer.instance.log_action( @@ -70,6 +72,7 @@ class WaitingListViewSet(viewsets.ModelViewSet): auth=self.request.auth, ) + @transaction.atomic() def perform_update(self, serializer): if serializer.instance.voucher: raise PermissionDenied('This entry can not be changed as it has already been assigned a voucher.') @@ -80,6 +83,7 @@ class WaitingListViewSet(viewsets.ModelViewSet): auth=self.request.auth, ) + @transaction.atomic() def perform_destroy(self, instance): if instance.voucher: raise PermissionDenied('This entry can not be deleted as it has already been assigned a voucher.') diff --git a/src/pretix/api/views/webhooks.py b/src/pretix/api/views/webhooks.py index b7b883906c..d42c40c6bd 100644 --- a/src/pretix/api/views/webhooks.py +++ b/src/pretix/api/views/webhooks.py @@ -20,6 +20,7 @@ # . # import django_filters +from django.db import transaction from django_filters.rest_framework import DjangoFilterBackend, FilterSet from rest_framework import viewsets @@ -48,6 +49,7 @@ class WebHookViewSet(viewsets.ModelViewSet): ctx['organizer'] = self.request.organizer return ctx + @transaction.atomic() def perform_create(self, serializer): inst = serializer.save(organizer=self.request.organizer) self.request.organizer.log_action( @@ -57,6 +59,7 @@ class WebHookViewSet(viewsets.ModelViewSet): data=merge_dicts(self.request.data, {'id': inst.pk}) ) + @transaction.atomic() def perform_update(self, serializer): inst = serializer.save(organizer=self.request.organizer) self.request.organizer.log_action( @@ -67,6 +70,7 @@ class WebHookViewSet(viewsets.ModelViewSet): ) return inst + @transaction.atomic() def perform_destroy(self, instance): self.request.organizer.log_action( 'pretix.webhook.changed', diff --git a/src/pretix/plugins/sendmail/api.py b/src/pretix/plugins/sendmail/api.py index 7d5e1548ae..2e751e5ef5 100644 --- a/src/pretix/plugins/sendmail/api.py +++ b/src/pretix/plugins/sendmail/api.py @@ -20,6 +20,7 @@ # . # from django.core.exceptions import ValidationError +from django.db import transaction from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_scopes import scopes_disabled from rest_framework import viewsets @@ -118,6 +119,7 @@ class RuleViewSet(viewsets.ModelViewSet): def get_queryset(self): return Rule.objects.filter(event=self.request.event) + @transaction.atomic() def perform_create(self, serializer): super().perform_create(serializer) serializer.instance.log_action( @@ -128,6 +130,7 @@ class RuleViewSet(viewsets.ModelViewSet): ) + @transaction.atomic() def perform_update(self, serializer): super().perform_update(serializer) serializer.instance.log_action( @@ -137,6 +140,7 @@ class RuleViewSet(viewsets.ModelViewSet): data=self.request.data ) + @transaction.atomic() def perform_destroy(self, instance): instance.log_action( 'pretix.plugins.sendmail.rule.deleted',