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.base.models import Device, Event, User
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 (
SessionInvalid, SessionReauthRequired, assert_session_valid,
)
@@ -50,9 +50,6 @@ class EventPermission(BasePermission):
return False
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):
return False
if isinstance(perm_holder, User) and perm_holder.has_active_staff_session(request.session.session_key):

View File

@@ -1,4 +1,6 @@
import json
from django.urls import resolve
from django_scopes import scope
from hashlib import sha1
from django.conf import settings
@@ -8,6 +10,7 @@ from django.utils.timezone import now
from rest_framework import status
from pretix.api.models import ApiCall
from pretix.base.models import Organizer
class IdempotencyMiddleware:
@@ -89,3 +92,21 @@ class IdempotencyMiddleware:
for k, v in json.loads(call.response_headers).values():
r[k] = v
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.timezone import now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.fields import DateTimeField
@@ -25,10 +26,11 @@ from pretix.base.services.checkin import (
from pretix.helpers.database import FixedOrderBy
class CheckinListFilter(FilterSet):
class Meta:
model = CheckinList
fields = ['subevent']
with scopes_disabled():
class CheckinListFilter(FilterSet):
class Meta:
model = CheckinList
fields = ['subevent']
class CheckinListViewSet(viewsets.ModelViewSet):
@@ -154,7 +156,7 @@ class CheckinOrderPositionFilter(OrderPositionFilter):
class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = CheckinListOrderPositionSerializer
queryset = OrderPosition.objects.none()
queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, RichOrderingFilter)
ordering = ('attendee_name_cached', 'positionid')
ordering_fields = (

View File

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

View File

@@ -3,6 +3,7 @@ from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
@@ -22,18 +23,19 @@ from pretix.base.models import (
from pretix.helpers.dicts import merge_dicts
class ItemFilter(FilterSet):
tax_rate = django_filters.CharFilter(method='tax_rate_qs')
with scopes_disabled():
class ItemFilter(FilterSet):
tax_rate = django_filters.CharFilter(method='tax_rate_qs')
def tax_rate_qs(self, queryset, name, value):
if value in ("0", "None", "0.00"):
return queryset.filter(Q(tax_rule__isnull=True) | Q(tax_rule__rate=0))
else:
return queryset.filter(tax_rule__rate=value)
def tax_rate_qs(self, queryset, name, value):
if value in ("0", "None", "0.00"):
return queryset.filter(Q(tax_rule__isnull=True) | Q(tax_rule__rate=0))
else:
return queryset.filter(tax_rule__rate=value)
class Meta:
model = Item
fields = ['active', 'category', 'admission', 'tax_rate', 'free_price']
class Meta:
model = Item
fields = ['active', 'category', 'admission', 'tax_rate', 'free_price']
class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
@@ -319,10 +321,11 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
super().perform_destroy(instance)
class QuestionFilter(FilterSet):
class Meta:
model = Question
fields = ['ask_during_checkin', 'required', 'identifier']
with scopes_disabled():
class QuestionFilter(FilterSet):
class Meta:
model = Question
fields = ['ask_during_checkin', 'required', 'identifier']
class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
@@ -418,10 +421,11 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
super().perform_destroy(instance)
class QuotaFilter(FilterSet):
class Meta:
model = Quota
fields = ['subevent']
with scopes_disabled():
class QuotaFilter(FilterSet):
class Meta:
model = Quota
fields = ['subevent']
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.translation import ugettext as _
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.decorators import action
from rest_framework.exceptions import (
@@ -51,16 +52,17 @@ from pretix.base.signals import (
from pretix.base.templatetags.money import money_filter
class OrderFilter(FilterSet):
email = django_filters.CharFilter(field_name='email', lookup_expr='iexact')
code = django_filters.CharFilter(field_name='code', lookup_expr='iexact')
status = django_filters.CharFilter(field_name='status', lookup_expr='iexact')
modified_since = django_filters.IsoDateTimeFilter(field_name='last_modified', lookup_expr='gte')
created_since = django_filters.IsoDateTimeFilter(field_name='datetime', lookup_expr='gte')
with scopes_disabled():
class OrderFilter(FilterSet):
email = django_filters.CharFilter(field_name='email', lookup_expr='iexact')
code = django_filters.CharFilter(field_name='code', lookup_expr='iexact')
status = django_filters.CharFilter(field_name='status', lookup_expr='iexact')
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:
model = Order
fields = ['code', 'status', 'email', 'locale', 'testmode', 'require_approval']
class Meta:
model = Order
fields = ['code', 'status', 'email', 'locale', 'testmode', 'require_approval']
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)
class OrderPositionFilter(FilterSet):
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
attendee_name = django_filters.CharFilter(method='attendee_name_qs')
search = django_filters.CharFilter(method='search_qs')
with scopes_disabled():
class OrderPositionFilter(FilterSet):
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs')
attendee_name = django_filters.CharFilter(method='attendee_name_qs')
search = django_filters.CharFilter(method='search_qs')
def search_qs(self, queryset, name, value):
return queryset.filter(
Q(secret__istartswith=value)
| Q(attendee_name_cached__icontains=value)
| Q(addon_to__attendee_name_cached__icontains=value)
| Q(attendee_email__icontains=value)
| Q(addon_to__attendee_email__icontains=value)
| Q(order__code__istartswith=value)
| Q(order__invoice_address__name_cached__icontains=value)
| Q(order__email__icontains=value)
)
def search_qs(self, queryset, name, value):
return queryset.filter(
Q(secret__istartswith=value)
| Q(attendee_name_cached__icontains=value)
| Q(addon_to__attendee_name_cached__icontains=value)
| Q(attendee_email__icontains=value)
| Q(addon_to__attendee_email__icontains=value)
| Q(order__code__istartswith=value)
| Q(order__invoice_address__name_cached__icontains=value)
| Q(order__email__icontains=value)
)
def has_checkin_qs(self, queryset, name, value):
return queryset.filter(checkins__isnull=not value)
@@ -572,7 +575,7 @@ class OrderPositionFilter(FilterSet):
class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPositionSerializer
queryset = OrderPosition.objects.none()
queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
ordering = ('order__datetime', 'positionid')
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
@@ -960,22 +963,23 @@ class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
serializer.save()
class InvoiceFilter(FilterSet):
refers = django_filters.CharFilter(method='refers_qs')
number = django_filters.CharFilter(method='nr_qs')
order = django_filters.CharFilter(field_name='order', lookup_expr='code__iexact')
with scopes_disabled():
class InvoiceFilter(FilterSet):
refers = django_filters.CharFilter(method='refers_qs')
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):
return queryset.annotate(
refers_nr=Concat('refers__prefix', 'refers__invoice_no')
).filter(refers_nr__iexact=value)
def refers_qs(self, queryset, name, value):
return queryset.annotate(
refers_nr=Concat('refers__prefix', 'refers__invoice_no')
).filter(refers_nr__iexact=value)
def nr_qs(self, queryset, name, value):
return queryset.filter(nr__iexact=value)
def nr_qs(self, queryset, name, value):
return queryset.filter(nr__iexact=value)
class Meta:
model = Invoice
fields = ['order', 'number', 'is_cancellation', 'refers', 'locale']
class Meta:
model = Invoice
fields = ['order', 'number', 'is_cancellation', 'refers', 'locale']
class RetryException(APIException):

View File

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

View File

@@ -1,5 +1,6 @@
import django_filters
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
@@ -11,15 +12,16 @@ from pretix.base.models import WaitingListEntry
from pretix.base.models.waitinglist import WaitingListException
class WaitingListFilter(FilterSet):
has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs')
with scopes_disabled():
class WaitingListFilter(FilterSet):
has_voucher = django_filters.rest_framework.BooleanFilter(method='has_voucher_qs')
def has_voucher_qs(self, queryset, name, value):
return queryset.filter(voucher__isnull=not value)
def has_voucher_qs(self, queryset, name, value):
return queryset.filter(voucher__isnull=not value)
class Meta:
model = WaitingListEntry
fields = ['item', 'variation', 'email', 'locale', 'has_voucher', 'subevent']
class Meta:
model = WaitingListEntry
fields = ['item', 'variation', 'email', 'locale', 'has_voucher', 'subevent']
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.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django_scopes import ScopedManager
from pretix.base.models import LoggedModel
@@ -20,6 +21,8 @@ class CheckinList(LoggedModel):
'order have not been paid. This only works with pretixdesk '
'0.3.0 or newer or pretixdroid 1.9 or newer.'))
objects = ScopedManager(organizer='event__organizer')
class Meta:
ordering = ('subevent__date_from', 'name')
@@ -167,6 +170,8 @@ class Checkin(models.Model):
'pretixbase.CheckinList', related_name='checkins', on_delete=models.PROTECT,
)
objects = ScopedManager(organizer='position__order__event__organizer')
class Meta:
unique_together = (('list', 'position'),)

View File

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

View File

@@ -2,6 +2,7 @@ import string
import uuid
from collections import OrderedDict
from datetime import datetime, time, timedelta
from django_scopes import ScopedManager
from operator import attrgetter
import pytz
@@ -336,6 +337,8 @@ class Event(EventMixin, LoggedModel):
default=False
)
objects = ScopedManager(organizer='organizer')
class Meta:
verbose_name = _("Event")
verbose_name_plural = _("Events")
@@ -875,6 +878,8 @@ class SubEvent(EventMixin, LoggedModel):
items = models.ManyToManyField('Item', through='SubEventItem')
variations = models.ManyToManyField('ItemVariation', through='SubEventItemVariation')
objects = ScopedManager(organizer='event__organizer')
class Meta:
verbose_name = _("Date 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.translation import pgettext
from django_countries.fields import CountryField
from django_scopes import ScopedManager
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)
internal_reference = models.TextField(blank=True)
objects = ScopedManager(organizer='event__organizer')
@staticmethod
def _to_numeric_invoice_number(number):
return '{:05d}'.format(int(number))

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
from django import forms
from django.urls import reverse
from django.utils.translation import pgettext_lazy
from django_scopes.forms import SafeModelMultipleChoiceField, SafeModelChoiceField
from pretix.base.models.checkin import CheckinList
from pretix.control.forms.widgets import Select2
@@ -44,3 +45,7 @@ class CheckinListForm(forms.ModelForm):
'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 (
pgettext_lazy, ugettext as __, ugettext_lazy as _,
)
from django_scopes.forms import SafeModelMultipleChoiceField, SafeModelChoiceField
from i18nfield.forms import I18nFormField, I18nTextarea
from pretix.base.channels import get_all_sales_channels
@@ -94,6 +95,10 @@ class QuestionForm(I18nModelForm):
),
'dependency_value': forms.Select,
}
field_classes = {
'items': SafeModelMultipleChoiceField,
'dependency_question': SafeModelChoiceField,
}
class QuestionOptionForm(I18nModelForm):
@@ -159,6 +164,9 @@ class QuotaForm(I18nModelForm):
'size',
'subevent'
]
field_classes = {
'subevent': SafeModelChoiceField,
}
def save(self, *args, **kwargs):
creating = not self.instance.pk

View File

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

View File

@@ -1,3 +1,4 @@
from django_scopes.forms import SafeModelChoiceField
from urllib.parse import urlparse
from django import forms
@@ -149,6 +150,9 @@ class TeamForm(forms.ModelForm):
'data-inverse-dependency': '#id_all_events'
}),
}
field_classes = {
'limit_events': SafeModelChoiceField
}
def clean(self):
data = super().clean()
@@ -177,6 +181,9 @@ class DeviceForm(forms.ModelForm):
'data-inverse-dependency': '#id_all_events'
}),
}
field_classes = {
'limit_events': SafeModelChoiceField
}
class OrganizerSettingsForm(SettingsForm):
@@ -307,3 +314,6 @@ class WebHookForm(forms.ModelForm):
'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.urls import reverse
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.models import Item, Voucher
@@ -35,6 +36,7 @@ class VoucherForm(I18nModelForm):
]
field_classes = {
'valid_until': SplitDateTimeField,
'subevent': SafeModelChoiceField,
}
widgets = {
'valid_until': SplitDateTimePickerWidget(),
@@ -199,6 +201,7 @@ class VoucherBulkForm(VoucherForm):
]
field_classes = {
'valid_until': SplitDateTimeField,
'subevent': SafeModelChoiceField,
}
widgets = {
'valid_until': SplitDateTimePickerWidget(),

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import warnings
from django_scopes import scope
from importlib import import_module
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
@scope(organizer=None)
def _detect_event(request, require_live=True, require_plugin=None):
if hasattr(request, '_event_detected'):
return
@@ -151,10 +153,11 @@ def _event_view(function=None, require_live=True, require_plugin=None):
if ret:
return ret
else:
response = func(request=request, *args, **kwargs)
for receiver, r in process_response.send(request.event, request=request, response=response):
response = r
return response
with scope(organizer=getattr(request, 'organizer', None)):
response = func(request=request, *args, **kwargs)
for receiver, r in process_response.send(request.event, request=request, response=response):
response = r
return response
for attrname in dir(func):
# Preserve flags like csrf_exempt

View File

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