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,6 +26,7 @@ from pretix.base.services.checkin import (
from pretix.helpers.database import FixedOrderBy from pretix.helpers.database import FixedOrderBy
with scopes_disabled():
class CheckinListFilter(FilterSet): class CheckinListFilter(FilterSet):
class Meta: class Meta:
model = CheckinList model = CheckinList
@@ -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,6 +20,7 @@ from pretix.base.models.event import SubEvent
from pretix.helpers.dicts import merge_dicts from pretix.helpers.dicts import merge_dicts
with scopes_disabled():
class EventFilter(FilterSet): class EventFilter(FilterSet):
is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs') is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs')
is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs') is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs')
@@ -182,6 +184,7 @@ class CloneEventViewSet(viewsets.ModelViewSet):
) )
with scopes_disabled():
class SubEventFilter(FilterSet): class SubEventFilter(FilterSet):
is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs') is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs')
is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs') is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs')

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,6 +23,7 @@ from pretix.base.models import (
from pretix.helpers.dicts import merge_dicts from pretix.helpers.dicts import merge_dicts
with scopes_disabled():
class ItemFilter(FilterSet): class ItemFilter(FilterSet):
tax_rate = django_filters.CharFilter(method='tax_rate_qs') tax_rate = django_filters.CharFilter(method='tax_rate_qs')
@@ -319,6 +321,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
super().perform_destroy(instance) super().perform_destroy(instance)
with scopes_disabled():
class QuestionFilter(FilterSet): class QuestionFilter(FilterSet):
class Meta: class Meta:
model = Question model = Question
@@ -418,6 +421,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
super().perform_destroy(instance) super().perform_destroy(instance)
with scopes_disabled():
class QuotaFilter(FilterSet): class QuotaFilter(FilterSet):
class Meta: class Meta:
model = Quota model = Quota

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,6 +52,7 @@ from pretix.base.signals import (
from pretix.base.templatetags.money import money_filter from pretix.base.templatetags.money import money_filter
with scopes_disabled():
class OrderFilter(FilterSet): class OrderFilter(FilterSet):
email = django_filters.CharFilter(field_name='email', lookup_expr='iexact') email = django_filters.CharFilter(field_name='email', lookup_expr='iexact')
code = django_filters.CharFilter(field_name='code', lookup_expr='iexact') code = django_filters.CharFilter(field_name='code', lookup_expr='iexact')
@@ -531,6 +533,7 @@ 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)
with scopes_disabled():
class OrderPositionFilter(FilterSet): class OrderPositionFilter(FilterSet):
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact') order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs') has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
@@ -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,6 +963,7 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer.save() serializer.save()
with scopes_disabled():
class InvoiceFilter(FilterSet): class InvoiceFilter(FilterSet):
refers = django_filters.CharFilter(method='refers_qs') refers = django_filters.CharFilter(method='refers_qs')
number = django_filters.CharFilter(method='nr_qs') number = django_filters.CharFilter(method='nr_qs')

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,6 +17,7 @@ from pretix.api.serializers.voucher import VoucherSerializer
from pretix.base.models import Voucher from pretix.base.models import Voucher
with scopes_disabled():
class VoucherFilter(FilterSet): class VoucherFilter(FilterSet):
active = BooleanFilter(method='filter_active') active = BooleanFilter(method='filter_active')

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,6 +12,7 @@ from pretix.base.models import WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException from pretix.base.models.waitinglist import WaitingListException
with scopes_disabled():
class WaitingListFilter(FilterSet): class WaitingListFilter(FilterSet):
has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs') has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs')

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,8 +156,7 @@ 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()
@@ -167,7 +167,7 @@ class ItemQuerySet(models.QuerySet):
) )
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:
@@ -179,6 +179,20 @@ class ItemQuerySet(models.QuerySet):
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):
""" """
An item is a thing which can be sold. It belongs to an event and may or may not belong to a category. An item is a thing which can be sold. It belongs to an event and may or may not belong to a category.
@@ -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,6 +84,7 @@ 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:
with scope(organizer=None):
request.event = Event.objects.filter( request.event = Event.objects.filter(
slug=url.kwargs['event'], slug=url.kwargs['event'],
organizer__slug=url.kwargs['organizer'], organizer__slug=url.kwargs['organizer'],
@@ -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)):
response = self.get_response(request)
if hasattr(request, '_namespace') and request._namespace == 'presale' and hasattr(request, 'event'): 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): for receiver, r in process_response.send(request.event, request=request, response=response):
response = r 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,6 +153,7 @@ def _event_view(function=None, require_live=True, require_plugin=None):
if ret: if ret:
return ret return ret
else: else:
with scope(organizer=getattr(request, 'organizer', None)):
response = func(request=request, *args, **kwargs) response = func(request=request, *args, **kwargs)
for receiver, r in process_response.send(request.event, request=request, response=response): for receiver, r in process_response.send(request.event, request=request, response=response):
response = r response = r

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)