diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 129667c0a..273300b5c 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -164,7 +164,7 @@ class EventMixin: def annotated(cls, qs, channel='web'): from pretix.base.models import Item, ItemVariation, Quota - sq_active_item = Item.objects.filter_available(channel=channel).filter( + sq_active_item = Item.objects.using(settings.DATABASE_REPLICA).filter_available(channel=channel).filter( Q(variations__isnull=True) & Q(quotas__pk=OuterRef('pk')) ).order_by().values_list('quotas__pk').annotate( @@ -186,7 +186,7 @@ class EventMixin: Prefetch( 'quotas', to_attr='active_quotas', - queryset=Quota.objects.annotate( + queryset=Quota.objects.using(settings.DATABASE_REPLICA).annotate( active_items=Subquery(sq_active_item, output_field=models.TextField()), active_variations=Subquery(sq_active_variation, output_field=models.TextField()), ).exclude( diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 0ee55bd05..62e4535a4 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -1290,7 +1290,8 @@ class Quota(LoggedModel): 'cached_availability_state', 'cached_availability_number', 'cached_availability_time', 'cached_availability_paid_orders' ], - clear_cache=False + clear_cache=False, + using='default' ) if _cache is not None: diff --git a/src/pretix/helpers/database.py b/src/pretix/helpers/database.py index ed0188198..fe734ae47 100644 --- a/src/pretix/helpers/database.py +++ b/src/pretix/helpers/database.py @@ -76,3 +76,21 @@ class GroupConcat(Aggregate): function='string_agg', template="%(function)s(%(field)s::text, '%(separator)s')", ) + + +class ReplicaRouter: + + def db_for_read(self, model, **hints): + return 'default' + + def db_for_write(self, model, **hints): + return 'default' + + def allow_relation(self, obj1, obj2, **hints): + db_list = ('default', 'replica') + if obj1._state.db in db_list and obj2._state.db in db_list: + return True + return None + + def allow_migrate(self, db, app_label, model_name=None, **hintrs): + return True diff --git a/src/pretix/presale/utils.py b/src/pretix/presale/utils.py index 66f9ea0aa..ff6496599 100644 --- a/src/pretix/presale/utils.py +++ b/src/pretix/presale/utils.py @@ -21,6 +21,10 @@ def _detect_event(request, require_live=True, require_plugin=None): if hasattr(request, '_event_detected'): return + db = 'default' + if request.method == 'GET': + db = settings.DATABASE_REPLICA + url = resolve(request.path_info) try: if hasattr(request, 'organizer_domain'): @@ -31,24 +35,24 @@ def _detect_event(request, require_live=True, require_plugin=None): path = "/" + request.get_full_path().split("/", 2)[-1] return redirect(path) - request.event = request.organizer.events\ - .get( - slug=url.kwargs['event'], - organizer=request.organizer, - ) + request.event = request.organizer.events.using(db).get( + slug=url.kwargs['event'], + organizer=request.organizer, + ) request.organizer = request.organizer else: # We are on our main domain if 'event' in url.kwargs and 'organizer' in url.kwargs: request.event = Event.objects\ .select_related('organizer')\ + .using(db)\ .get( slug=url.kwargs['event'], organizer__slug=url.kwargs['organizer'] ) request.organizer = request.event.organizer elif 'organizer' in url.kwargs: - request.organizer = Organizer.objects.get( + request.organizer = Organizer.objects.using(db).get( slug=url.kwargs['organizer'] ) else: diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index c7c23917b..268f89446 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -50,32 +50,34 @@ def item_group_by_category(items): def get_grouped_items(event, subevent=None, voucher=None, channel='web'): - items = event.items.filter_available(channel=channel, voucher=voucher).select_related( + items = event.items.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher).select_related( 'category', 'tax_rule', # for re-grouping ).prefetch_related( Prefetch('quotas', to_attr='_subevent_quotas', - queryset=event.quotas.filter(subevent=subevent)), + queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)), Prefetch('bundles', - queryset=ItemBundle.objects.prefetch_related( + queryset=ItemBundle.objects.using(settings.DATABASE_REPLICA).prefetch_related( Prefetch('bundled_item', - queryset=event.items.select_related('tax_rule').prefetch_related( + queryset=event.items.using(settings.DATABASE_REPLICA).select_related('tax_rule').prefetch_related( Prefetch('quotas', to_attr='_subevent_quotas', - queryset=event.quotas.filter(subevent=subevent)), + queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)), )), Prefetch('bundled_variation', - queryset=ItemVariation.objects.select_related('item', 'item__tax_rule').filter(item__event=event).prefetch_related( + queryset=ItemVariation.objects.using( + settings.DATABASE_REPLICA + ).select_related('item', 'item__tax_rule').filter(item__event=event).prefetch_related( Prefetch('quotas', to_attr='_subevent_quotas', - queryset=event.quotas.filter(subevent=subevent)), + queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)), )), )), Prefetch('variations', to_attr='available_variations', - queryset=ItemVariation.objects.filter(active=True, quotas__isnull=False).prefetch_related( + queryset=ItemVariation.objects.using(settings.DATABASE_REPLICA).filter(active=True, quotas__isnull=False).prefetch_related( Prefetch('quotas', to_attr='_subevent_quotas', - queryset=event.quotas.filter(subevent=subevent)) + queryset=event.quotas.using(settings.DATABASE_REPLICA).filter(subevent=subevent)) ).distinct()), ).annotate( quotac=Count('quotas'), @@ -229,7 +231,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView): if request.event.has_subevents: if 'subevent' in kwargs: - self.subevent = request.event.subevents.filter(pk=kwargs['subevent'], active=True).first() + self.subevent = request.event.subevents.using(settings.DATABASE_REPLICA).filter(pk=kwargs['subevent'], active=True).first() if not self.subevent: raise Http404() return super().get(request, *args, **kwargs) @@ -287,7 +289,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView): ebd = defaultdict(list) add_subevents_for_days( - filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel), self.request), + filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel).using(settings.DATABASE_REPLICA), self.request), before, after, ebd, set(), self.request.event, kwargs.get('cart_namespace') ) @@ -297,7 +299,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView): context['years'] = range(now().year - 2, now().year + 3) else: context['subevent_list'] = self.request.event.subevents_sorted( - filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel), self.request) + filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel).using(settings.DATABASE_REPLICA), self.request) ) context['show_cart'] = ( diff --git a/src/pretix/presale/views/organizer.py b/src/pretix/presale/views/organizer.py index 847599c9c..62f5527ed 100644 --- a/src/pretix/presale/views/organizer.py +++ b/src/pretix/presale/views/organizer.py @@ -93,7 +93,7 @@ class EventListMixin: def _get_event_queryset(self): query = Q(is_public=True) & Q(live=True) - qs = self.request.organizer.events.filter(query) + qs = self.request.organizer.events.using(settings.DATABASE_REPLICA).filter(query) qs = qs.annotate( min_from=Min('subevents__date_from'), min_to=Min('subevents__date_to'), @@ -126,7 +126,7 @@ class EventListMixin: def _set_month_to_next_subevent(self): tz = pytz.timezone(self.request.event.settings.timezone) - next_sev = self.request.event.subevents.filter( + next_sev = self.request.event.subevents.using(settings.DATABASE_REPLICA).filter( active=True, is_public=True, date_from__gte=now() @@ -141,14 +141,14 @@ class EventListMixin: self.month = now().month def _set_month_to_next_event(self): - next_ev = filter_qs_by_attr(Event.objects.filter( + next_ev = filter_qs_by_attr(Event.objects.using(settings.DATABASE_REPLICA).filter( organizer=self.request.organizer, live=True, is_public=True, date_from__gte=now(), has_subevents=False ), self.request).order_by('date_from').first() - next_sev = filter_qs_by_attr(SubEvent.objects.filter( + next_sev = filter_qs_by_attr(SubEvent.objects.using(settings.DATABASE_REPLICA).filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, @@ -353,14 +353,14 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView): def _events_by_day(self, before, after): ebd = defaultdict(list) timezones = set() - add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web'), before, after, ebd, timezones) + add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(settings.DATABASE_REPLICA), before, after, ebd, timezones) add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter( event__organizer=self.request.organizer, event__is_public=True, event__live=True, ).prefetch_related( 'event___settings_objects', 'event__organizer___settings_objects' - )), self.request), before, after, ebd, timezones) + )), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones) self._multiple_timezones = len(timezones) > 1 return ebd diff --git a/src/pretix/settings.py b/src/pretix/settings.py index 62536e278..742b4df59 100644 --- a/src/pretix/settings.py +++ b/src/pretix/settings.py @@ -109,6 +109,7 @@ if config.has_section('replica'): 'COLLATION': 'utf8mb4_unicode_ci', } if 'mysql' in db_backend else {} } + DATABASE_ROUTERS = ['pretix.helpers.database.ReplicaRouter'] STATIC_URL = config.get('urls', 'static', fallback='/static/')