Install django-scopes

This commit is contained in:
Raphael Michel
2019-05-15 07:04:11 +02:00
parent b1db5dbb3e
commit 5c06c41a5b
30 changed files with 348 additions and 196 deletions

View File

@@ -3,7 +3,7 @@ from rest_framework.permissions import SAFE_METHODS, BasePermission
from pretix.api.models import OAuthAccessToken from pretix.api.models import OAuthAccessToken
from pretix.base.models import Device, Event, User from pretix.base.models import Device, Event, User
from pretix.base.models.auth import SuperuserPermissionSet from pretix.base.models.auth import SuperuserPermissionSet
from pretix.base.models.organizer import Organizer, TeamAPIToken from pretix.base.models.organizer import TeamAPIToken
from pretix.helpers.security import ( from pretix.helpers.security import (
SessionInvalid, SessionReauthRequired, assert_session_valid, SessionInvalid, SessionReauthRequired, assert_session_valid,
) )
@@ -50,9 +50,6 @@ class EventPermission(BasePermission):
return False return False
elif 'organizer' in request.resolver_match.kwargs: elif 'organizer' in request.resolver_match.kwargs:
request.organizer = Organizer.objects.filter(
slug=request.resolver_match.kwargs['organizer'],
).first()
if not request.organizer or not perm_holder.has_organizer_permission(request.organizer, request=request): if not request.organizer or not perm_holder.has_organizer_permission(request.organizer, request=request):
return False return False
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key): if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):

View File

@@ -1,4 +1,6 @@
import json import json
from django.urls import resolve
from django_scopes import scope
from hashlib import sha1 from hashlib import sha1
from django.conf import settings from django.conf import settings
@@ -8,6 +10,7 @@ from django.utils.timezone import now
from rest_framework import status from rest_framework import status
from pretix.api.models import ApiCall from pretix.api.models import ApiCall
from pretix.base.models import Organizer
class IdempotencyMiddleware: class IdempotencyMiddleware:
@@ -89,3 +92,21 @@ class IdempotencyMiddleware:
for k, v in json.loads(call.response_headers).values(): for k, v in json.loads(call.response_headers).values():
r[k] = v r[k] = v
return r return r
class ApiScopeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request: HttpRequest):
if not request.path.startswith('/api/'):
return self.get_response(request)
url = resolve(request.path_info)
if 'organizer' in url.kwargs:
request.organizer = Organizer.objects.filter(
slug=url.kwargs['organizer'],
).first()
with scope(organizer=getattr(request, 'organizer', None)):
return self.get_response(request)

View File

@@ -6,6 +6,7 @@ from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import DateTimeField from rest_framework.fields import DateTimeField
@@ -25,10 +26,11 @@ from pretix.base.services.checkin import (
from pretix.helpers.database import FixedOrderBy from pretix.helpers.database import FixedOrderBy
class CheckinListFilter(FilterSet): with scopes_disabled():
class Meta: class CheckinListFilter(FilterSet):
model = CheckinList class Meta:
fields = ['subevent'] model = CheckinList
fields = ['subevent']
class CheckinListViewSet(viewsets.ModelViewSet): class CheckinListViewSet(viewsets.ModelViewSet):
@@ -154,7 +156,7 @@ class CheckinOrderPositionFilter(OrderPositionFilter):
class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinListOrderPositionSerializer serializer_class = CheckinListOrderPositionSerializer
queryset = OrderPosition.objects.none() queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter) filter_backends = (DjangoFilterBackend, RichOrderingFilter)
ordering = ('attendee_name_cached', 'positionid') ordering = ('attendee_name_cached', 'positionid')
ordering_fields = ( ordering_fields = (

View File

@@ -3,6 +3,7 @@ from django.db import transaction
from django.db.models import ProtectedError, Q from django.db.models import ProtectedError, Q
from django.utils.timezone import now from django.utils.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import filters, viewsets from rest_framework import filters, viewsets
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
@@ -19,50 +20,51 @@ from pretix.base.models.event import SubEvent
from pretix.helpers.dicts import merge_dicts from pretix.helpers.dicts import merge_dicts
class EventFilter(FilterSet): with scopes_disabled():
is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs') class EventFilter(FilterSet):
is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs') is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs') is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs')
class Meta: class Meta:
model = Event model = Event
fields = ['is_public', 'live', 'has_subevents'] fields = ['is_public', 'live', 'has_subevents']
def ends_after_qs(self, queryset, name, value): def ends_after_qs(self, queryset, name, value):
expr = ( expr = (
Q(has_subevents=False) & Q(has_subevents=False) &
Q( Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=value)) Q(Q(date_to__isnull=True) & Q(date_from__gte=value))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=value)) | Q(Q(date_to__isnull=False) & Q(date_to__gte=value))
)
) )
)
return queryset.filter(expr)
def is_past_qs(self, queryset, name, value):
expr = (
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
)
)
if value:
return queryset.filter(expr) return queryset.filter(expr)
else:
return queryset.exclude(expr)
def is_future_qs(self, queryset, name, value): def is_past_qs(self, queryset, name, value):
expr = ( expr = (
Q(has_subevents=False) & Q(has_subevents=False) &
Q( Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now())) Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now())) | Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
)
) )
) if value:
if value: return queryset.filter(expr)
return queryset.filter(expr) else:
else: return queryset.exclude(expr)
return queryset.exclude(expr)
def is_future_qs(self, queryset, name, value):
expr = (
Q(has_subevents=False) &
Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now()))
)
)
if value:
return queryset.filter(expr)
else:
return queryset.exclude(expr)
class EventViewSet(viewsets.ModelViewSet): class EventViewSet(viewsets.ModelViewSet):
@@ -182,41 +184,42 @@ class CloneEventViewSet(viewsets.ModelViewSet):
) )
class SubEventFilter(FilterSet): with scopes_disabled():
is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs') class SubEventFilter(FilterSet):
is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs') is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs') is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs')
ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs')
class Meta: class Meta:
model = SubEvent model = SubEvent
fields = ['active', 'event__live'] fields = ['active', 'event__live']
def ends_after_qs(self, queryset, name, value): def ends_after_qs(self, queryset, name, value):
expr = Q( expr = Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=value)) Q(Q(date_to__isnull=True) & Q(date_from__gte=value))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=value)) | Q(Q(date_to__isnull=False) & Q(date_to__gte=value))
) )
return queryset.filter(expr)
def is_past_qs(self, queryset, name, value):
expr = Q(
Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
)
if value:
return queryset.filter(expr) return queryset.filter(expr)
else:
return queryset.exclude(expr)
def is_future_qs(self, queryset, name, value): def is_past_qs(self, queryset, name, value):
expr = Q( expr = Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now())) Q(Q(date_to__isnull=True) & Q(date_from__lt=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now())) | Q(Q(date_to__isnull=False) & Q(date_to__lt=now()))
) )
if value: if value:
return queryset.filter(expr) return queryset.filter(expr)
else: else:
return queryset.exclude(expr) return queryset.exclude(expr)
def is_future_qs(self, queryset, name, value):
expr = Q(
Q(Q(date_to__isnull=True) & Q(date_from__gte=now()))
| Q(Q(date_to__isnull=False) & Q(date_to__gte=now()))
)
if value:
return queryset.filter(expr)
else:
return queryset.exclude(expr)
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet): class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):

View File

@@ -3,6 +3,7 @@ from django.db.models import Q
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
@@ -22,18 +23,19 @@ from pretix.base.models import (
from pretix.helpers.dicts import merge_dicts from pretix.helpers.dicts import merge_dicts
class ItemFilter(FilterSet): with scopes_disabled():
tax_rate = django_filters.CharFilter(method='tax_rate_qs') class ItemFilter(FilterSet):
tax_rate = django_filters.CharFilter(method='tax_rate_qs')
def tax_rate_qs(self, queryset, name, value): def tax_rate_qs(self, queryset, name, value):
if value in ("0", "None", "0.00"): if value in ("0", "None", "0.00"):
return queryset.filter(Q(tax_rule__isnull=True) | Q(tax_rule__rate=0)) return queryset.filter(Q(tax_rule__isnull=True) | Q(tax_rule__rate=0))
else: else:
return queryset.filter(tax_rule__rate=value) return queryset.filter(tax_rule__rate=value)
class Meta: class Meta:
model = Item model = Item
fields = ['active', 'category', 'admission', 'tax_rate', 'free_price'] fields = ['active', 'category', 'admission', 'tax_rate', 'free_price']
class ItemViewSet(ConditionalListView, viewsets.ModelViewSet): class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
@@ -319,10 +321,11 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
super().perform_destroy(instance) super().perform_destroy(instance)
class QuestionFilter(FilterSet): with scopes_disabled():
class Meta: class QuestionFilter(FilterSet):
model = Question class Meta:
fields = ['ask_during_checkin', 'required', 'identifier'] model = Question
fields = ['ask_during_checkin', 'required', 'identifier']
class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet): class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
@@ -418,10 +421,11 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
super().perform_destroy(instance) super().perform_destroy(instance)
class QuotaFilter(FilterSet): with scopes_disabled():
class Meta: class QuotaFilter(FilterSet):
model = Quota class Meta:
fields = ['subevent'] model = Quota
fields = ['subevent']
class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet): class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):

View File

@@ -11,6 +11,7 @@ from django.shortcuts import get_object_or_404
from django.utils.timezone import make_aware, now from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import mixins, serializers, status, viewsets from rest_framework import mixins, serializers, status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import ( from rest_framework.exceptions import (
@@ -51,16 +52,17 @@ from pretix.base.signals import (
from pretix.base.templatetags.money import money_filter from pretix.base.templatetags.money import money_filter
class OrderFilter(FilterSet): with scopes_disabled():
email = django_filters.CharFilter(field_name='email', lookup_expr='iexact') class OrderFilter(FilterSet):
code = django_filters.CharFilter(field_name='code', lookup_expr='iexact') email = django_filters.CharFilter(field_name='email', lookup_expr='iexact')
status = django_filters.CharFilter(field_name='status', lookup_expr='iexact') code = django_filters.CharFilter(field_name='code', lookup_expr='iexact')
modified_since = django_filters.IsoDateTimeFilter(field_name='last_modified', lookup_expr='gte') status = django_filters.CharFilter(field_name='status', lookup_expr='iexact')
created_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte') modified_since = django_filters.IsoDateTimeFilter(field_name='last_modified', lookup_expr='gte')
created_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
class Meta: class Meta:
model = Order model = Order
fields = ['code', 'status', 'email', 'locale', 'testmode', 'require_approval'] fields = ['code', 'status', 'email', 'locale', 'testmode', 'require_approval']
class OrderViewSet(viewsets.ModelViewSet): class OrderViewSet(viewsets.ModelViewSet):
@@ -531,23 +533,24 @@ class OrderViewSet(viewsets.ModelViewSet):
self.get_object().gracefully_delete(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth) self.get_object().gracefully_delete(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
class OrderPositionFilter(FilterSet): with scopes_disabled():
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact') class OrderPositionFilter(FilterSet):
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs') order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
attendee_name = django_filters.CharFilter(method='attendee_name_qs') has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
search = django_filters.CharFilter(method='search_qs') attendee_name = django_filters.CharFilter(method='attendee_name_qs')
search = django_filters.CharFilter(method='search_qs')
def search_qs(self, queryset, name, value): def search_qs(self, queryset, name, value):
return queryset.filter( return queryset.filter(
Q(secret__istartswith=value) Q(secret__istartswith=value)
| Q(attendee_name_cached__icontains=value) | Q(attendee_name_cached__icontains=value)
| Q(addon_to__attendee_name_cached__icontains=value) | Q(addon_to__attendee_name_cached__icontains=value)
| Q(attendee_email__icontains=value) | Q(attendee_email__icontains=value)
| Q(addon_to__attendee_email__icontains=value) | Q(addon_to__attendee_email__icontains=value)
| Q(order__code__istartswith=value) | Q(order__code__istartswith=value)
| Q(order__invoice_address__name_cached__icontains=value) | Q(order__invoice_address__name_cached__icontains=value)
| Q(order__email__icontains=value) | Q(order__email__icontains=value)
) )
def has_checkin_qs(self, queryset, name, value): def has_checkin_qs(self, queryset, name, value):
return queryset.filter(checkins__isnull=not value) return queryset.filter(checkins__isnull=not value)
@@ -572,7 +575,7 @@ class OrderPositionFilter(FilterSet):
class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewSet): class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPositionSerializer serializer_class = OrderPositionSerializer
queryset = OrderPosition.objects.none() queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, OrderingFilter) filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('order__datetime', 'positionid') ordering = ('order__datetime', 'positionid')
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',) ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
@@ -960,22 +963,23 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer.save() serializer.save()
class InvoiceFilter(FilterSet): with scopes_disabled():
refers = django_filters.CharFilter(method='refers_qs') class InvoiceFilter(FilterSet):
number = django_filters.CharFilter(method='nr_qs') refers = django_filters.CharFilter(method='refers_qs')
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact') number = django_filters.CharFilter(method='nr_qs')
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
def refers_qs(self, queryset, name, value): def refers_qs(self, queryset, name, value):
return queryset.annotate( return queryset.annotate(
refers_nr=Concat('refers__prefix', 'refers__invoice_no') refers_nr=Concat('refers__prefix', 'refers__invoice_no')
).filter(refers_nr__iexact=value) ).filter(refers_nr__iexact=value)
def nr_qs(self, queryset, name, value): def nr_qs(self, queryset, name, value):
return queryset.filter(nr__iexact=value) return queryset.filter(nr__iexact=value)
class Meta: class Meta:
model = Invoice model = Invoice
fields = ['order', 'number', 'is_cancellation', 'refers', 'locale'] fields = ['order', 'number', 'is_cancellation', 'refers', 'locale']
class RetryException(APIException): class RetryException(APIException):

View File

@@ -6,6 +6,7 @@ from django.utils.timezone import now
from django_filters.rest_framework import ( from django_filters.rest_framework import (
BooleanFilter, DjangoFilterBackend, FilterSet, BooleanFilter, DjangoFilterBackend, FilterSet,
) )
from django_scopes import scopes_disabled
from rest_framework import status, viewsets from rest_framework import status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
@@ -16,21 +17,22 @@ from pretix.api.serializers.voucher import VoucherSerializer
from pretix.base.models import Voucher from pretix.base.models import Voucher
class VoucherFilter(FilterSet): with scopes_disabled():
active = BooleanFilter(method='filter_active') class VoucherFilter(FilterSet):
active = BooleanFilter(method='filter_active')
class Meta: class Meta:
model = Voucher model = Voucher
fields = ['code', 'max_usages', 'redeemed', 'block_quota', 'allow_ignore_quota', fields = ['code', 'max_usages', 'redeemed', 'block_quota', 'allow_ignore_quota',
'price_mode', 'value', 'item', 'variation', 'quota', 'tag', 'subevent'] 'price_mode', 'value', 'item', 'variation', 'quota', 'tag', 'subevent']
def filter_active(self, queryset, name, value): def filter_active(self, queryset, name, value):
if value: if value:
return queryset.filter(Q(redeemed__lt=F('max_usages')) & return queryset.filter(Q(redeemed__lt=F('max_usages')) &
(Q(valid_until__isnull=True) | Q(valid_until__gt=now()))) (Q(valid_until__isnull=True) | Q(valid_until__gt=now())))
else: else:
return queryset.filter(Q(redeemed__gte=F('max_usages')) | return queryset.filter(Q(redeemed__gte=F('max_usages')) |
(Q(valid_until__isnull=False) & Q(valid_until__lte=now()))) (Q(valid_until__isnull=False) & Q(valid_until__lte=now())))
class VoucherViewSet(viewsets.ModelViewSet): class VoucherViewSet(viewsets.ModelViewSet):

View File

@@ -1,5 +1,6 @@
import django_filters import django_filters
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.exceptions import PermissionDenied, ValidationError
@@ -11,15 +12,16 @@ from pretix.base.models import WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException from pretix.base.models.waitinglist import WaitingListException
class WaitingListFilter(FilterSet): with scopes_disabled():
has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs') class WaitingListFilter(FilterSet):
has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs')
def has_voucher_qs(self, queryset, name, value): def has_voucher_qs(self, queryset, name, value):
return queryset.filter(voucher__isnull=not value) return queryset.filter(voucher__isnull=not value)
class Meta: class Meta:
model = WaitingListEntry model = WaitingListEntry
fields = ['item', 'variation', 'email', 'locale', 'has_voucher', 'subevent'] fields = ['item', 'variation', 'email', 'locale', 'has_voucher', 'subevent']
class WaitingListViewSet(viewsets.ModelViewSet): class WaitingListViewSet(viewsets.ModelViewSet):

View File

@@ -3,6 +3,7 @@ from django.db.models import Case, Count, F, OuterRef, Q, Subquery, When
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes import ScopedManager
from pretix.base.models import LoggedModel from pretix.base.models import LoggedModel
@@ -20,6 +21,8 @@ class CheckinList(LoggedModel):
'order have not been paid. This only works with pretixdesk ' 'order have not been paid. This only works with pretixdesk '
'0.3.0 or newer or pretixdroid 1.9 or newer.')) '0.3.0 or newer or pretixdroid 1.9 or newer.'))
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
ordering = ('subevent__date_from', 'name') ordering = ('subevent__date_from', 'name')
@@ -167,6 +170,8 @@ class Checkin(models.Model):
'pretixbase.CheckinList', related_name='checkins', on_delete=models.PROTECT, 'pretixbase.CheckinList', related_name='checkins', on_delete=models.PROTECT,
) )
objects = ScopedManager(organizer='position__order__event__organizer')
class Meta: class Meta:
unique_together = (('list', 'position'),) unique_together = (('list', 'position'),)

View File

@@ -4,6 +4,7 @@ from django.db import models
from django.db.models import Max from django.db.models import Max
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_scopes import ScopedManager
from pretix.base.models import LoggedModel from pretix.base.models import LoggedModel
@@ -71,6 +72,8 @@ class Device(LoggedModel):
null=True, blank=True null=True, blank=True
) )
objects = ScopedManager(organizer='organizer')
class Meta: class Meta:
unique_together = (('organizer', 'device_id'),) unique_together = (('organizer', 'device_id'),)

View File

@@ -2,6 +2,7 @@ import string
import uuid import uuid
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, time, timedelta from datetime import datetime, time, timedelta
from django_scopes import ScopedManager
from operator import attrgetter from operator import attrgetter
import pytz import pytz
@@ -336,6 +337,8 @@ class Event(EventMixin, LoggedModel):
default=False default=False
) )
objects = ScopedManager(organizer='organizer')
class Meta: class Meta:
verbose_name = _("Event") verbose_name = _("Event")
verbose_name_plural = _("Events") verbose_name_plural = _("Events")
@@ -875,6 +878,8 @@ class SubEvent(EventMixin, LoggedModel):
items = models.ManyToManyField('Item', through='SubEventItem') items = models.ManyToManyField('Item', through='SubEventItem')
variations = models.ManyToManyField('ItemVariation', through='SubEventItemVariation') variations = models.ManyToManyField('ItemVariation', through='SubEventItemVariation')
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
verbose_name = _("Date in event series") verbose_name = _("Date in event series")
verbose_name_plural = _("Dates in event series") verbose_name_plural = _("Dates in event series")

View File

@@ -9,6 +9,7 @@ from django.utils.crypto import get_random_string
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import pgettext from django.utils.translation import pgettext
from django_countries.fields import CountryField from django_countries.fields import CountryField
from django_scopes import ScopedManager
def invoice_filename(instance, filename: str) -> str: def invoice_filename(instance, filename: str) -> str:
@@ -107,6 +108,8 @@ class Invoice(models.Model):
file = models.FileField(null=True, blank=True, upload_to=invoice_filename, max_length=255) file = models.FileField(null=True, blank=True, upload_to=invoice_filename, max_length=255)
internal_reference = models.TextField(blank=True) internal_reference = models.TextField(blank=True)
objects = ScopedManager(organizer='event__organizer')
@staticmethod @staticmethod
def _to_numeric_invoice_number(number): def _to_numeric_invoice_number(number):
return '{:05d}'.format(int(number)) return '{:05d}'.format(int(number))

View File

@@ -3,6 +3,7 @@ import uuid
from collections import Counter from collections import Counter
from datetime import date, datetime, time from datetime import date, datetime, time
from decimal import Decimal, DecimalException from decimal import Decimal, DecimalException
from django_scopes import ScopedManager
from typing import Tuple from typing import Tuple
import dateutil.parser import dateutil.parser
@@ -155,28 +156,41 @@ class SubEventItemVariation(models.Model):
self.subevent.event.cache.clear() self.subevent.event.cache.clear()
class ItemQuerySet(models.QuerySet): def filter_available(qs, channel='web', voucher=None, allow_addons=False):
def filter_available(self, channel='web', voucher=None, allow_addons=False): q = (
q = ( # IMPORTANT: If this is updated, also update the ItemVariation query
# IMPORTANT: If this is updated, also update the ItemVariation query # in models/event.py: EventMixin.annotated()
# in models/event.py: EventMixin.annotated()
Q(active=True) Q(active=True)
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now())) & Q(Q(available_from__isnull=True) | Q(available_from__lte=now()))
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now())) & Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
& Q(sales_channels__contains=channel) & Q(require_bundling=False) & Q(sales_channels__contains=channel) & Q(require_bundling=False)
) )
if not allow_addons: if not allow_addons:
q &= Q(Q(category__isnull=True) | Q(category__is_addon=False)) q &= Q(Q(category__isnull=True) | Q(category__is_addon=False))
qs = self.filter(q) qs = qs.filter(q)
vouchq = Q(hide_without_voucher=False) vouchq = Q(hide_without_voucher=False)
if voucher: if voucher:
if voucher.item_id: if voucher.item_id:
vouchq |= Q(pk=voucher.item_id) vouchq |= Q(pk=voucher.item_id)
qs = qs.filter(pk=voucher.item_id) qs = qs.filter(pk=voucher.item_id)
elif voucher.quota_id: elif voucher.quota_id:
qs = qs.filter(quotas__in=[voucher.quota_id]) qs = qs.filter(quotas__in=[voucher.quota_id])
return qs.filter(vouchq) return qs.filter(vouchq)
class ItemQuerySet(models.QuerySet):
def filter_available(self, channel='web', voucher=None, allow_addons=False):
return filter_available(self)
class ItemQuerySetManager(ScopedManager(organizer='event__organizer').__class__):
def __init__(self):
super().__init__()
self._queryset_class = ItemQuerySet
def filter_available(self, channel='web', voucher=None, allow_addons=False):
return filter_available(self.get_queryset())
class Item(LoggedModel): class Item(LoggedModel):
@@ -226,7 +240,7 @@ class Item(LoggedModel):
:type sales_channels: bool :type sales_channels: bool
""" """
objects = ItemQuerySet.as_manager() objects = ItemQuerySetManager()
event = models.ForeignKey( event = models.ForeignKey(
Event, Event,
@@ -377,6 +391,7 @@ class Item(LoggedModel):
# !!! Attention: If you add new fields here, also add them to the copying code in # !!! Attention: If you add new fields here, also add them to the copying code in
# pretix/control/forms/item.py if applicable. # pretix/control/forms/item.py if applicable.
class Meta: class Meta:
verbose_name = _("Product") verbose_name = _("Product")
verbose_name_plural = _("Products") verbose_name_plural = _("Products")
@@ -591,6 +606,8 @@ class ItemVariation(models.Model):
'discounted one. This is just a cosmetic setting and will not actually impact pricing.') 'discounted one. This is just a cosmetic setting and will not actually impact pricing.')
) )
objects = ScopedManager(organizer='item__event__organizer')
class Meta: class Meta:
verbose_name = _("Product variation") verbose_name = _("Product variation")
verbose_name_plural = _("Product variations") verbose_name_plural = _("Product variations")
@@ -985,6 +1002,8 @@ class Question(LoggedModel):
) )
dependency_value = models.TextField(null=True, blank=True) dependency_value = models.TextField(null=True, blank=True)
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
verbose_name = _("Question") verbose_name = _("Question")
verbose_name_plural = _("Questions") verbose_name_plural = _("Questions")
@@ -1234,6 +1253,8 @@ class Quota(LoggedModel):
cached_availability_paid_orders = models.PositiveIntegerField(null=True, blank=True) cached_availability_paid_orders = models.PositiveIntegerField(null=True, blank=True)
cached_availability_time = models.DateTimeField(null=True, blank=True) cached_availability_time = models.DateTimeField(null=True, blank=True)
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
verbose_name = _("Quota") verbose_name = _("Quota")
verbose_name_plural = _("Quotas") verbose_name_plural = _("Quotas")

View File

@@ -6,6 +6,7 @@ import os
import string import string
from datetime import datetime, time, timedelta from datetime import datetime, time, timedelta
from decimal import Decimal from decimal import Decimal
from django_scopes import ScopedManager
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
import dateutil import dateutil
@@ -186,6 +187,8 @@ class Order(LockModel, LoggedModel):
verbose_name=_('E-mail address verified') verbose_name=_('E-mail address verified')
) )
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
verbose_name = _("Order") verbose_name = _("Order")
verbose_name_plural = _("Orders") verbose_name_plural = _("Orders")
@@ -822,6 +825,8 @@ class QuestionAnswer(models.Model):
max_length=255 max_length=255
) )
objects = ScopedManager(organizer='question__event__organizer')
@property @property
def backend_file_url(self): def backend_file_url(self):
if self.file: if self.file:
@@ -1145,6 +1150,8 @@ class OrderPayment(models.Model):
) )
migrated = models.BooleanField(default=False) migrated = models.BooleanField(default=False)
objects = ScopedManager(organizer='order__event__organizer')
class Meta: class Meta:
ordering = ('local_id',) ordering = ('local_id',)
@@ -1501,6 +1508,8 @@ class OrderRefund(models.Model):
null=True, blank=True null=True, blank=True
) )
objects = ScopedManager(organizer='order__event__organizer')
class Meta: class Meta:
ordering = ('local_id',) ordering = ('local_id',)
@@ -1562,7 +1571,7 @@ class OrderRefund(models.Model):
super().save(*args, **kwargs) super().save(*args, **kwargs)
class ActivePositionManager(models.Manager): class ActivePositionManager(ScopedManager(organizer='order__event__organizer').__class__):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(canceled=False) return super().get_queryset().filter(canceled=False)
@@ -1639,7 +1648,7 @@ class OrderFee(models.Model):
) )
canceled = models.BooleanField(default=False) canceled = models.BooleanField(default=False)
all = models.Manager() all = ScopedManager(organizer='order__event__organizer')
objects = ActivePositionManager() objects = ActivePositionManager()
@property @property
@@ -1744,7 +1753,7 @@ class OrderPosition(AbstractPosition):
) )
canceled = models.BooleanField(default=False) canceled = models.BooleanField(default=False)
all = models.Manager() all = ScopedManager(organizer='order__event__organizer')
objects = ActivePositionManager() objects = ActivePositionManager()
class Meta: class Meta:
@@ -1951,6 +1960,8 @@ class CartPosition(AbstractPosition):
) )
is_bundled = models.BooleanField(default=False) is_bundled = models.BooleanField(default=False)
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
verbose_name = _("Cart position") verbose_name = _("Cart position")
verbose_name_plural = _("Cart positions") verbose_name_plural = _("Cart positions")
@@ -2000,6 +2011,8 @@ class InvoiceAddress(models.Model):
blank=True blank=True
) )
objects = ScopedManager(organizer='order__event__organizer')
def save(self, **kwargs): def save(self, **kwargs):
if self.order: if self.order:
self.order.touch() self.order.touch()

View File

@@ -8,6 +8,7 @@ from django.db.models import Q
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes import ScopedManager
from ..decimal import round_decimal from ..decimal import round_decimal
from .base import LoggedModel from .base import LoggedModel
@@ -173,6 +174,8 @@ class Voucher(LoggedModel):
"convenience.") "convenience.")
) )
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
verbose_name = _("Voucher") verbose_name = _("Voucher")
verbose_name_plural = _("Vouchers") verbose_name_plural = _("Vouchers")

View File

@@ -4,6 +4,7 @@ from django.core.exceptions import ValidationError
from django.db import models, transaction from django.db import models, transaction
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes import ScopedManager
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Voucher from pretix.base.models import Voucher
@@ -67,6 +68,8 @@ class WaitingListEntry(LoggedModel):
) )
priority = models.IntegerField(default=0) priority = models.IntegerField(default=0)
objects = ScopedManager(organizer='event__organizer')
class Meta: class Meta:
verbose_name = _("Waiting list entry") verbose_name = _("Waiting list entry")
verbose_name_plural = _("Waiting list entries") verbose_name_plural = _("Waiting list entries")

View File

@@ -3,6 +3,7 @@ import hmac
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django_scopes import scopes_disabled
from .. import metrics from .. import metrics
@@ -15,6 +16,7 @@ def unauthed_response():
return response return response
@scopes_disabled()
def serve_metrics(request): def serve_metrics(request):
if not settings.METRICS_ENABLED: if not settings.METRICS_ENABLED:
return unauthed_response() return unauthed_response()

View File

@@ -1,6 +1,7 @@
from django import forms from django import forms
from django.urls import reverse from django.urls import reverse
from django.utils.translation import pgettext_lazy from django.utils.translation import pgettext_lazy
from django_scopes.forms import SafeModelMultipleChoiceField, SafeModelChoiceField
from pretix.base.models.checkin import CheckinList from pretix.base.models.checkin import CheckinList
from pretix.control.forms.widgets import Select2 from pretix.control.forms.widgets import Select2
@@ -44,3 +45,7 @@ class CheckinListForm(forms.ModelForm):
'data-inverse-dependency': '<[name$=all_products]' 'data-inverse-dependency': '<[name$=all_products]'
}), }),
} }
field_classes = {
'limit_products': SafeModelMultipleChoiceField,
'subevent': SafeModelChoiceField,
}

View File

@@ -6,6 +6,7 @@ from django.urls import reverse
from django.utils.translation import ( from django.utils.translation import (
pgettext_lazy, ugettext as __, ugettext_lazy as _, pgettext_lazy, ugettext as __, ugettext_lazy as _,
) )
from django_scopes.forms import SafeModelMultipleChoiceField, SafeModelChoiceField
from i18nfield.forms import I18nFormField, I18nTextarea from i18nfield.forms import I18nFormField, I18nTextarea
from pretix.base.channels import get_all_sales_channels from pretix.base.channels import get_all_sales_channels
@@ -94,6 +95,10 @@ class QuestionForm(I18nModelForm):
), ),
'dependency_value': forms.Select, 'dependency_value': forms.Select,
} }
field_classes = {
'items': SafeModelMultipleChoiceField,
'dependency_question': SafeModelChoiceField,
}
class QuestionOptionForm(I18nModelForm): class QuestionOptionForm(I18nModelForm):
@@ -159,6 +164,9 @@ class QuotaForm(I18nModelForm):
'size', 'size',
'subevent' 'subevent'
] ]
field_classes = {
'subevent': SafeModelChoiceField,
}
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
creating = not self.instance.pk creating = not self.instance.pk

View File

@@ -192,7 +192,7 @@ class OrderPositionAddForm(forms.Form):
label=_('Product') label=_('Product')
) )
addon_to = forms.ModelChoiceField( addon_to = forms.ModelChoiceField(
OrderPosition.objects.none(), OrderPosition.all.none(),
required=False, required=False,
label=_('Add-on to'), label=_('Add-on to'),
) )

View File

@@ -1,3 +1,4 @@
from django_scopes.forms import SafeModelChoiceField
from urllib.parse import urlparse from urllib.parse import urlparse
from django import forms from django import forms
@@ -149,6 +150,9 @@ class TeamForm(forms.ModelForm):
'data-inverse-dependency': '#id_all_events' 'data-inverse-dependency': '#id_all_events'
}), }),
} }
field_classes = {
'limit_events': SafeModelChoiceField
}
def clean(self): def clean(self):
data = super().clean() data = super().clean()
@@ -177,6 +181,9 @@ class DeviceForm(forms.ModelForm):
'data-inverse-dependency': '#id_all_events' 'data-inverse-dependency': '#id_all_events'
}), }),
} }
field_classes = {
'limit_events': SafeModelChoiceField
}
class OrganizerSettingsForm(SettingsForm): class OrganizerSettingsForm(SettingsForm):
@@ -307,3 +314,6 @@ class WebHookForm(forms.ModelForm):
'data-inverse-dependency': '#id_all_events' 'data-inverse-dependency': '#id_all_events'
}), }),
} }
field_classes = {
'limit_events': SafeModelChoiceField
}

View File

@@ -3,6 +3,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.urls import reverse from django.urls import reverse
from django.utils.translation import pgettext_lazy, ugettext_lazy as _ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes.forms import SafeModelChoiceField
from pretix.base.forms import I18nModelForm from pretix.base.forms import I18nModelForm
from pretix.base.models import Item, Voucher from pretix.base.models import Item, Voucher
@@ -35,6 +36,7 @@ class VoucherForm(I18nModelForm):
] ]
field_classes = { field_classes = {
'valid_until': SplitDateTimeField, 'valid_until': SplitDateTimeField,
'subevent': SafeModelChoiceField,
} }
widgets = { widgets = {
'valid_until': SplitDateTimePickerWidget(), 'valid_until': SplitDateTimePickerWidget(),
@@ -199,6 +201,7 @@ class VoucherBulkForm(VoucherForm):
] ]
field_classes = { field_classes = {
'valid_until': SplitDateTimeField, 'valid_until': SplitDateTimeField,
'subevent': SafeModelChoiceField,
} }
widgets = { widgets = {
'valid_until': SplitDateTimePickerWidget(), 'valid_until': SplitDateTimePickerWidget(),

View File

@@ -1,3 +1,4 @@
from django_scopes import scope
from urllib.parse import quote, urljoin, urlparse from urllib.parse import quote, urljoin, urlparse
from django.conf import settings from django.conf import settings
@@ -17,7 +18,7 @@ from pretix.helpers.security import (
) )
class PermissionMiddleware(MiddlewareMixin): class PermissionMiddleware:
""" """
This middleware enforces all requests to the control app to require login. This middleware enforces all requests to the control app to require login.
Additionally, it enforces all requests to "control:event." URLs Additionally, it enforces all requests to "control:event." URLs
@@ -34,6 +35,10 @@ class PermissionMiddleware(MiddlewareMixin):
"user.settings.notifications.off", "user.settings.notifications.off",
) )
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def _login_redirect(self, request): def _login_redirect(self, request):
# Taken from django/contrib/auth/decorators.py # Taken from django/contrib/auth/decorators.py
path = request.build_absolute_uri() path = request.build_absolute_uri()
@@ -52,19 +57,19 @@ class PermissionMiddleware(MiddlewareMixin):
return redirect_to_login( return redirect_to_login(
path, resolved_login_url, REDIRECT_FIELD_NAME) path, resolved_login_url, REDIRECT_FIELD_NAME)
def process_request(self, request): def __call__(self, request):
url = resolve(request.path_info) url = resolve(request.path_info)
url_name = url.url_name url_name = url.url_name
if not request.path.startswith(get_script_prefix() + 'control'): if not request.path.startswith(get_script_prefix() + 'control'):
# This middleware should only touch the /control subpath # This middleware should only touch the /control subpath
return return self.get_response(request)
if hasattr(request, 'organizer'): if hasattr(request, 'organizer'):
# If the user is on a organizer's subdomain, he should be redirected to pretix # If the user is on a organizer's subdomain, he should be redirected to pretix
return redirect(urljoin(settings.SITE_URL, request.get_full_path())) return redirect(urljoin(settings.SITE_URL, request.get_full_path()))
if url_name in self.EXCEPTIONS: if url_name in self.EXCEPTIONS:
return return self.get_response(request)
if not request.user.is_authenticated: if not request.user.is_authenticated:
return self._login_redirect(request) return self._login_redirect(request)
@@ -79,10 +84,11 @@ class PermissionMiddleware(MiddlewareMixin):
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path())) return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
if 'event' in url.kwargs and 'organizer' in url.kwargs: if 'event' in url.kwargs and 'organizer' in url.kwargs:
request.event = Event.objects.filter( with scope(organizer=None):
slug=url.kwargs['event'], request.event = Event.objects.filter(
organizer__slug=url.kwargs['organizer'], slug=url.kwargs['event'],
).select_related('organizer').first() organizer__slug=url.kwargs['organizer'],
).select_related('organizer').first()
if not request.event or not request.user.has_event_permission(request.event.organizer, request.event, if not request.event or not request.user.has_event_permission(request.event.organizer, request.event,
request=request): request=request):
raise Http404(_("The selected event was not found or you " raise Http404(_("The selected event was not found or you "
@@ -104,6 +110,9 @@ class PermissionMiddleware(MiddlewareMixin):
else: else:
request.orgapermset = request.user.get_organizer_permission_set(request.organizer) request.orgapermset = request.user.get_organizer_permission_set(request.organizer)
with scope(organizer=getattr(request, 'organizer', None)):
return self.get_response(request)
class AuditLogMiddleware: class AuditLogMiddleware:

View File

@@ -1,6 +1,7 @@
from django import forms from django import forms
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_scopes.forms import SafeModelMultipleChoiceField, SafeModelChoiceField
from pretix.control.forms.widgets import Select2 from pretix.control.forms.widgets import Select2
from pretix.plugins.pretixdroid.models import AppConfiguration from pretix.plugins.pretixdroid.models import AppConfiguration
@@ -16,6 +17,10 @@ class AppConfigurationForm(forms.ModelForm):
}), }),
'app': forms.RadioSelect 'app': forms.RadioSelect
} }
field_classes = {
'items': SafeModelMultipleChoiceField,
'list': SafeModelChoiceField,
}
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.event = kwargs.pop('event') self.event = kwargs.pop('event')

View File

@@ -1,25 +1,33 @@
from django.urls import resolve from django.urls import resolve
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from django_scopes import scope
from pretix.presale.signals import process_response from pretix.presale.signals import process_response
from .utils import _detect_event from .utils import _detect_event
class EventMiddleware(MiddlewareMixin): class EventMiddleware:
def process_request(self, request): def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def __call__(self, request):
url = resolve(request.path_info) url = resolve(request.path_info)
request._namespace = url.namespace request._namespace = url.namespace
if url.namespace != 'presale': if url.namespace != 'presale':
return return self.get_response(request)
if 'organizer' in url.kwargs or 'event' in url.kwargs: if 'organizer' in url.kwargs or 'event' in url.kwargs:
redirect = _detect_event(request, require_live=url.url_name != 'event.widget.productlist') redirect = _detect_event(request, require_live=url.url_name != 'event.widget.productlist')
if redirect: if redirect:
return redirect return redirect
def process_response(self, request, response): with scope(organizer=getattr(request, 'organizer', None)):
if hasattr(request, '_namespace') and request._namespace == 'presale' and hasattr(request, 'event'): response = self.get_response(request)
for receiver, r in process_response.send(request.event, request=request, response=response):
response = r if hasattr(request, '_namespace') and request._namespace == 'presale' and hasattr(request, 'event'):
for receiver, r in process_response.send(request.event, request=request, response=response):
response = r
return response return response

View File

@@ -1,4 +1,5 @@
import warnings import warnings
from django_scopes import scope
from importlib import import_module from importlib import import_module
from urllib.parse import urljoin from urllib.parse import urljoin
@@ -17,6 +18,7 @@ from pretix.presale.signals import process_request, process_response
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
@scope(organizer=None)
def _detect_event(request, require_live=True, require_plugin=None): def _detect_event(request, require_live=True, require_plugin=None):
if hasattr(request, '_event_detected'): if hasattr(request, '_event_detected'):
return return
@@ -151,10 +153,11 @@ def _event_view(function=None, require_live=True, require_plugin=None):
if ret: if ret:
return ret return ret
else: else:
response = func(request=request, *args, **kwargs) with scope(organizer=getattr(request, 'organizer', None)):
for receiver, r in process_response.send(request.event, request=request, response=response): response = func(request=request, *args, **kwargs)
response = r for receiver, r in process_response.send(request.event, request=request, response=response):
return response response = r
return response
for attrname in dir(func): for attrname in dir(func):
# Preserve flags like csrf_exempt # Preserve flags like csrf_exempt

View File

@@ -343,6 +343,7 @@ MIDDLEWARE = [
'pretix.base.middleware.LocaleMiddleware', 'pretix.base.middleware.LocaleMiddleware',
'pretix.base.middleware.SecurityMiddleware', 'pretix.base.middleware.SecurityMiddleware',
'pretix.presale.middleware.EventMiddleware', 'pretix.presale.middleware.EventMiddleware',
'pretix.api.middleware.ApiScopeMiddleware',
] ]
try: try:

View File

@@ -9,6 +9,7 @@ django-formset-js-improved==0.5.0.2
django-compressor==2.2.* django-compressor==2.2.*
django-hierarkey==1.0.*,>=1.0.3 django-hierarkey==1.0.*,>=1.0.3
django-filter==2.1.* django-filter==2.1.*
django-scopes==1.1.*
reportlab==3.5.* reportlab==3.5.*
PyPDF2==1.26.* PyPDF2==1.26.*
Pillow==5.* Pillow==5.*

View File

@@ -97,6 +97,7 @@ setup(
'django-compressor==2.2.*', 'django-compressor==2.2.*',
'django-hierarkey==1.0.*,>=1.0.2', 'django-hierarkey==1.0.*,>=1.0.2',
'django-filter==2.1.*', 'django-filter==2.1.*',
'django-scopes==1.1.*',
'reportlab==3.5.*', 'reportlab==3.5.*',
'Pillow==5.*', 'Pillow==5.*',
'PyPDF2==1.26.*', 'PyPDF2==1.26.*',

View File

@@ -1,7 +1,9 @@
from datetime import datetime from datetime import datetime
import pytest import pytest
from django.test import utils
from django.utils.timezone import now from django.utils.timezone import now
from django_scopes import scopes_disabled
from pytz import UTC from pytz import UTC
from rest_framework.test import APIClient from rest_framework.test import APIClient
@@ -144,3 +146,6 @@ def taxrule(event):
@pytest.fixture @pytest.fixture
def taxrule2(event2): def taxrule2(event2):
return event2.tax_rules.create(name="VAT", rate=25) return event2.tax_rules.create(name="VAT", rate=25)
utils.setup_databases = scopes_disabled()(utils.setup_databases)