forked from CGM_Public/pretix_original
Integrate django-scopes (#1319)
* Install django-scopes * Fix tests.api * Update tasks and cronjobs * Fix remaining tests * Remove unused import * Fix tests after rebase * Disable scopes for get_Events_with_any_permission * Disable scopes for a management command
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
|
||||
@@ -12,7 +13,8 @@ class DeviceTokenAuthentication(TokenAuthentication):
|
||||
def authenticate_credentials(self, key):
|
||||
model = self.get_model()
|
||||
try:
|
||||
device = model.objects.select_related('organizer').get(api_token=key)
|
||||
with scopes_disabled():
|
||||
device = model.objects.select_related('organizer').get(api_token=key)
|
||||
except model.DoesNotExist:
|
||||
raise exceptions.AuthenticationFailed('Invalid token.')
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -4,10 +4,13 @@ from hashlib import sha1
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.urls import resolve
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope
|
||||
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)
|
||||
|
||||
@@ -2,6 +2,7 @@ from datetime import timedelta
|
||||
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.api.models import ApiCall, WebHookCall
|
||||
from pretix.base.signals import periodic_task
|
||||
@@ -17,10 +18,12 @@ instances.
|
||||
|
||||
|
||||
@receiver(periodic_task)
|
||||
@scopes_disabled()
|
||||
def cleanup_webhook_logs(sender, **kwargs):
|
||||
WebHookCall.objects.filter(datetime__lte=now() - timedelta(days=30)).delete()
|
||||
|
||||
|
||||
@receiver(periodic_task)
|
||||
@scopes_disabled()
|
||||
def cleanup_api_logs(sender, **kwargs):
|
||||
ApiCall.objects.filter(created__lte=now() - timedelta(hours=24)).delete()
|
||||
|
||||
@@ -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
|
||||
@@ -24,11 +25,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):
|
||||
@@ -146,15 +147,16 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
return Response(response)
|
||||
|
||||
|
||||
class CheckinOrderPositionFilter(OrderPositionFilter):
|
||||
with scopes_disabled():
|
||||
class CheckinOrderPositionFilter(OrderPositionFilter):
|
||||
|
||||
def has_checkin_qs(self, queryset, name, value):
|
||||
return queryset.filter(last_checked_in__isnull=not value)
|
||||
def has_checkin_qs(self, queryset, name, value):
|
||||
return queryset.filter(last_checked_in__isnull=not value)
|
||||
|
||||
|
||||
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 = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,51 +19,51 @@ from pretix.base.models import (
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
|
||||
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 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 +183,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):
|
||||
|
||||
@@ -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
|
||||
@@ -21,19 +22,19 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
|
||||
with scopes_disabled():
|
||||
class ItemFilter(FilterSet):
|
||||
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):
|
||||
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 +320,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 +420,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):
|
||||
|
||||
@@ -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 (
|
||||
@@ -50,17 +51,17 @@ from pretix.base.signals import (
|
||||
)
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
|
||||
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 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,48 +532,49 @@ 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)
|
||||
def has_checkin_qs(self, queryset, name, value):
|
||||
return queryset.filter(checkins__isnull=not value)
|
||||
|
||||
def attendee_name_qs(self, queryset, name, value):
|
||||
return queryset.filter(Q(attendee_name_cached__iexact=value) | Q(addon_to__attendee_name_cached__iexact=value))
|
||||
def attendee_name_qs(self, queryset, name, value):
|
||||
return queryset.filter(Q(attendee_name_cached__iexact=value) | Q(addon_to__attendee_name_cached__iexact=value))
|
||||
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = {
|
||||
'item': ['exact', 'in'],
|
||||
'variation': ['exact', 'in'],
|
||||
'secret': ['exact'],
|
||||
'order__status': ['exact', 'in'],
|
||||
'addon_to': ['exact', 'in'],
|
||||
'subevent': ['exact', 'in'],
|
||||
'pseudonymization_id': ['exact'],
|
||||
'voucher__code': ['exact'],
|
||||
'voucher': ['exact'],
|
||||
}
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = {
|
||||
'item': ['exact', 'in'],
|
||||
'variation': ['exact', 'in'],
|
||||
'secret': ['exact'],
|
||||
'order__status': ['exact', 'in'],
|
||||
'addon_to': ['exact', 'in'],
|
||||
'subevent': ['exact', 'in'],
|
||||
'pseudonymization_id': ['exact'],
|
||||
'voucher__code': ['exact'],
|
||||
'voucher': ['exact'],
|
||||
}
|
||||
|
||||
|
||||
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 +962,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):
|
||||
|
||||
@@ -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
|
||||
@@ -15,22 +16,22 @@ from rest_framework.response import Response
|
||||
from pretix.api.serializers.voucher import VoucherSerializer
|
||||
from pretix.base.models import Voucher
|
||||
|
||||
with scopes_disabled():
|
||||
class VoucherFilter(FilterSet):
|
||||
active = BooleanFilter(method='filter_active')
|
||||
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
@@ -10,16 +11,16 @@ from pretix.api.serializers.waitinglist import WaitingListSerializer
|
||||
from pretix.base.models import WaitingListEntry
|
||||
from pretix.base.models.waitinglist import WaitingListException
|
||||
|
||||
with scopes_disabled():
|
||||
class WaitingListFilter(FilterSet):
|
||||
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):
|
||||
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):
|
||||
|
||||
@@ -8,6 +8,7 @@ from celery.exceptions import MaxRetriesExceededError
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from requests import RequestException
|
||||
|
||||
from pretix.api.models import WebHook, WebHookCall, WebHookEventListener
|
||||
@@ -203,51 +204,52 @@ def notify_webhooks(logentry_id: int):
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=9)
|
||||
def send_webhook(self, logentry_id: int, action_type: str, webhook_id: int):
|
||||
# 9 retries with 2**(2*x) timing is roughly 72 hours
|
||||
logentry = LogEntry.all.get(id=logentry_id)
|
||||
webhook = WebHook.objects.get(id=webhook_id)
|
||||
with scopes_disabled():
|
||||
webhook = WebHook.objects.get(id=webhook_id)
|
||||
with scope(organizer=webhook.organizer):
|
||||
logentry = LogEntry.all.get(id=logentry_id)
|
||||
types = get_all_webhook_events()
|
||||
event_type = types.get(action_type)
|
||||
if not event_type or not webhook.enabled:
|
||||
return # Ignore, e.g. plugin not installed
|
||||
|
||||
types = get_all_webhook_events()
|
||||
event_type = types.get(action_type)
|
||||
if not event_type or not webhook.enabled:
|
||||
return # Ignore, e.g. plugin not installed
|
||||
payload = event_type.build_payload(logentry)
|
||||
t = time.time()
|
||||
|
||||
payload = event_type.build_payload(logentry)
|
||||
t = time.time()
|
||||
|
||||
try:
|
||||
try:
|
||||
resp = requests.post(
|
||||
webhook.target_url,
|
||||
json=payload,
|
||||
allow_redirects=False
|
||||
)
|
||||
WebHookCall.objects.create(
|
||||
webhook=webhook,
|
||||
action_type=logentry.action_type,
|
||||
target_url=webhook.target_url,
|
||||
is_retry=self.request.retries > 0,
|
||||
execution_time=time.time() - t,
|
||||
return_code=resp.status_code,
|
||||
payload=json.dumps(payload),
|
||||
response_body=resp.text[:1024 * 1024],
|
||||
success=200 <= resp.status_code <= 299
|
||||
)
|
||||
if resp.status_code == 410:
|
||||
webhook.enabled = False
|
||||
webhook.save()
|
||||
elif resp.status_code > 299:
|
||||
try:
|
||||
resp = requests.post(
|
||||
webhook.target_url,
|
||||
json=payload,
|
||||
allow_redirects=False
|
||||
)
|
||||
WebHookCall.objects.create(
|
||||
webhook=webhook,
|
||||
action_type=logentry.action_type,
|
||||
target_url=webhook.target_url,
|
||||
is_retry=self.request.retries > 0,
|
||||
execution_time=time.time() - t,
|
||||
return_code=resp.status_code,
|
||||
payload=json.dumps(payload),
|
||||
response_body=resp.text[:1024 * 1024],
|
||||
success=200 <= resp.status_code <= 299
|
||||
)
|
||||
if resp.status_code == 410:
|
||||
webhook.enabled = False
|
||||
webhook.save()
|
||||
elif resp.status_code > 299:
|
||||
raise self.retry(countdown=2 ** (self.request.retries * 2))
|
||||
except RequestException as e:
|
||||
WebHookCall.objects.create(
|
||||
webhook=webhook,
|
||||
action_type=logentry.action_type,
|
||||
target_url=webhook.target_url,
|
||||
is_retry=self.request.retries > 0,
|
||||
execution_time=time.time() - t,
|
||||
return_code=0,
|
||||
payload=json.dumps(payload),
|
||||
response_body=str(e)[:1024 * 1024]
|
||||
)
|
||||
raise self.retry(countdown=2 ** (self.request.retries * 2))
|
||||
except RequestException as e:
|
||||
WebHookCall.objects.create(
|
||||
webhook=webhook,
|
||||
action_type=logentry.action_type,
|
||||
target_url=webhook.target_url,
|
||||
is_retry=self.request.retries > 0,
|
||||
execution_time=time.time() - t,
|
||||
return_code=0,
|
||||
payload=json.dumps(payload),
|
||||
response_body=str(e)[:1024 * 1024]
|
||||
)
|
||||
raise self.retry(countdown=2 ** (self.request.retries * 2))
|
||||
except MaxRetriesExceededError:
|
||||
pass
|
||||
except MaxRetriesExceededError:
|
||||
pass
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_otp.models import Device
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
@@ -283,6 +284,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
return True
|
||||
return False
|
||||
|
||||
@scopes_disabled()
|
||||
def get_events_with_any_permission(self, request=None):
|
||||
"""
|
||||
Returns a queryset of events the user has any permissions to.
|
||||
@@ -300,6 +302,7 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
| Q(id__in=self.teams.values_list('limit_events__id', flat=True))
|
||||
)
|
||||
|
||||
@scopes_disabled()
|
||||
def get_events_with_permission(self, permission, request=None):
|
||||
"""
|
||||
Returns a queryset of events the user has a specific permissions to.
|
||||
|
||||
@@ -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'),)
|
||||
|
||||
|
||||
@@ -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'),)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.utils.crypto import get_random_string
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_scopes import ScopedManager
|
||||
from i18nfield.fields import I18nCharField, I18nTextField
|
||||
|
||||
from pretix.base.models.base import LoggedModel
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import is_naive, make_aware, now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_countries.fields import Country
|
||||
from django_scopes import ScopedManager
|
||||
from i18nfield.fields import I18nCharField, I18nTextField
|
||||
|
||||
from pretix.base.models import fields
|
||||
@@ -155,28 +156,41 @@ class SubEventItemVariation(models.Model):
|
||||
self.subevent.event.cache.clear()
|
||||
|
||||
|
||||
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 = 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)
|
||||
|
||||
|
||||
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()
|
||||
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)
|
||||
return filter_available(self, channel, voucher, allow_addons)
|
||||
|
||||
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 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(), channel, voucher, allow_addons)
|
||||
|
||||
|
||||
class Item(LoggedModel):
|
||||
@@ -226,7 +240,7 @@ class Item(LoggedModel):
|
||||
:type sales_channels: bool
|
||||
"""
|
||||
|
||||
objects = ItemQuerySet.as_manager()
|
||||
objects = ItemQuerySetManager()
|
||||
|
||||
event = models.ForeignKey(
|
||||
Event,
|
||||
@@ -591,6 +605,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 +1001,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 +1252,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")
|
||||
|
||||
@@ -26,6 +26,7 @@ from django.utils.functional import cached_property
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_countries.fields import Country, CountryField
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from jsonfallback.fields import FallbackJSONField
|
||||
|
||||
@@ -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")
|
||||
@@ -231,6 +234,7 @@ class Order(LockModel, LoggedModel):
|
||||
return self.all_fees(manager='objects')
|
||||
|
||||
@cached_property
|
||||
@scopes_disabled()
|
||||
def count_positions(self):
|
||||
if hasattr(self, 'pcnt'):
|
||||
return self.pcnt or 0
|
||||
@@ -254,6 +258,7 @@ class Order(LockModel, LoggedModel):
|
||||
return None
|
||||
|
||||
@property
|
||||
@scopes_disabled()
|
||||
def payment_refund_sum(self):
|
||||
payment_sum = self.payments.filter(
|
||||
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED)
|
||||
@@ -265,6 +270,7 @@ class Order(LockModel, LoggedModel):
|
||||
return payment_sum - refund_sum
|
||||
|
||||
@property
|
||||
@scopes_disabled()
|
||||
def pending_sum(self):
|
||||
total = self.total
|
||||
if self.status == Order.STATUS_CANCELED:
|
||||
@@ -439,6 +445,7 @@ class Order(LockModel, LoggedModel):
|
||||
return round_decimal(fee, self.event.currency)
|
||||
|
||||
@property
|
||||
@scopes_disabled()
|
||||
def user_cancel_allowed(self) -> bool:
|
||||
"""
|
||||
Returns whether or not this order can be canceled by the user.
|
||||
@@ -822,6 +829,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 +1154,8 @@ class OrderPayment(models.Model):
|
||||
)
|
||||
migrated = models.BooleanField(default=False)
|
||||
|
||||
objects = ScopedManager(organizer='order__event__organizer')
|
||||
|
||||
class Meta:
|
||||
ordering = ('local_id',)
|
||||
|
||||
@@ -1501,6 +1512,8 @@ class OrderRefund(models.Model):
|
||||
null=True, blank=True
|
||||
)
|
||||
|
||||
objects = ScopedManager(organizer='order__event__organizer')
|
||||
|
||||
class Meta:
|
||||
ordering = ('local_id',)
|
||||
|
||||
@@ -1562,7 +1575,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 +1652,7 @@ class OrderFee(models.Model):
|
||||
)
|
||||
canceled = models.BooleanField(default=False)
|
||||
|
||||
all = models.Manager()
|
||||
all = ScopedManager(organizer='order__event__organizer')
|
||||
objects = ActivePositionManager()
|
||||
|
||||
@property
|
||||
@@ -1744,7 +1757,7 @@ class OrderPosition(AbstractPosition):
|
||||
)
|
||||
canceled = models.BooleanField(default=False)
|
||||
|
||||
all = models.Manager()
|
||||
all = ScopedManager(organizer='order__event__organizer')
|
||||
objects = ActivePositionManager()
|
||||
|
||||
class Meta:
|
||||
@@ -1951,6 +1964,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 +2015,8 @@ class InvoiceAddress(models.Model):
|
||||
blank=True
|
||||
)
|
||||
|
||||
objects = ScopedManager(organizer='order__event__organizer')
|
||||
|
||||
def save(self, **kwargs):
|
||||
if self.order:
|
||||
self.order.touch()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import pgettext_lazy, ugettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
@@ -23,7 +24,7 @@ from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.services.checkin import _save_answers
|
||||
from pretix.base.services.locking import LockTimeoutException, NoLockManager
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.celery_app import app
|
||||
@@ -902,7 +903,7 @@ def get_fees(event, request, total, invoice_address, provider):
|
||||
return fees
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, locale='en',
|
||||
invoice_address: int=None, widget_data=None, sales_channel='web') -> None:
|
||||
"""
|
||||
@@ -913,12 +914,11 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
|
||||
:raises CartError: On any error that occured
|
||||
"""
|
||||
with language(locale):
|
||||
event = Event.objects.get(id=event)
|
||||
|
||||
ia = False
|
||||
if invoice_address:
|
||||
try:
|
||||
ia = InvoiceAddress.objects.get(pk=invoice_address)
|
||||
with scopes_disabled():
|
||||
ia = InvoiceAddress.objects.get(pk=invoice_address)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -934,8 +934,8 @@ def add_items_to_cart(self, event: int, items: List[dict], cart_id: str=None, lo
|
||||
raise CartError(error_messages['busy'])
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def remove_cart_position(self, event: int, position: int, cart_id: str=None, locale='en') -> None:
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def remove_cart_position(self, event: Event, position: int, cart_id: str=None, locale='en') -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
@@ -943,7 +943,6 @@ def remove_cart_position(self, event: int, position: int, cart_id: str=None, loc
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
event = Event.objects.get(id=event)
|
||||
try:
|
||||
try:
|
||||
cm = CartManager(event=event, cart_id=cart_id)
|
||||
@@ -955,15 +954,14 @@ def remove_cart_position(self, event: int, position: int, cart_id: str=None, loc
|
||||
raise CartError(error_messages['busy'])
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def clear_cart(self, event: int, cart_id: str=None, locale='en') -> None:
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def clear_cart(self, event: Event, cart_id: str=None, locale='en') -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
:param event: The event ID in question
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
event = Event.objects.get(id=event)
|
||||
try:
|
||||
try:
|
||||
cm = CartManager(event=event, cart_id=cart_id)
|
||||
@@ -975,8 +973,8 @@ def clear_cart(self, event: int, cart_id: str=None, locale='en') -> None:
|
||||
raise CartError(error_messages['busy'])
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def set_cart_addons(self, event: int, addons: List[dict], cart_id: str=None, locale='en',
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||
def set_cart_addons(self, event: Event, addons: List[dict], cart_id: str=None, locale='en',
|
||||
invoice_address: int=None, sales_channel='web') -> None:
|
||||
"""
|
||||
Removes a list of items from a user's cart.
|
||||
@@ -985,12 +983,11 @@ def set_cart_addons(self, event: int, addons: List[dict], cart_id: str=None, loc
|
||||
:param session: Session ID of a guest
|
||||
"""
|
||||
with language(locale):
|
||||
event = Event.objects.get(id=event)
|
||||
|
||||
ia = False
|
||||
if invoice_address:
|
||||
try:
|
||||
ia = InvoiceAddress.objects.get(pk=invoice_address)
|
||||
with scopes_disabled():
|
||||
ia = InvoiceAddress.objects.get(pk=invoice_address)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
try:
|
||||
|
||||
@@ -2,6 +2,7 @@ from datetime import timedelta
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import CachedCombinedTicket, CachedTicket
|
||||
|
||||
@@ -10,6 +11,7 @@ from ..signals import periodic_task
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def clean_cart_positions(sender, **kwargs):
|
||||
for cp in CartPosition.objects.filter(expires__lt=now() - timedelta(days=14), addon_to__isnull=False):
|
||||
cp.delete()
|
||||
@@ -20,12 +22,14 @@ def clean_cart_positions(sender, **kwargs):
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def clean_cached_files(sender, **kwargs):
|
||||
for cf in CachedFile.objects.filter(expires__isnull=False, expires__lt=now()):
|
||||
cf.delete()
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def clean_cached_tickets(sender, **kwargs):
|
||||
for cf in CachedTicket.objects.filter(created__lte=now() - timedelta(days=30)):
|
||||
cf.delete()
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.utils.translation import ugettext
|
||||
|
||||
from pretix.base.i18n import LazyLocaleException, language
|
||||
from pretix.base.models import CachedFile, Event, cachedfile_name
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.base.signals import register_data_exporters
|
||||
from pretix.celery_app import app
|
||||
|
||||
@@ -15,9 +15,8 @@ class ExportError(LazyLocaleException):
|
||||
pass
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, throws=(ExportError,))
|
||||
def export(event: str, fileid: str, provider: str, form_data: Dict[str, Any]) -> None:
|
||||
event = Event.objects.get(id=event)
|
||||
@app.task(base=ProfiledEventTask, throws=(ExportError,))
|
||||
def export(event: Event, fileid: str, provider: str, form_data: Dict[str, Any]) -> None:
|
||||
file = CachedFile.objects.get(id=fileid)
|
||||
with language(event.settings.locale), override(event.settings.timezone):
|
||||
responses = register_data_exporters.send(event)
|
||||
|
||||
@@ -15,6 +15,7 @@ from django.utils import timezone
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import pgettext, ugettext as _
|
||||
from django_countries.fields import Country
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.i18n import language
|
||||
@@ -244,16 +245,18 @@ def generate_invoice(order: Order, trigger_pdf=True):
|
||||
|
||||
@app.task(base=TransactionAwareTask)
|
||||
def invoice_pdf_task(invoice: int):
|
||||
i = Invoice.objects.get(pk=invoice)
|
||||
if i.shredded:
|
||||
return None
|
||||
if i.file:
|
||||
i.file.delete()
|
||||
with language(i.locale):
|
||||
fname, ftype, fcontent = i.event.invoice_renderer.generate(i)
|
||||
i.file.save(fname, ContentFile(fcontent))
|
||||
i.save()
|
||||
return i.file.name
|
||||
with scopes_disabled():
|
||||
i = Invoice.objects.get(pk=invoice)
|
||||
with scope(organizer=i.order.event.organizer):
|
||||
if i.shredded:
|
||||
return None
|
||||
if i.file:
|
||||
i.file.delete()
|
||||
with language(i.locale):
|
||||
fname, ftype, fcontent = i.event.invoice_renderer.generate(i)
|
||||
i.file.save(fname, ContentFile(fcontent))
|
||||
i.save()
|
||||
return i.file.name
|
||||
|
||||
|
||||
def invoice_qualified(order: Order):
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.conf import settings
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import get_template
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.email import ClassicMailRenderer
|
||||
@@ -234,83 +235,87 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
||||
pass
|
||||
|
||||
if event:
|
||||
event = Event.objects.get(id=event)
|
||||
with scopes_disabled():
|
||||
event = Event.objects.get(id=event)
|
||||
backend = event.get_mail_backend()
|
||||
cm = lambda: scope(organizer=event.organizer) # noqa
|
||||
else:
|
||||
backend = get_connection(fail_silently=False)
|
||||
cm = lambda: scopes_disabled() # noqa
|
||||
|
||||
if event:
|
||||
if order:
|
||||
try:
|
||||
order = event.orders.get(pk=order)
|
||||
except Order.DoesNotExist:
|
||||
order = None
|
||||
else:
|
||||
if position:
|
||||
try:
|
||||
position = order.positions.get(pk=position)
|
||||
except OrderPosition.DoesNotExist:
|
||||
attach_tickets = False
|
||||
if attach_tickets:
|
||||
args = []
|
||||
attach_size = 0
|
||||
for name, ct in get_tickets_for_order(order, base_position=position):
|
||||
content = ct.file.read()
|
||||
args.append((name, content, ct.type))
|
||||
attach_size += len(content)
|
||||
with cm():
|
||||
if event:
|
||||
if order:
|
||||
try:
|
||||
order = event.orders.get(pk=order)
|
||||
except Order.DoesNotExist:
|
||||
order = None
|
||||
else:
|
||||
if position:
|
||||
try:
|
||||
position = order.positions.get(pk=position)
|
||||
except OrderPosition.DoesNotExist:
|
||||
attach_tickets = False
|
||||
if attach_tickets:
|
||||
args = []
|
||||
attach_size = 0
|
||||
for name, ct in get_tickets_for_order(order, base_position=position):
|
||||
content = ct.file.read()
|
||||
args.append((name, content, ct.type))
|
||||
attach_size += len(content)
|
||||
|
||||
if attach_size < 4 * 1024 * 1024:
|
||||
# Do not attach more than 4MB, it will bounce way to often.
|
||||
for a in args:
|
||||
try:
|
||||
email.attach(*a)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
order.log_action(
|
||||
'pretix.event.order.email.attachments.skipped',
|
||||
data={
|
||||
'subject': 'Attachments skipped',
|
||||
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
|
||||
'recipient': '',
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
if attach_size < 4 * 1024 * 1024:
|
||||
# Do not attach more than 4MB, it will bounce way to often.
|
||||
for a in args:
|
||||
try:
|
||||
email.attach(*a)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
order.log_action(
|
||||
'pretix.event.order.email.attachments.skipped',
|
||||
data={
|
||||
'subject': 'Attachments skipped',
|
||||
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
|
||||
'recipient': '',
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
|
||||
email = email_filter.send_chained(event, 'message', message=email, order=order)
|
||||
email = email_filter.send_chained(event, 'message', message=email, order=order)
|
||||
|
||||
try:
|
||||
backend.send_messages([email])
|
||||
except smtplib.SMTPResponseException as e:
|
||||
if e.smtp_code in (101, 111, 421, 422, 431, 442, 447, 452):
|
||||
self.retry(max_retries=5, countdown=2 ** (self.request.retries * 2))
|
||||
logger.exception('Error sending email')
|
||||
try:
|
||||
backend.send_messages([email])
|
||||
except smtplib.SMTPResponseException as e:
|
||||
if e.smtp_code in (101, 111, 421, 422, 431, 442, 447, 452):
|
||||
self.retry(max_retries=5, countdown=2 ** (self.request.retries * 2))
|
||||
logger.exception('Error sending email')
|
||||
|
||||
if order:
|
||||
order.log_action(
|
||||
'pretix.event.order.email.error',
|
||||
data={
|
||||
'subject': 'SMTP code {}'.format(e.smtp_code),
|
||||
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
|
||||
'recipient': '',
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
if order:
|
||||
order.log_action(
|
||||
'pretix.event.order.email.error',
|
||||
data={
|
||||
'subject': 'SMTP code {}'.format(e.smtp_code),
|
||||
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
|
||||
'recipient': '',
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
except Exception as e:
|
||||
if order:
|
||||
order.log_action(
|
||||
'pretix.event.order.email.error',
|
||||
data={
|
||||
'subject': 'Internal error',
|
||||
'message': str(e),
|
||||
'recipient': '',
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
logger.exception('Error sending email')
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
except Exception as e:
|
||||
if order:
|
||||
order.log_action(
|
||||
'pretix.event.order.email.error',
|
||||
data={
|
||||
'subject': 'Internal error',
|
||||
'message': str(e),
|
||||
'recipient': '',
|
||||
'invoices': [],
|
||||
}
|
||||
)
|
||||
logger.exception('Error sending email')
|
||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
||||
|
||||
|
||||
def mail_send(*args, **kwargs):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.conf import settings
|
||||
from django.template.loader import get_template
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from inlinestyler.utils import inline_css
|
||||
|
||||
from pretix.base.i18n import language
|
||||
@@ -12,6 +13,7 @@ from pretix.helpers.urls import build_absolute_uri
|
||||
|
||||
|
||||
@app.task(base=TransactionAwareTask)
|
||||
@scopes_disabled()
|
||||
def notify(logentry_id: int):
|
||||
logentry = LogEntry.all.get(id=logentry_id)
|
||||
if not logentry.event:
|
||||
@@ -66,17 +68,22 @@ def notify(logentry_id: int):
|
||||
@app.task(base=ProfiledTask)
|
||||
def send_notification(logentry_id: int, action_type: str, user_id: int, method: str):
|
||||
logentry = LogEntry.all.get(id=logentry_id)
|
||||
user = User.objects.get(id=user_id)
|
||||
types = get_all_notification_types(logentry.event)
|
||||
notification_type = types.get(action_type)
|
||||
if not notification_type:
|
||||
return # Ignore, e.g. plugin not active for this event
|
||||
if logentry.event:
|
||||
sm = lambda: scope(organizer=logentry.event.organizer) # noqa
|
||||
else:
|
||||
sm = lambda: scopes_disabled() # noqa
|
||||
with sm():
|
||||
user = User.objects.get(id=user_id)
|
||||
types = get_all_notification_types(logentry.event)
|
||||
notification_type = types.get(action_type)
|
||||
if not notification_type:
|
||||
return # Ignore, e.g. plugin not active for this event
|
||||
|
||||
with language(user.locale):
|
||||
notification = notification_type.build_notification(logentry)
|
||||
with language(user.locale):
|
||||
notification = notification_type.build_notification(logentry)
|
||||
|
||||
if method == "mail":
|
||||
send_notification_mail(notification, user)
|
||||
if method == "mail":
|
||||
send_notification_mail(notification, user)
|
||||
|
||||
|
||||
def send_notification_mail(notification: Notification, user: User):
|
||||
|
||||
@@ -16,6 +16,7 @@ from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.api.models import OAuthApplication
|
||||
from pretix.base.i18n import (
|
||||
@@ -42,7 +43,7 @@ from pretix.base.services.invoices import (
|
||||
from pretix.base.services.locking import LockTimeoutException, NoLockManager
|
||||
from pretix.base.services.mail import SendMailException
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import ProfiledEventTask, ProfiledTask
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import (
|
||||
allow_ticket_download, order_approved, order_canceled, order_changed,
|
||||
@@ -715,10 +716,8 @@ def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosi
|
||||
logger.exception('Order received email could not be sent to attendee')
|
||||
|
||||
|
||||
def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
def _perform_order(event: Event, payment_provider: str, position_ids: List[str],
|
||||
email: str, locale: str, address: int, meta_info: dict=None, sales_channel: str='web'):
|
||||
|
||||
event = Event.objects.get(id=event)
|
||||
if payment_provider:
|
||||
pprov = event.get_payment_providers().get(payment_provider)
|
||||
if not pprov:
|
||||
@@ -732,7 +731,8 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
addr = None
|
||||
if address is not None:
|
||||
try:
|
||||
addr = InvoiceAddress.objects.get(pk=address)
|
||||
with scopes_disabled():
|
||||
addr = InvoiceAddress.objects.get(pk=address)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
|
||||
@@ -804,6 +804,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def expire_orders(sender, **kwargs):
|
||||
eventcache = {}
|
||||
|
||||
@@ -818,6 +819,7 @@ def expire_orders(sender, **kwargs):
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def send_expiry_warnings(sender, **kwargs):
|
||||
eventcache = {}
|
||||
today = now().replace(hour=0, minute=0, second=0)
|
||||
@@ -875,6 +877,7 @@ def send_expiry_warnings(sender, **kwargs):
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def send_download_reminders(sender, **kwargs):
|
||||
today = now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
@@ -1497,8 +1500,8 @@ class OrderChangeManager:
|
||||
return pprov
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||
def perform_order(self, event: str, payment_provider: str, positions: List[str],
|
||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||
def perform_order(self, event: Event, payment_provider: str, positions: List[str],
|
||||
email: str=None, locale: str=None, address: int=None, meta_info: dict=None,
|
||||
sales_channel: str='web'):
|
||||
with language(locale):
|
||||
@@ -1513,6 +1516,7 @@ def perform_order(self, event: str, payment_provider: str, positions: List[str],
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||
@scopes_disabled()
|
||||
def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_token=None, oauth_application=None,
|
||||
device=None, cancellation_fee=None, try_auto_refund=False):
|
||||
try:
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.conf import settings
|
||||
from django.db.models import Max, Q
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, LogEntry
|
||||
from pretix.celery_app import app
|
||||
@@ -17,6 +18,7 @@ def build_all_quota_caches(sender, **kwargs):
|
||||
|
||||
|
||||
@app.task
|
||||
@scopes_disabled()
|
||||
def refresh_quota_caches():
|
||||
# Active events
|
||||
active = LogEntry.objects.using(settings.DATABASE_REPLICA).filter(
|
||||
|
||||
@@ -11,14 +11,13 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import CachedFile, Event, cachedfile_name
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.base.shredder import ShredError
|
||||
from pretix.celery_app import app
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def export(event: str, shredders: List[str]) -> None:
|
||||
event = Event.objects.get(id=event)
|
||||
@app.task(base=ProfiledEventTask)
|
||||
def export(event: Event, shredders: List[str]) -> None:
|
||||
known_shredders = event.get_data_shredders()
|
||||
|
||||
with NamedTemporaryFile() as rawfile:
|
||||
@@ -63,9 +62,8 @@ def export(event: str, shredders: List[str]) -> None:
|
||||
return cf.pk
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask, throws=(ShredError,))
|
||||
def shred(event: str, fileid: str, confirm_code: str) -> None:
|
||||
event = Event.objects.get(id=event)
|
||||
@app.task(base=ProfiledEventTask, throws=(ShredError,))
|
||||
def shred(event: Event, fileid: str, confirm_code: str) -> None:
|
||||
known_shredders = event.get_data_shredders()
|
||||
try:
|
||||
cf = CachedFile.objects.get(pk=fileid)
|
||||
|
||||
@@ -14,10 +14,12 @@ import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
from pretix.base.metrics import (
|
||||
pretix_task_duration_seconds, pretix_task_runs_total,
|
||||
)
|
||||
from pretix.base.models import Event
|
||||
from pretix.celery_app import app
|
||||
|
||||
|
||||
@@ -61,6 +63,35 @@ class ProfiledTask(app.Task):
|
||||
return super().on_success(retval, task_id, args, kwargs)
|
||||
|
||||
|
||||
class EventTask(app.Task):
|
||||
def __call__(self, *args, **kwargs):
|
||||
if 'event_id' in kwargs:
|
||||
event_id = kwargs.get('event_id')
|
||||
with scopes_disabled():
|
||||
event = Event.objects.select_related('organizer').get(pk=event_id)
|
||||
del kwargs['event_id']
|
||||
kwargs['event'] = event
|
||||
elif 'event' in kwargs:
|
||||
event_id = kwargs.get('event')
|
||||
with scopes_disabled():
|
||||
event = Event.objects.select_related('organizer').get(pk=event_id)
|
||||
kwargs['event'] = event
|
||||
else:
|
||||
args = list(args)
|
||||
event_id = args[0]
|
||||
with scopes_disabled():
|
||||
event = Event.objects.select_related('organizer').get(pk=event_id)
|
||||
args[0] = event
|
||||
|
||||
with scope(organizer=event.organizer):
|
||||
ret = super().__call__(*args, **kwargs)
|
||||
return ret
|
||||
|
||||
|
||||
class ProfiledEventTask(ProfiledTask, EventTask):
|
||||
pass
|
||||
|
||||
|
||||
class TransactionAwareTask(ProfiledTask):
|
||||
"""
|
||||
Task class which is aware of django db transactions and only executes tasks
|
||||
|
||||
@@ -4,13 +4,14 @@ import os
|
||||
from django.core.files.base import ContentFile
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, Order,
|
||||
OrderPosition,
|
||||
)
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import EventTask, ProfiledTask
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import allow_ticket_download, register_ticket_outputs
|
||||
from pretix.celery_app import app
|
||||
@@ -57,10 +58,11 @@ def generate_order(order: int, provider: str):
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def generate(model: str, pk: int, provider: str):
|
||||
if model == 'order':
|
||||
return generate_order(pk, provider)
|
||||
elif model == 'orderposition':
|
||||
return generate_orderposition(pk, provider)
|
||||
with scopes_disabled():
|
||||
if model == 'order':
|
||||
return generate_order(pk, provider)
|
||||
elif model == 'orderposition':
|
||||
return generate_orderposition(pk, provider)
|
||||
|
||||
|
||||
class DummyRollbackException(Exception):
|
||||
@@ -165,9 +167,8 @@ def get_tickets_for_order(order, base_position=None):
|
||||
return tickets
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def invalidate_cache(event: int, item: int=None, provider: str=None, order: int=None, **kwargs):
|
||||
event = Event.objects.get(id=event)
|
||||
@app.task(base=EventTask)
|
||||
def invalidate_cache(event: Event, item: int=None, provider: str=None, order: int=None, **kwargs):
|
||||
qs = CachedTicket.objects.filter(order_position__order__event=event)
|
||||
qsc = CachedCombinedTicket.objects.filter(order__event=event)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import requests
|
||||
from django.dispatch import receiver
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _, ugettext_noop
|
||||
from django_scopes import scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix import __version__
|
||||
@@ -29,6 +30,7 @@ def run_update_check(sender, **kwargs):
|
||||
|
||||
|
||||
@app.task
|
||||
@scopes_disabled()
|
||||
def update_check():
|
||||
gs = GlobalSettingsObject()
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import sys
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, User, WaitingListEntry
|
||||
from pretix.base.models.waitinglist import WaitingListException
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import EventTask
|
||||
from pretix.base.signals import periodic_task
|
||||
from pretix.celery_app import app
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def assign_automatically(event_id: int, user_id: int=None, subevent_id: int=None):
|
||||
event = Event.objects.get(id=event_id)
|
||||
@app.task(base=EventTask)
|
||||
def assign_automatically(event: Event, user_id: int=None, subevent_id: int=None):
|
||||
if user_id:
|
||||
user = User.objects.get(id=user_id)
|
||||
else:
|
||||
@@ -69,6 +69,7 @@ def assign_automatically(event_id: int, user_id: int=None, subevent_id: int=None
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
@scopes_disabled()
|
||||
def process_waitinglist(sender, **kwargs):
|
||||
qs = Event.objects.filter(
|
||||
live=True
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.urls import Resolver404, get_script_prefix, resolve
|
||||
from django.utils.translation import get_language
|
||||
from django_scopes import scope
|
||||
|
||||
from pretix.base.models.auth import StaffSession
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
@@ -53,10 +54,11 @@ def contextprocessor(request):
|
||||
ctx['has_domain'] = request.event.organizer.domains.exists()
|
||||
|
||||
if not request.event.testmode:
|
||||
complain_testmode_orders = request.event.cache.get('complain_testmode_orders')
|
||||
if complain_testmode_orders is None:
|
||||
complain_testmode_orders = request.event.orders.filter(testmode=True).exists()
|
||||
request.event.cache.set('complain_testmode_orders', complain_testmode_orders, 30)
|
||||
with scope(organizer=request.organizer):
|
||||
complain_testmode_orders = request.event.cache.get('complain_testmode_orders')
|
||||
if complain_testmode_orders is None:
|
||||
complain_testmode_orders = request.event.orders.filter(testmode=True).exists()
|
||||
request.event.cache.set('complain_testmode_orders', complain_testmode_orders, 30)
|
||||
ctx['complain_testmode_orders'] = complain_testmode_orders
|
||||
else:
|
||||
ctx['complain_testmode_orders'] = False
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import pgettext_lazy
|
||||
from django_scopes.forms import (
|
||||
SafeModelChoiceField, SafeModelMultipleChoiceField,
|
||||
)
|
||||
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.control.forms.widgets import Select2
|
||||
@@ -44,3 +47,7 @@ class CheckinListForm(forms.ModelForm):
|
||||
'data-inverse-dependency': '<[name$=all_products]'
|
||||
}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_products': SafeModelMultipleChoiceField,
|
||||
'subevent': SafeModelChoiceField,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ from django.urls import reverse
|
||||
from django.utils.translation import (
|
||||
pgettext_lazy, ugettext as __, ugettext_lazy as _,
|
||||
)
|
||||
from django_scopes.forms import (
|
||||
SafeModelChoiceField, SafeModelMultipleChoiceField,
|
||||
)
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
|
||||
from pretix.base.channels import get_all_sales_channels
|
||||
@@ -94,6 +97,10 @@ class QuestionForm(I18nModelForm):
|
||||
),
|
||||
'dependency_value': forms.Select,
|
||||
}
|
||||
field_classes = {
|
||||
'items': SafeModelMultipleChoiceField,
|
||||
'dependency_question': SafeModelChoiceField,
|
||||
}
|
||||
|
||||
|
||||
class QuestionOptionForm(I18nModelForm):
|
||||
@@ -159,6 +166,9 @@ class QuotaForm(I18nModelForm):
|
||||
'size',
|
||||
'subevent'
|
||||
]
|
||||
field_classes = {
|
||||
'subevent': SafeModelChoiceField,
|
||||
}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
creating = not self.instance.pk
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django_scopes.forms import SafeModelMultipleChoiceField
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
|
||||
from pretix.api.models import WebHook
|
||||
@@ -149,6 +150,9 @@ class TeamForm(forms.ModelForm):
|
||||
'data-inverse-dependency': '#id_all_events'
|
||||
}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_events': SafeModelMultipleChoiceField
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
data = super().clean()
|
||||
@@ -177,6 +181,9 @@ class DeviceForm(forms.ModelForm):
|
||||
'data-inverse-dependency': '#id_all_events'
|
||||
}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_events': SafeModelMultipleChoiceField
|
||||
}
|
||||
|
||||
|
||||
class OrganizerSettingsForm(SettingsForm):
|
||||
@@ -307,3 +314,6 @@ class WebHookForm(forms.ModelForm):
|
||||
'data-inverse-dependency': '#id_all_events'
|
||||
}),
|
||||
}
|
||||
field_classes = {
|
||||
'limit_events': SafeModelMultipleChoiceField
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -4,10 +4,11 @@ from django.conf import settings
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME, logout
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect, resolve_url
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import get_script_prefix, resolve, reverse
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_scopes import scope
|
||||
from hijack.templatetags.hijack_tags import is_hijacked
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
@@ -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,12 @@ class PermissionMiddleware(MiddlewareMixin):
|
||||
else:
|
||||
request.orgapermset = request.user.get_organizer_permission_set(request.organizer)
|
||||
|
||||
with scope(organizer=getattr(request, 'organizer', None)):
|
||||
r = self.get_response(request)
|
||||
if isinstance(r, TemplateResponse):
|
||||
r = r.render()
|
||||
return r
|
||||
|
||||
|
||||
class AuditLogMiddleware:
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from pretix.base.models import (
|
||||
CachedFile, Event, OrderPosition, cachedfile_name,
|
||||
)
|
||||
from pretix.base.services.orders import OrderError
|
||||
from pretix.base.services.tasks import EventTask
|
||||
from pretix.celery_app import app
|
||||
|
||||
from .exporters import render_pdf
|
||||
@@ -14,8 +15,8 @@ from .exporters import render_pdf
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.task(throws=(OrderError,))
|
||||
def badges_create_pdf(fileid: int, event: int, positions: List[int]) -> int:
|
||||
@app.task(base=EventTask, throws=(OrderError,))
|
||||
def badges_create_pdf(event: int, fileid: int, positions: List[int]) -> int:
|
||||
file = CachedFile.objects.get(id=fileid)
|
||||
event = Event.objects.get(id=event)
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View):
|
||||
else:
|
||||
positions = [p.pk for p in order.positions.all()]
|
||||
return self.do(
|
||||
str(cf.id),
|
||||
self.request.event.pk,
|
||||
str(cf.id),
|
||||
positions,
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import ugettext, ugettext_noop
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
@@ -194,51 +195,53 @@ def _get_unknown_transactions(job: BankImportJob, data: list, event: Event=None,
|
||||
@app.task(base=TransactionAwareTask, bind=True, max_retries=5, default_retry_delay=1)
|
||||
def process_banktransfers(self, job: int, data: list) -> None:
|
||||
with language("en"): # We'll translate error messages at display time
|
||||
job = BankImportJob.objects.get(pk=job)
|
||||
job.state = BankImportJob.STATE_RUNNING
|
||||
job.save()
|
||||
prefixes = []
|
||||
with scopes_disabled():
|
||||
job = BankImportJob.objects.get(pk=job)
|
||||
with scope(organizer=job.organizer or job.event.organizer):
|
||||
job.state = BankImportJob.STATE_RUNNING
|
||||
job.save()
|
||||
prefixes = []
|
||||
|
||||
try:
|
||||
# Delete left-over transactions from a failed run before so they can reimported
|
||||
BankTransaction.objects.filter(state=BankTransaction.STATE_UNCHECKED, **job.owner_kwargs).delete()
|
||||
|
||||
transactions = _get_unknown_transactions(job, data, **job.owner_kwargs)
|
||||
|
||||
code_len = settings.ENTROPY['order_code']
|
||||
if job.event:
|
||||
pattern = re.compile(job.event.slug.upper() + r"[ \-_]*([A-Z0-9]{%s})" % code_len)
|
||||
else:
|
||||
if not prefixes:
|
||||
prefixes = [e.slug.upper().replace(".", r"\.").replace("-", r"[\- ]*")
|
||||
for e in job.organizer.events.all()]
|
||||
pattern = re.compile("(%s)[ \\-_]*([A-Z0-9]{%s})" % ("|".join(prefixes), code_len))
|
||||
|
||||
for trans in transactions:
|
||||
match = pattern.search(trans.reference.replace(" ", "").replace("\n", "").upper())
|
||||
|
||||
if match:
|
||||
if job.event:
|
||||
code = match.group(1)
|
||||
_handle_transaction(trans, code, event=job.event)
|
||||
else:
|
||||
slug = match.group(1)
|
||||
code = match.group(2)
|
||||
_handle_transaction(trans, code, organizer=job.organizer, slug=slug)
|
||||
else:
|
||||
trans.state = BankTransaction.STATE_NOMATCH
|
||||
trans.save()
|
||||
except LockTimeoutException:
|
||||
try:
|
||||
self.retry()
|
||||
except MaxRetriesExceededError:
|
||||
logger.exception('Maximum number of retries exceeded for task.')
|
||||
# Delete left-over transactions from a failed run before so they can reimported
|
||||
BankTransaction.objects.filter(state=BankTransaction.STATE_UNCHECKED, **job.owner_kwargs).delete()
|
||||
|
||||
transactions = _get_unknown_transactions(job, data, **job.owner_kwargs)
|
||||
|
||||
code_len = settings.ENTROPY['order_code']
|
||||
if job.event:
|
||||
pattern = re.compile(job.event.slug.upper() + r"[ \-_]*([A-Z0-9]{%s})" % code_len)
|
||||
else:
|
||||
if not prefixes:
|
||||
prefixes = [e.slug.upper().replace(".", r"\.").replace("-", r"[\- ]*")
|
||||
for e in job.organizer.events.all()]
|
||||
pattern = re.compile("(%s)[ \\-_]*([A-Z0-9]{%s})" % ("|".join(prefixes), code_len))
|
||||
|
||||
for trans in transactions:
|
||||
match = pattern.search(trans.reference.replace(" ", "").replace("\n", "").upper())
|
||||
|
||||
if match:
|
||||
if job.event:
|
||||
code = match.group(1)
|
||||
_handle_transaction(trans, code, event=job.event)
|
||||
else:
|
||||
slug = match.group(1)
|
||||
code = match.group(2)
|
||||
_handle_transaction(trans, code, organizer=job.organizer, slug=slug)
|
||||
else:
|
||||
trans.state = BankTransaction.STATE_NOMATCH
|
||||
trans.save()
|
||||
except LockTimeoutException:
|
||||
try:
|
||||
self.retry()
|
||||
except MaxRetriesExceededError:
|
||||
logger.exception('Maximum number of retries exceeded for task.')
|
||||
job.state = BankImportJob.STATE_ERROR
|
||||
job.save()
|
||||
except Exception as e:
|
||||
job.state = BankImportJob.STATE_ERROR
|
||||
job.save()
|
||||
except Exception as e:
|
||||
job.state = BankImportJob.STATE_ERROR
|
||||
job.save()
|
||||
raise e
|
||||
else:
|
||||
job.state = BankImportJob.STATE_COMPLETED
|
||||
job.save()
|
||||
raise e
|
||||
else:
|
||||
job.state = BankImportJob.STATE_COMPLETED
|
||||
job.save()
|
||||
|
||||
@@ -13,6 +13,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from django_scopes import scopes_disabled
|
||||
from paypalrestsdk.openid_connect import Tokeninfo
|
||||
|
||||
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
||||
@@ -133,6 +134,7 @@ def abort(request, *args, **kwargs):
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
@scopes_disabled()
|
||||
def webhook(request, *args, **kwargs):
|
||||
event_body = request.body.decode('utf-8').strip()
|
||||
event_json = json.loads(event_body)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from django import forms
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_scopes.forms import (
|
||||
SafeModelChoiceField, SafeModelMultipleChoiceField,
|
||||
)
|
||||
|
||||
from pretix.control.forms.widgets import Select2
|
||||
from pretix.plugins.pretixdroid.models import AppConfiguration
|
||||
@@ -16,6 +19,10 @@ class AppConfigurationForm(forms.ModelForm):
|
||||
}),
|
||||
'app': forms.RadioSelect
|
||||
}
|
||||
field_classes = {
|
||||
'items': SafeModelMultipleChoiceField,
|
||||
'list': SafeModelChoiceField,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.event = kwargs.pop('event')
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import TemplateView, View
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
from pretix.base.models import Checkin, Event, Order, OrderPosition
|
||||
from pretix.base.models.event import SubEvent
|
||||
@@ -124,34 +125,35 @@ class ConfigView(EventPermissionRequiredMixin, TemplateView):
|
||||
class ApiView(View):
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request, **kwargs):
|
||||
try:
|
||||
self.event = Event.objects.get(
|
||||
slug=self.kwargs['event'],
|
||||
organizer__slug=self.kwargs['organizer']
|
||||
)
|
||||
except Event.DoesNotExist:
|
||||
return HttpResponseNotFound('Unknown event')
|
||||
with scopes_disabled():
|
||||
try:
|
||||
self.event = Event.objects.get(
|
||||
slug=self.kwargs['event'],
|
||||
organizer__slug=self.kwargs['organizer']
|
||||
)
|
||||
except Event.DoesNotExist:
|
||||
return HttpResponseNotFound('Unknown event')
|
||||
with scope(organizer=self.event.organizer):
|
||||
try:
|
||||
self.config = self.event.appconfiguration_set.get(key=request.GET.get("key", "-unset-"))
|
||||
except AppConfiguration.DoesNotExist:
|
||||
return HttpResponseForbidden('Invalid key')
|
||||
|
||||
try:
|
||||
self.config = self.event.appconfiguration_set.get(key=request.GET.get("key", "-unset-"))
|
||||
except AppConfiguration.DoesNotExist:
|
||||
return HttpResponseForbidden('Invalid key')
|
||||
|
||||
self.subevent = None
|
||||
if self.event.has_subevents:
|
||||
if self.config.list.subevent:
|
||||
self.subevent = self.config.list.subevent
|
||||
if 'subevent' in kwargs and kwargs['subevent'] != str(self.subevent.pk):
|
||||
return HttpResponseForbidden('Invalid subevent selected.')
|
||||
elif 'subevent' in kwargs:
|
||||
self.subevent = get_object_or_404(SubEvent, event=self.event, pk=kwargs['subevent'])
|
||||
self.subevent = None
|
||||
if self.event.has_subevents:
|
||||
if self.config.list.subevent:
|
||||
self.subevent = self.config.list.subevent
|
||||
if 'subevent' in kwargs and kwargs['subevent'] != str(self.subevent.pk):
|
||||
return HttpResponseForbidden('Invalid subevent selected.')
|
||||
elif 'subevent' in kwargs:
|
||||
self.subevent = get_object_or_404(SubEvent, event=self.event, pk=kwargs['subevent'])
|
||||
else:
|
||||
return HttpResponseForbidden('No subevent selected.')
|
||||
else:
|
||||
return HttpResponseForbidden('No subevent selected.')
|
||||
else:
|
||||
if 'subevent' in kwargs:
|
||||
return HttpResponseForbidden('Subevents not enabled.')
|
||||
if 'subevent' in kwargs:
|
||||
return HttpResponseForbidden('Subevents not enabled.')
|
||||
|
||||
return super().dispatch(request, **kwargs)
|
||||
return super().dispatch(request, **kwargs)
|
||||
|
||||
|
||||
class ApiRedeemView(ApiView):
|
||||
|
||||
@@ -5,15 +5,14 @@ from i18nfield.strings import LazyI18nString
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Event, InvoiceAddress, Order, User
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.celery_app import app
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def send_mails(event: int, user: int, subject: dict, message: dict, orders: list, items: list, recipients: str) -> None:
|
||||
@app.task(base=ProfiledEventTask)
|
||||
def send_mails(event: Event, user: int, subject: dict, message: dict, orders: list, items: list, recipients: str) -> None:
|
||||
failures = []
|
||||
event = Event.objects.get(pk=event)
|
||||
user = User.objects.get(pk=user) if user else None
|
||||
orders = Order.objects.filter(pk__in=orders, event=event)
|
||||
subject = LazyI18nString(subject)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import stripe
|
||||
from django.core.management.base import BaseCommand
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
@@ -8,6 +9,7 @@ from pretix.base.settings import GlobalSettingsObject
|
||||
class Command(BaseCommand):
|
||||
help = "Detect country for Stripe Connect accounts connected with pretix 2.0 (required for payment request buttons)"
|
||||
|
||||
@scopes_disabled()
|
||||
def handle(self, *args, **options):
|
||||
cache = {}
|
||||
gs = GlobalSettingsObject()
|
||||
|
||||
@@ -5,6 +5,7 @@ import stripe
|
||||
from django.conf import settings
|
||||
|
||||
from pretix.base.models import Event
|
||||
from pretix.base.services.tasks import EventTask
|
||||
from pretix.celery_app import app
|
||||
from pretix.multidomain.urlreverse import get_domain
|
||||
from pretix.plugins.stripe.models import RegisteredApplePayDomain
|
||||
@@ -27,7 +28,7 @@ def get_stripe_account_key(prov):
|
||||
return prov.settings.publishable_key
|
||||
|
||||
|
||||
@app.task(max_retries=5, default_retry_delay=1)
|
||||
@app.task(base=EventTask, max_retries=5, default_retry_delay=1)
|
||||
def stripe_verify_domain(event_id, domain):
|
||||
from pretix.plugins.stripe.payment import StripeCC
|
||||
event = Event.objects.get(pk=event_id)
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.views import View
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event, Order, OrderPayment, Quota
|
||||
from pretix.base.payment import PaymentException
|
||||
@@ -140,6 +141,7 @@ def oauth_return(request, *args, **kwargs):
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
@scopes_disabled()
|
||||
def webhook(request, *args, **kwargs):
|
||||
event_json = json.loads(request.body.decode('utf-8'))
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.utils.translation import (
|
||||
get_language, pgettext_lazy, ugettext_lazy as _,
|
||||
)
|
||||
from django.views.generic.base import TemplateResponseMixin
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Order
|
||||
from pretix.base.models.orders import InvoiceAddress, OrderPayment
|
||||
@@ -114,7 +115,10 @@ class BaseCheckoutFlowStep:
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress()
|
||||
else:
|
||||
try:
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
|
||||
with scopes_disabled():
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress.objects.get(
|
||||
pk=iapk, order__isnull=True
|
||||
)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress()
|
||||
return self.request._checkout_flow_invoice_address
|
||||
|
||||
@@ -4,6 +4,7 @@ from django.conf import settings
|
||||
from django.core.files.base import ContentFile, File
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.management.base import BaseCommand
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import Event_SettingsStore, Organizer_SettingsStore
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
@@ -15,6 +16,7 @@ from ...style import regenerate_css, regenerate_organizer_css
|
||||
class Command(BaseCommand):
|
||||
help = "Re-generate all custom stylesheets and scripts"
|
||||
|
||||
@scopes_disabled()
|
||||
def handle(self, *args, **options):
|
||||
for es in Organizer_SettingsStore.objects.filter(key="presale_css_file"):
|
||||
regenerate_organizer_css.apply_async(args=(es.object_id,))
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
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
|
||||
|
||||
@@ -11,9 +11,10 @@ from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
from django.dispatch import Signal
|
||||
from django.templatetags.static import static as _static
|
||||
from django_scopes import scope
|
||||
|
||||
from pretix.base.models import Event, Event_SettingsStore, Organizer
|
||||
from pretix.base.services.tasks import ProfiledTask
|
||||
from pretix.base.services.tasks import ProfiledEventTask, ProfiledTask
|
||||
from pretix.celery_app import app
|
||||
from pretix.multidomain.urlreverse import get_domain
|
||||
from pretix.presale.signals import sass_postamble, sass_preamble
|
||||
@@ -78,10 +79,8 @@ def compile_scss(object, file="main.scss", fonts=True):
|
||||
return css, checksum
|
||||
|
||||
|
||||
@app.task(base=ProfiledTask)
|
||||
def regenerate_css(event_id: int):
|
||||
event = Event.objects.select_related('organizer').get(pk=event_id)
|
||||
|
||||
@app.task(base=ProfiledEventTask)
|
||||
def regenerate_css(event):
|
||||
# main.scss
|
||||
css, checksum = compile_scss(event)
|
||||
fname = 'pub/{}/{}/presale.{}.css'.format(event.organizer.slug, event.slug, checksum[:16])
|
||||
@@ -105,28 +104,29 @@ def regenerate_css(event_id: int):
|
||||
def regenerate_organizer_css(organizer_id: int):
|
||||
organizer = Organizer.objects.get(pk=organizer_id)
|
||||
|
||||
# main.scss
|
||||
css, checksum = compile_scss(organizer)
|
||||
fname = 'pub/{}/presale.{}.css'.format(organizer.slug, checksum[:16])
|
||||
if organizer.settings.get('presale_css_checksum', '') != checksum:
|
||||
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
|
||||
organizer.settings.set('presale_css_file', newname)
|
||||
organizer.settings.set('presale_css_checksum', checksum)
|
||||
with scope(organizer=organizer):
|
||||
# main.scss
|
||||
css, checksum = compile_scss(organizer)
|
||||
fname = 'pub/{}/presale.{}.css'.format(organizer.slug, checksum[:16])
|
||||
if organizer.settings.get('presale_css_checksum', '') != checksum:
|
||||
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
|
||||
organizer.settings.set('presale_css_file', newname)
|
||||
organizer.settings.set('presale_css_checksum', checksum)
|
||||
|
||||
# widget.scss
|
||||
css, checksum = compile_scss(organizer, file='widget.scss', fonts=False)
|
||||
fname = 'pub/{}/widget.{}.css'.format(organizer.slug, checksum[:16])
|
||||
if organizer.settings.get('presale_widget_css_checksum', '') != checksum:
|
||||
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
|
||||
organizer.settings.set('presale_widget_css_file', newname)
|
||||
organizer.settings.set('presale_widget_css_checksum', checksum)
|
||||
# widget.scss
|
||||
css, checksum = compile_scss(organizer, file='widget.scss', fonts=False)
|
||||
fname = 'pub/{}/widget.{}.css'.format(organizer.slug, checksum[:16])
|
||||
if organizer.settings.get('presale_widget_css_checksum', '') != checksum:
|
||||
newname = default_storage.save(fname, ContentFile(css.encode('utf-8')))
|
||||
organizer.settings.set('presale_widget_css_file', newname)
|
||||
organizer.settings.set('presale_widget_css_checksum', checksum)
|
||||
|
||||
non_inherited_events = set(Event_SettingsStore.objects.filter(
|
||||
object__organizer=organizer, key__in=affected_keys
|
||||
).values_list('object_id', flat=True))
|
||||
for event in organizer.events.all():
|
||||
if event.pk not in non_inherited_events:
|
||||
regenerate_css.apply_async(args=(event.pk,))
|
||||
non_inherited_events = set(Event_SettingsStore.objects.filter(
|
||||
object__organizer=organizer, key__in=affected_keys
|
||||
).values_list('object_id', flat=True))
|
||||
for event in organizer.events.all():
|
||||
if event.pk not in non_inherited_events:
|
||||
regenerate_css.apply_async(args=(event.pk,))
|
||||
|
||||
|
||||
register_fonts = Signal()
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.http import Http404
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import resolve
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_scopes import scope
|
||||
|
||||
from pretix.base.middleware import LocaleMiddleware
|
||||
from pretix.base.models import Event, Organizer
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.db.models import Prefetch, Sum
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
@@ -40,7 +41,10 @@ class CartMixin:
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress()
|
||||
else:
|
||||
try:
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
|
||||
with scopes_disabled():
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress.objects.get(
|
||||
pk=iapk, order__isnull=True
|
||||
)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
self.request._checkout_flow_invoice_address = InvoiceAddress()
|
||||
return self.request._checkout_flow_invoice_address
|
||||
@@ -215,7 +219,8 @@ def get_cart_invoice_address(request):
|
||||
request._checkout_flow_invoice_address = InvoiceAddress()
|
||||
else:
|
||||
try:
|
||||
request._checkout_flow_invoice_address = InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
|
||||
with scopes_disabled():
|
||||
request._checkout_flow_invoice_address = InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
request._checkout_flow_invoice_address = InvoiceAddress()
|
||||
return request._checkout_flow_invoice_address
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.generic import TemplateView, View
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.models import (
|
||||
CartPosition, InvoiceAddress, QuestionAnswer, SubEvent, Voucher,
|
||||
@@ -80,7 +81,8 @@ class CartActionMixin:
|
||||
return InvoiceAddress()
|
||||
|
||||
try:
|
||||
return InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
|
||||
with scopes_disabled():
|
||||
return InvoiceAddress.objects.get(pk=iapk, order__isnull=True)
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
return InvoiceAddress()
|
||||
|
||||
|
||||
@@ -343,6 +343,7 @@ MIDDLEWARE = [
|
||||
'pretix.base.middleware.LocaleMiddleware',
|
||||
'pretix.base.middleware.SecurityMiddleware',
|
||||
'pretix.presale.middleware.EventMiddleware',
|
||||
'pretix.api.middleware.ApiScopeMiddleware',
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
10
src/pretix/testutils/scope.py
Normal file
10
src/pretix/testutils/scope.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django_scopes import scope
|
||||
|
||||
|
||||
def classscope(attr='o'):
|
||||
def wrap(fn):
|
||||
def wrapped(self, *args, **kwargs):
|
||||
with scope(organizer=getattr(self, attr)):
|
||||
return fn(self, *args, **kwargs)
|
||||
return wrapped
|
||||
return wrap
|
||||
@@ -12,6 +12,7 @@ from pretix.settings import * # NOQA
|
||||
DATA_DIR = tmpdir.name
|
||||
LOG_DIR = os.path.join(DATA_DIR, 'logs')
|
||||
MEDIA_ROOT = os.path.join(DATA_DIR, 'media')
|
||||
SITE_URL = "http://example.com"
|
||||
|
||||
atexit.register(tmpdir.cleanup)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user