mirror of
https://github.com/pretix/pretix.git
synced 2026-05-08 15:44:02 +00:00
Install django-scopes
This commit is contained in:
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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'),)
|
||||||
|
|
||||||
|
|||||||
@@ -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'),)
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.*
|
||||||
|
|||||||
@@ -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.*',
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user