forked from CGM_Public/pretix_original
158 lines
6.2 KiB
Python
158 lines
6.2 KiB
Python
#
|
|
# This file is part of pretix (Community Edition).
|
|
#
|
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
|
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
|
#
|
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
|
# this file, see <https://pretix.eu/about/en/license>.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
|
# <https://www.gnu.org/licenses/>.
|
|
#
|
|
import sys
|
|
from datetime import timedelta
|
|
|
|
from django.db.models import (
|
|
Exists, F, OuterRef, Prefetch, Q, Sum, prefetch_related_objects,
|
|
)
|
|
from django.dispatch import receiver
|
|
from django.utils.timezone import now
|
|
from django_scopes import scopes_disabled
|
|
|
|
from pretix.base.models import (
|
|
Event, EventMetaValue, SeatCategoryMapping, User, WaitingListEntry,
|
|
)
|
|
from pretix.base.models.waitinglist import WaitingListException
|
|
from pretix.base.services.tasks import EventTask
|
|
from pretix.base.signals import periodic_task
|
|
from pretix.celery_app import app
|
|
|
|
|
|
@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:
|
|
user = None
|
|
|
|
quota_cache = {}
|
|
gone = set()
|
|
seats_available = {}
|
|
|
|
for m in SeatCategoryMapping.objects.filter(event=event).select_related('subevent'):
|
|
# See comment in WaitingListEntry.send_voucher() for rationale
|
|
num_free_seets_for_product = (m.subevent or event).free_seats().filter(product_id=m.product_id).count()
|
|
num_valid_vouchers_for_product = event.vouchers.filter(
|
|
Q(valid_until__isnull=True) | Q(valid_until__gte=now()),
|
|
block_quota=True,
|
|
item_id=m.product_id,
|
|
subevent_id=m.subevent_id,
|
|
waitinglistentries__isnull=False
|
|
).aggregate(free=Sum(F('max_usages') - F('redeemed')))['free'] or 0
|
|
seats_available[(m.product_id, m.subevent_id)] = num_free_seets_for_product - num_valid_vouchers_for_product
|
|
|
|
prefetch_related_objects(
|
|
[event.organizer],
|
|
'meta_properties'
|
|
)
|
|
prefetch_related_objects(
|
|
[event],
|
|
Prefetch(
|
|
'meta_values',
|
|
EventMetaValue.objects.select_related('property'),
|
|
to_attr='meta_values_cached'
|
|
)
|
|
)
|
|
|
|
qs = event.waitinglistentries.filter(
|
|
voucher__isnull=True
|
|
).select_related('item', 'variation', 'subevent').prefetch_related(
|
|
'item__quotas', 'variation__quotas'
|
|
).order_by('-priority', 'created')
|
|
|
|
if subevent_id and event.has_subevents:
|
|
subevent = event.subevents.get(id=subevent_id)
|
|
qs = qs.filter(subevent=subevent)
|
|
|
|
sent = 0
|
|
|
|
with event.lock():
|
|
for wle in qs:
|
|
if (wle.item, wle.variation, wle.subevent) in gone:
|
|
continue
|
|
|
|
ev = (wle.subevent or event)
|
|
if not ev.presale_is_running or (wle.subevent and not wle.subevent.active):
|
|
continue
|
|
if wle.subevent and not wle.subevent.presale_is_running:
|
|
continue
|
|
if not wle.item.is_available():
|
|
gone.add((wle.item, wle.variation, wle.subevent))
|
|
continue
|
|
|
|
if (wle.item_id, wle.subevent_id) in seats_available:
|
|
if seats_available[wle.item_id, wle.subevent_id] < 1:
|
|
gone.add((wle.item, wle.variation, wle.subevent))
|
|
continue
|
|
|
|
quotas = (wle.variation.quotas.filter(subevent=wle.subevent)
|
|
if wle.variation
|
|
else wle.item.quotas.filter(subevent=wle.subevent))
|
|
availability = (
|
|
wle.variation.check_quotas(count_waitinglist=False, _cache=quota_cache, subevent=wle.subevent)
|
|
if wle.variation
|
|
else wle.item.check_quotas(count_waitinglist=False, _cache=quota_cache, subevent=wle.subevent)
|
|
)
|
|
if availability[1] is None or availability[1] > 0:
|
|
try:
|
|
wle.send_voucher(quota_cache, user=user)
|
|
sent += 1
|
|
except WaitingListException: # noqa
|
|
continue
|
|
|
|
# Reduce affected quotas in cache
|
|
for q in quotas:
|
|
quota_cache[q.pk] = (
|
|
quota_cache[q.pk][0] if quota_cache[q.pk][0] > 1 else 0,
|
|
quota_cache[q.pk][1] - 1 if quota_cache[q.pk][1] is not None else sys.maxsize
|
|
)
|
|
|
|
if (wle.item_id, wle.subevent_id) in seats_available:
|
|
seats_available[wle.item_id, wle.subevent_id] -= 1
|
|
else:
|
|
gone.add((wle.item, wle.variation, wle.subevent))
|
|
|
|
return sent
|
|
|
|
|
|
@receiver(signal=periodic_task)
|
|
@scopes_disabled()
|
|
def process_waitinglist(sender, **kwargs):
|
|
qs = Event.objects.filter(
|
|
Exists(
|
|
WaitingListEntry.objects.filter(
|
|
event_id=OuterRef('pk'),
|
|
voucher__isnull=True,
|
|
)
|
|
),
|
|
live=True
|
|
).exclude(
|
|
Q(date_to__isnull=True) | Q(date_to__lt=now() - timedelta(days=14)),
|
|
Q(presale_end__isnull=True) | Q(presale_end__lt=now() - timedelta(days=14)),
|
|
has_subevents=False,
|
|
date_from__lt=now() - timedelta(days=14),
|
|
).prefetch_related('_settings_objects', 'organizer___settings_objects').select_related('organizer')
|
|
for e in qs:
|
|
if e.settings.waiting_list_auto and (e.presale_is_running or e.has_subevents):
|
|
assign_automatically.apply_async(args=(e.pk,))
|