agendas: trigger full and places event fields (#54747)
This commit is contained in:
parent
5041ef37ce
commit
0b53360c0c
|
@ -0,0 +1,21 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agendas', '0097_min_booking_delay_working_days'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='booked_places',
|
||||
field=models.PositiveSmallIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='booked_waiting_list_places',
|
||||
field=models.PositiveSmallIntegerField(default=0),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
import os
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), '..', 'sql', 'event_booked_places_and_full_triggers.sql'
|
||||
)
|
||||
) as sql_file:
|
||||
sql_forwards = sql_file.read()
|
||||
|
||||
sql_backwards = """
|
||||
DROP TRIGGER IF EXISTS update_event_full_fields_trigger ON agendas_event;
|
||||
DROP TRIGGER IF EXISTS update_event_places_fields_trigger ON agendas_booking;
|
||||
|
||||
DROP FUNCTION IF EXISTS update_event_full_fields;
|
||||
DROP FUNCTION IF EXISTS update_event_places_fields;
|
||||
"""
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agendas', '0098_event_booked_places'),
|
||||
]
|
||||
|
||||
operations = [migrations.RunSQL(sql=sql_forwards, reverse_sql=sql_backwards)]
|
|
@ -0,0 +1,13 @@
|
|||
from django.db import migrations
|
||||
|
||||
# trigger places fields
|
||||
sql_forwards = """UPDATE agendas_event SET id=id;"""
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agendas', '0099_event_triggers'),
|
||||
]
|
||||
|
||||
operations = [migrations.RunSQL(sql=sql_forwards, reverse_sql='')]
|
|
@ -32,8 +32,7 @@ from django.contrib.postgres.fields import ArrayField, JSONField
|
|||
from django.core.exceptions import FieldDoesNotExist, ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import IntegrityError, connection, models, transaction
|
||||
from django.db.models import Count, IntegerField, Max, OuterRef, Prefetch, Q, Subquery, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models import Count, Max, Prefetch, Q
|
||||
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist, engines
|
||||
from django.urls import reverse
|
||||
from django.utils import functional
|
||||
|
@ -645,7 +644,6 @@ class Agenda(models.Model):
|
|||
def get_open_events(
|
||||
self,
|
||||
prefetched_queryset=False,
|
||||
annotate_queryset=False,
|
||||
include_full=True,
|
||||
min_start=None,
|
||||
max_start=None,
|
||||
|
@ -690,9 +688,6 @@ class Agenda(models.Model):
|
|||
if user_external_id and not prefetched_queryset:
|
||||
entries = Event.annotate_queryset_for_user(entries, user_external_id)
|
||||
|
||||
if annotate_queryset and not prefetched_queryset:
|
||||
entries = Event.annotate_queryset(entries)
|
||||
|
||||
if max_start:
|
||||
entries = self.add_event_recurrences(
|
||||
entries,
|
||||
|
@ -706,7 +701,6 @@ class Agenda(models.Model):
|
|||
|
||||
def get_past_events(
|
||||
self,
|
||||
annotate_queryset=False,
|
||||
min_start=None,
|
||||
max_start=None,
|
||||
user_external_id=None,
|
||||
|
@ -729,9 +723,6 @@ class Agenda(models.Model):
|
|||
if user_external_id:
|
||||
entries = Event.annotate_queryset_for_user(entries, user_external_id)
|
||||
|
||||
if annotate_queryset:
|
||||
entries = Event.annotate_queryset(entries)
|
||||
|
||||
if min_start:
|
||||
entries = self.add_event_recurrences(
|
||||
entries,
|
||||
|
@ -1363,6 +1354,8 @@ class Event(models.Model):
|
|||
)
|
||||
pricing = models.CharField(_('Pricing'), max_length=150, null=True, blank=True)
|
||||
url = models.CharField(_('URL'), max_length=200, null=True, blank=True)
|
||||
booked_places = models.PositiveSmallIntegerField(default=0)
|
||||
booked_waiting_list_places = models.PositiveSmallIntegerField(default=0)
|
||||
almost_full = models.BooleanField(default=False)
|
||||
full = models.BooleanField(default=False)
|
||||
cancelled = models.BooleanField(default=False)
|
||||
|
@ -1396,7 +1389,6 @@ class Event(models.Model):
|
|||
assert self.agenda.kind != 'virtual', "an event can't reference a virtual agenda"
|
||||
assert not (self.slug and self.slug.isdigit()), 'slug cannot be a number'
|
||||
self.start_datetime = self.start_datetime.replace(second=0, microsecond=0)
|
||||
self.check_full()
|
||||
if not self.slug:
|
||||
self.slug = generate_slug(self, seen_slugs=seen_slugs, agenda=self.agenda)
|
||||
return super().save(*args, **kwargs)
|
||||
|
@ -1406,17 +1398,9 @@ class Event(models.Model):
|
|||
# label can be empty
|
||||
return slugify(self.label or ('%s-event' % self.agenda.label))
|
||||
|
||||
@functional.cached_property
|
||||
def main_list_full(self):
|
||||
return bool(self.booked_places >= self.places)
|
||||
|
||||
def check_full(self):
|
||||
self.full = bool(
|
||||
(self.booked_places >= self.places and self.waiting_list_places == 0)
|
||||
or (self.waiting_list_places and self.waiting_list >= self.waiting_list_places)
|
||||
)
|
||||
self.almost_full = bool(self.booked_places >= 0.9 * self.places)
|
||||
|
||||
def set_is_checked(self):
|
||||
if not self.agenda.mark_event_checked_auto:
|
||||
return
|
||||
|
@ -1451,23 +1435,6 @@ class Event(models.Model):
|
|||
def is_day_past(self):
|
||||
return self.start_datetime.date() <= now().date()
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset(qs):
|
||||
not_cancelled_bookings = Booking.objects.filter(
|
||||
cancellation_datetime__isnull=True, event=OuterRef('pk')
|
||||
)
|
||||
|
||||
bookings = not_cancelled_bookings.filter(in_waiting_list=False).order_by().values('event')
|
||||
count_bookings = bookings.annotate(count=Count('event')).values('count')
|
||||
|
||||
waiting_list_bookings = not_cancelled_bookings.filter(in_waiting_list=True).order_by().values('event')
|
||||
count_waiting_list = waiting_list_bookings.annotate(count=Count('event')).values('count')
|
||||
|
||||
return qs.annotate(
|
||||
booked_places_count=Coalesce(Subquery(count_bookings, output_field=IntegerField()), Value(0)),
|
||||
waiting_list_count=Coalesce(Subquery(count_waiting_list, output_field=IntegerField()), Value(0)),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset_for_user(qs, user_external_id):
|
||||
return qs.annotate(
|
||||
|
@ -1489,25 +1456,13 @@ class Event(models.Model):
|
|||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def booked_places(self):
|
||||
if hasattr(self, 'booked_places_count'):
|
||||
return self.booked_places_count
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True, in_waiting_list=False).count()
|
||||
|
||||
@property
|
||||
def remaining_places(self):
|
||||
return max(0, self.places - self.booked_places)
|
||||
|
||||
@property
|
||||
def waiting_list(self):
|
||||
if hasattr(self, 'waiting_list_count'):
|
||||
return self.waiting_list_count
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True, in_waiting_list=True).count()
|
||||
|
||||
@property
|
||||
def remaining_waiting_list_places(self):
|
||||
return max(0, self.waiting_list_places - self.waiting_list)
|
||||
return max(0, self.waiting_list_places - self.booked_waiting_list_places)
|
||||
|
||||
@property
|
||||
def end_datetime(self):
|
||||
|
@ -1834,14 +1789,6 @@ class Booking(models.Model):
|
|||
def user_name(self):
|
||||
return ('%s %s' % (self.user_first_name, self.user_last_name)).strip()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
with transaction.atomic():
|
||||
super().save(*args, **kwargs)
|
||||
initial_values = self.event.full, self.event.almost_full
|
||||
self.event.check_full()
|
||||
if (self.event.full, self.event.almost_full) != initial_values:
|
||||
self.event.save()
|
||||
|
||||
def cancel(self, trigger_callback=False):
|
||||
timestamp = now()
|
||||
with transaction.atomic():
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
CREATE OR REPLACE FUNCTION update_event_full_fields() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
NEW.booked_places = 0;
|
||||
NEW.booked_waiting_list_places = 0;
|
||||
ELSE
|
||||
-- update booked_places and booked_waiting_list_places fields
|
||||
SELECT
|
||||
COUNT(1) FILTER (WHERE NOT(in_waiting_list)),
|
||||
COUNT(1) FILTER (WHERE in_waiting_list)
|
||||
INTO NEW.booked_places, NEW.booked_waiting_list_places
|
||||
FROM agendas_booking b
|
||||
WHERE b.event_id = NEW.id AND b.cancellation_datetime IS NULL;
|
||||
END IF;
|
||||
|
||||
-- update almost_full field
|
||||
IF (NEW.booked_places >= NEW.places * 0.9) THEN
|
||||
NEW.almost_full = true;
|
||||
ELSE
|
||||
NEW.almost_full = false;
|
||||
END IF;
|
||||
|
||||
-- update full field
|
||||
IF (NEW.booked_places >= NEW.places) AND (NEW.waiting_list_places = 0) THEN
|
||||
NEW.full = true;
|
||||
ELSIF (NEW.waiting_list_places > 0) AND (NEW.booked_waiting_list_places >= NEW.waiting_list_places) THEN
|
||||
NEW.full = true;
|
||||
ELSE
|
||||
NEW.full = false;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_event_places_fields() RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
e_id integer;
|
||||
BEGIN
|
||||
IF (TG_OP = 'DELETE') THEN
|
||||
e_id = OLD.event_id;
|
||||
ELSE
|
||||
e_id = NEW.event_id;
|
||||
END IF;
|
||||
-- update booked_places and booked_waiting_list_places fields on agendas_event
|
||||
WITH new_stats AS (
|
||||
SELECT
|
||||
COUNT(1) FILTER (WHERE NOT(in_waiting_list)) AS booked,
|
||||
COUNT(1) FILTER (WHERE in_waiting_list) AS waiting
|
||||
FROM agendas_booking b
|
||||
WHERE b.event_id = e_id AND b.cancellation_datetime IS NULL
|
||||
)
|
||||
UPDATE agendas_event
|
||||
SET booked_places = new_stats.booked,
|
||||
booked_waiting_list_places = new_stats.waiting
|
||||
FROM new_stats
|
||||
WHERE id = e_id AND (agendas_event.booked_places <> new_stats.booked
|
||||
OR agendas_event.booked_waiting_list_places <> new_stats.waiting);
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS update_event_full_fields_trigger ON agendas_event;
|
||||
CREATE TRIGGER update_event_full_fields_trigger BEFORE INSERT OR UPDATE ON agendas_event
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_event_full_fields();
|
||||
|
||||
DROP TRIGGER IF EXISTS update_event_places_fields_trigger ON agendas_booking;
|
||||
CREATE TRIGGER update_event_places_fields_trigger AFTER INSERT OR UPDATE OR DELETE ON agendas_booking
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_event_places_fields();
|
|
@ -396,7 +396,7 @@ def get_agenda_detail(request, agenda, check_events=False):
|
|||
|
||||
|
||||
def get_event_places(event):
|
||||
available = event.places - event.booked_places
|
||||
available = event.remaining_places
|
||||
places = {
|
||||
'total': event.places,
|
||||
'reserved': event.booked_places,
|
||||
|
@ -407,9 +407,9 @@ def get_event_places(event):
|
|||
if event.waiting_list_places:
|
||||
places['has_waiting_list'] = True
|
||||
places['waiting_list_total'] = event.waiting_list_places
|
||||
places['waiting_list_reserved'] = event.waiting_list
|
||||
places['waiting_list_available'] = event.waiting_list_places - event.waiting_list
|
||||
places['waiting_list_activated'] = event.waiting_list > 0 or available <= 0
|
||||
places['waiting_list_reserved'] = event.booked_waiting_list_places
|
||||
places['waiting_list_available'] = event.remaining_waiting_list_places
|
||||
places['waiting_list_activated'] = event.booked_waiting_list_places > 0 or available <= 0
|
||||
# 'waiting_list_activated' means next booking will go into the waiting list
|
||||
|
||||
return places
|
||||
|
@ -756,14 +756,12 @@ class Datetimes(APIView):
|
|||
entries = []
|
||||
if show_past:
|
||||
entries += agenda.get_past_events(
|
||||
annotate_queryset=True,
|
||||
min_start=payload.get('date_start'),
|
||||
max_start=payload.get('date_end'),
|
||||
user_external_id=user_external_id,
|
||||
)
|
||||
if show_future:
|
||||
entries += agenda.get_open_events(
|
||||
annotate_queryset=True,
|
||||
min_start=payload.get('date_start'),
|
||||
max_start=payload.get('date_end'),
|
||||
user_external_id=user_external_id,
|
||||
|
@ -826,9 +824,7 @@ class MultipleAgendasDatetimes(APIView):
|
|||
|
||||
user_external_id = payload.get('user_external_id') or payload.get('exclude_user_external_id')
|
||||
disable_booked = bool(payload.get('exclude_user_external_id'))
|
||||
agendas = Agenda.prefetch_events_and_exceptions(
|
||||
agendas, annotate_events=True, user_external_id=user_external_id
|
||||
)
|
||||
agendas = Agenda.prefetch_events_and_exceptions(agendas, user_external_id=user_external_id)
|
||||
|
||||
entries = []
|
||||
for agenda in agendas:
|
||||
|
@ -1393,12 +1389,12 @@ class Fillslots(APIView):
|
|||
if (
|
||||
payload.get('force_waiting_list')
|
||||
or (event.booked_places + places_count) > event.places
|
||||
or event.waiting_list
|
||||
or event.booked_waiting_list_places
|
||||
):
|
||||
# if this is full or there are people waiting, put new bookings
|
||||
# in the waiting list.
|
||||
in_waiting_list = True
|
||||
if (event.waiting_list + places_count) > event.waiting_list_places:
|
||||
if (event.booked_waiting_list_places + places_count) > event.waiting_list_places:
|
||||
raise APIError(
|
||||
_('sold out'),
|
||||
err_class='sold out',
|
||||
|
@ -1573,10 +1569,9 @@ class RecurringFillslots(APIView):
|
|||
full_events = list(events_to_book.filter(full=True))
|
||||
events_to_book = events_to_book.filter(full=False)
|
||||
|
||||
events_to_book = Event.annotate_queryset(events_to_book)
|
||||
events_to_book = events_to_book.annotate(
|
||||
in_waiting_list=ExpressionWrapper(
|
||||
Q(booked_places_count__gte=F('places')) | Q(waiting_list_count__gt=0),
|
||||
Q(booked_places__gte=F('places')) | Q(booked_waiting_list_places__gt=0),
|
||||
output_field=BooleanField(),
|
||||
)
|
||||
)
|
||||
|
@ -1584,19 +1579,11 @@ class RecurringFillslots(APIView):
|
|||
extra_data = {k: v for k, v in request.data.items() if k not in payload}
|
||||
bookings = [make_booking(event, payload, extra_data) for event in events_to_book]
|
||||
|
||||
events_to_update = Event.annotate_queryset(
|
||||
agenda.event_set.filter(pk__in=events_to_unbook + [event.pk for event in events_to_book])
|
||||
)
|
||||
with transaction.atomic():
|
||||
deleted_count = Booking.objects.filter(
|
||||
user_external_id=user_external_id, event__in=events_to_unbook
|
||||
).delete()[0]
|
||||
Booking.objects.bulk_create(bookings)
|
||||
events_to_update.update(
|
||||
full=Q(booked_places_count__gte=F('places'), waiting_list_places=0)
|
||||
| Q(waiting_list_places__gt=0, waiting_list_count__gte=F('waiting_list_places')),
|
||||
almost_full=Q(booked_places_count__gte=0.9 * F('places')),
|
||||
)
|
||||
|
||||
response = {
|
||||
'err': 0,
|
||||
|
@ -1633,8 +1620,6 @@ class EventsFillslots(APIView):
|
|||
user_external_id = payload['user_external_id']
|
||||
|
||||
events = self.get_events(request, payload)
|
||||
events = events.select_related('agenda')
|
||||
events = Event.annotate_queryset(events)
|
||||
|
||||
already_booked_events = self.get_already_booked_events(user_external_id)
|
||||
if start_datetime:
|
||||
|
@ -1653,7 +1638,7 @@ class EventsFillslots(APIView):
|
|||
|
||||
events = events.annotate(
|
||||
in_waiting_list=ExpressionWrapper(
|
||||
Q(booked_places_count__gte=F('places')) | Q(waiting_list_count__gt=0),
|
||||
Q(booked_places__gte=F('places')) | Q(booked_waiting_list_places__gt=0),
|
||||
output_field=BooleanField(),
|
||||
)
|
||||
)
|
||||
|
@ -1662,19 +1647,11 @@ class EventsFillslots(APIView):
|
|||
extra_data = {k: v for k, v in request.data.items() if k not in payload}
|
||||
bookings = [make_booking(event, payload, extra_data) for event in events]
|
||||
|
||||
events_to_update = Event.annotate_queryset(
|
||||
Event.objects.filter(pk__in=events_to_unbook + [event.pk for event in events])
|
||||
)
|
||||
with transaction.atomic():
|
||||
deleted_count = Booking.objects.filter(
|
||||
user_external_id=user_external_id, event__in=events_to_unbook
|
||||
).delete()[0]
|
||||
Booking.objects.bulk_create(bookings)
|
||||
events_to_update.update(
|
||||
full=Q(booked_places_count__gte=F('places'), waiting_list_places=0)
|
||||
| Q(waiting_list_places__gt=0, waiting_list_count__gte=F('waiting_list_places')),
|
||||
almost_full=Q(booked_places_count__gte=0.9 * F('places')),
|
||||
)
|
||||
|
||||
response = {
|
||||
'err': 0,
|
||||
|
@ -2106,7 +2083,7 @@ class ResizeBooking(APIView):
|
|||
# total places for the event (in waiting or main list, depending on the primary booking location)
|
||||
places = event.waiting_list_places if booking.in_waiting_list else event.places
|
||||
# total booked places for the event (in waiting or main list, depending on the primary booking location)
|
||||
booked_places = event.waiting_list if booking.in_waiting_list else event.booked_places
|
||||
booked_places = event.booked_waiting_list_places if booking.in_waiting_list else event.booked_places
|
||||
|
||||
# places to book for this primary booking
|
||||
primary_wanted_places = payload['count']
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{% load i18n %}
|
||||
<li class="{% if event.booked_places_count > event.places %}overbooking{% endif %}
|
||||
<li class="{% if event.booked_places > event.places %}overbooking{% endif %}
|
||||
{% if event.main_list_full %}full{% endif %}
|
||||
{% if event.cancellation_status %}cancelled{% endif %}
|
||||
{% if not event.in_bookable_period %}not-{% endif %}bookable"
|
||||
{% if event.places %}
|
||||
data-total="{{ event.places }}" data-booked="{{ event.booked_places_count }}"
|
||||
data-total="{{ event.places }}" data-booked="{{ event.booked_places }}"
|
||||
{% elif event.waiting_list_places %}
|
||||
data-total="{{ event.waiting_list_places }}" data-booked="{{ event.waiting_list_count }}"
|
||||
data-total="{{ event.waiting_list_places }}" data-booked="{{ event.booked_waiting_list_places }}"
|
||||
{% endif %}
|
||||
><a href="{% if view_mode == 'settings_view' %}{% url 'chrono-manager-event-edit' pk=agenda.pk event_pk=event.pk %}?next=settings{% elif event.pk %}{% url 'chrono-manager-event-view' pk=agenda.pk event_pk=event.pk %}{% else %}{% url 'chrono-manager-event-create-recurrence' pk=agenda.pk event_identifier=event.slug %}{% endif %}">
|
||||
{% if event.cancellation_status %}
|
||||
|
@ -40,7 +40,7 @@
|
|||
({% trans "Waiting list:" %}
|
||||
{% blocktrans count remaining_places=event.remaining_waiting_list_places %}{{ remaining_places }} remaining place{% plural %}{{ remaining_places }} remaining places{% endblocktrans %}
|
||||
-
|
||||
{% blocktrans with places=event.waiting_list_places count booked_places=event.waiting_list %}{{ booked_places }}/{{ places }} booking{% plural %}{{ booked_places }}/{{ places }} bookings{% endblocktrans %})
|
||||
{% blocktrans with places=event.waiting_list_places count booked_places=event.booked_waiting_list_places %}{{ booked_places }}/{{ places }} booking{% plural %}{{ booked_places }}/{{ places }} bookings{% endblocktrans %})
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if view_mode == 'settings_view' and event.publication_date %}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{% block content %}
|
||||
<div class="section">
|
||||
<h3>
|
||||
{% blocktrans with places=object.places %}Bookings ({{ booked_places }}/{{ places }}){% endblocktrans %}
|
||||
{% blocktrans with booked_places=object.booked_places places=object.places %}Bookings ({{ booked_places }}/{{ places }}){% endblocktrans %}
|
||||
</h3>
|
||||
<div>
|
||||
<form class="check-bookings-filters">
|
||||
|
@ -76,7 +76,7 @@
|
|||
{% if object.waiting_list_places %}
|
||||
<div class="section">
|
||||
<h3>
|
||||
{% blocktrans with places=object.waiting_list_places %}Waiting List ({{ waiting_places }}/{{ places }}){% endblocktrans %}
|
||||
{% blocktrans with booked_places=object.booked_waiting_list_places places=object.waiting_list_places %}Waiting List ({{ booked_places }}/{{ places }}){% endblocktrans %}
|
||||
</h3>
|
||||
<div>
|
||||
<table class="main check-bookings">
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<div class="section">
|
||||
<h3>
|
||||
{% blocktrans with places=object.places booked_places=booked|length count remaining_places=object.remaining_places %}
|
||||
{% blocktrans with places=object.places booked_places=object.booked_places count remaining_places=object.remaining_places %}
|
||||
Bookings ({{ booked_places }}/{{ places }}): {{ remaining_places }} remaining place
|
||||
{% plural %}
|
||||
Bookings ({{ booked_places }}/{{ places }}): {{ remaining_places }} remaining places
|
||||
|
@ -20,9 +20,9 @@
|
|||
</h3>
|
||||
<div>
|
||||
|
||||
{% if booked|length > event.places %}
|
||||
{% if object.booked_places > event.places %}
|
||||
<div class="errornotice"><p>{% trans "This event is overbooked." %}</p></div>
|
||||
{% elif booked|length == event.places %}
|
||||
{% elif object.booked_places == event.places %}
|
||||
<div class="infonotice"><p>{% trans "This event is full." %}</p></div>
|
||||
{% endif %}
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
|||
{% if object.waiting_list_places %}
|
||||
<div class="section">
|
||||
<h3>
|
||||
{% blocktrans with places=object.waiting_list_places booked_places=waiting|length count remaining_places=object.remaining_waiting_list_places %}
|
||||
{% blocktrans with places=object.waiting_list_places booked_places=object.booked_waiting_list_places count remaining_places=object.remaining_waiting_list_places %}
|
||||
Waiting List ({{ booked_places }}/{{ places }}): {{ remaining_places }} remaining place
|
||||
{% plural %}
|
||||
Waiting List ({{ booked_places }}/{{ places }}): {{ remaining_places }} remaining places
|
||||
|
|
|
@ -25,7 +25,7 @@ import requests
|
|||
from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.models import BooleanField, Count, Max, Min, Q, Sum, Value
|
||||
from django.db.models import BooleanField, Count, Max, Min, Q, Value
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.template.defaultfilters import title
|
||||
|
@ -1123,7 +1123,7 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
qs = super().get_queryset()
|
||||
if self.agenda.kind != 'events':
|
||||
return qs
|
||||
return Event.annotate_queryset(qs).order_by('start_datetime', 'label')
|
||||
return qs.order_by('start_datetime', 'label')
|
||||
|
||||
def get_dated_items(self):
|
||||
date_list, object_list, extra_context = super().get_dated_items()
|
||||
|
@ -1253,7 +1253,7 @@ class AgendaMonthView(AgendaDateView, MonthArchiveView):
|
|||
qs = super().get_queryset()
|
||||
if self.agenda.kind != 'events':
|
||||
return qs
|
||||
return Event.annotate_queryset(qs).order_by('start_datetime', 'label')
|
||||
return qs.order_by('start_datetime', 'label')
|
||||
|
||||
def get_dated_items(self):
|
||||
date_list, object_list, extra_context = super().get_dated_items()
|
||||
|
@ -1466,7 +1466,7 @@ class AgendaOpenEventsView(ViewableAgendaMixin, DetailView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['user_can_manage'] = self.agenda.can_be_managed(self.request.user)
|
||||
context['open_events'] = self.agenda.get_open_events(annotate_queryset=True)
|
||||
context['open_events'] = self.agenda.get_open_events()
|
||||
return context
|
||||
|
||||
|
||||
|
@ -1644,8 +1644,7 @@ class AgendaSettings(ManagedAgendaMixin, DetailView):
|
|||
return context
|
||||
|
||||
def get_events(self):
|
||||
qs = self.agenda.event_set.filter(primary_event__isnull=True)
|
||||
return Event.annotate_queryset(qs)
|
||||
return self.agenda.event_set.filter(primary_event__isnull=True)
|
||||
|
||||
def get_template_names(self):
|
||||
return ['chrono/manager_%s_agenda_settings.html' % self.agenda.kind]
|
||||
|
@ -2025,7 +2024,6 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
|
|||
|
||||
# build booking list
|
||||
context['booked'] = filterset.qs.order_by('user_last_name', 'user_first_name')
|
||||
context['booked_places'] = context['booked'].aggregate(places=Sum('places_count'))['places']
|
||||
context['booked_without_status'] = any(e.user_was_present is None for e in context['booked'])
|
||||
if context['booked_without_status']:
|
||||
context['absence_form'] = BookingAbsenceReasonForm(agenda=self.agenda)
|
||||
|
@ -2041,7 +2039,6 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
|
|||
+ Count('secondary_booking_set', filter=Q(cancellation_datetime__isnull=True))
|
||||
)
|
||||
context['waiting'] = waiting_qs
|
||||
context['waiting_places'] = waiting_qs.aggregate(places=Sum('places_count'))['places']
|
||||
|
||||
return context
|
||||
|
||||
|
|
|
@ -168,17 +168,17 @@ def test_agendas_api(app):
|
|||
|
||||
event1 = Event.objects.create(
|
||||
start_datetime=(localtime() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
||||
places=20,
|
||||
places=1,
|
||||
agenda=event_agenda,
|
||||
)
|
||||
event2 = Event.objects.create(
|
||||
start_datetime=(localtime() + datetime.timedelta(days=10)).replace(hour=10, minute=0),
|
||||
places=20,
|
||||
places=1,
|
||||
agenda=event_agenda,
|
||||
)
|
||||
event3 = Event.objects.create(
|
||||
start_datetime=(localtime() + datetime.timedelta(days=15)).replace(hour=10, minute=0),
|
||||
places=20,
|
||||
places=1,
|
||||
agenda=event_agenda,
|
||||
)
|
||||
|
||||
|
@ -187,17 +187,26 @@ def test_agendas_api(app):
|
|||
assert len(resp.json['data']) == 1
|
||||
|
||||
# one event is full
|
||||
Event.objects.filter(pk=event1.pk).update(full=True)
|
||||
event1.booking_set.create()
|
||||
event1.refresh_from_db()
|
||||
assert event1.full is True
|
||||
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
||||
assert len(resp.json['data']) == 1
|
||||
|
||||
# all events are full
|
||||
Event.objects.update(full=True)
|
||||
event2.booking_set.create()
|
||||
event2.refresh_from_db()
|
||||
assert event2.full is True
|
||||
event3.booking_set.create()
|
||||
event3.refresh_from_db()
|
||||
assert event3.full is True
|
||||
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
||||
assert len(resp.json['data']) == 0
|
||||
|
||||
# event1 is not full but too soon
|
||||
Event.objects.filter(pk=event1.pk).update(full=False)
|
||||
event1.booking_set.all().delete()
|
||||
event1.refresh_from_db()
|
||||
assert event1.full is False
|
||||
event_agenda.minimal_booking_delay = 10
|
||||
event_agenda.save()
|
||||
assert list(event_agenda.get_open_events()) == [event2, event3]
|
||||
|
@ -205,7 +214,9 @@ def test_agendas_api(app):
|
|||
assert len(resp.json['data']) == 0
|
||||
|
||||
# event3 is not full but too late
|
||||
Event.objects.filter(pk=event3.pk).update(full=False)
|
||||
event3.booking_set.all().delete()
|
||||
event3.refresh_from_db()
|
||||
assert event3.full is False
|
||||
event_agenda.maximal_booking_delay = 12
|
||||
event_agenda.save()
|
||||
assert list(event_agenda.get_open_events()) == [event2]
|
||||
|
@ -213,7 +224,9 @@ def test_agendas_api(app):
|
|||
assert len(resp.json['data']) == 0
|
||||
|
||||
# events are not full but not published
|
||||
Event.objects.update(full=False)
|
||||
event2.booking_set.all().delete()
|
||||
event2.refresh_from_db()
|
||||
assert event2.full is False
|
||||
event_agenda.event_set.update(publication_date=now().date() + datetime.timedelta(days=20))
|
||||
assert list(event_agenda.get_open_events()) == []
|
||||
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
||||
|
@ -366,17 +379,17 @@ def test_agenda_detail_api(app):
|
|||
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
|
||||
event1 = Event.objects.create(
|
||||
start_datetime=(localtime() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
||||
places=20,
|
||||
places=1,
|
||||
agenda=agenda,
|
||||
)
|
||||
event2 = Event.objects.create(
|
||||
start_datetime=(localtime() + datetime.timedelta(days=10)).replace(hour=10, minute=0),
|
||||
places=20,
|
||||
places=1,
|
||||
agenda=agenda,
|
||||
)
|
||||
event3 = Event.objects.create(
|
||||
start_datetime=(localtime() + datetime.timedelta(days=15)).replace(hour=10, minute=0),
|
||||
places=20,
|
||||
places=1,
|
||||
agenda=agenda,
|
||||
)
|
||||
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
||||
|
@ -389,17 +402,26 @@ def test_agenda_detail_api(app):
|
|||
assert data['api']['datetimes_url'] == 'http://testserver/api/agenda/foo-bar/datetimes/'
|
||||
|
||||
# one event is full
|
||||
Event.objects.filter(pk=event1.pk).update(full=True)
|
||||
event1.booking_set.create()
|
||||
event1.refresh_from_db()
|
||||
assert event1.full is True
|
||||
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
||||
assert resp.json['data']['opened_events_available'] is True
|
||||
|
||||
# all events are full
|
||||
Event.objects.update(full=True)
|
||||
event2.booking_set.create()
|
||||
event2.refresh_from_db()
|
||||
assert event2.full is True
|
||||
event3.booking_set.create()
|
||||
event3.refresh_from_db()
|
||||
assert event3.full is True
|
||||
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
||||
assert resp.json['data']['opened_events_available'] is False
|
||||
|
||||
# event1 is not full but too soon
|
||||
Event.objects.filter(pk=event1.pk).update(full=False)
|
||||
event1.booking_set.all().delete()
|
||||
event1.refresh_from_db()
|
||||
assert event1.full is False
|
||||
agenda.minimal_booking_delay = 10
|
||||
agenda.save()
|
||||
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
||||
|
@ -407,7 +429,9 @@ def test_agenda_detail_api(app):
|
|||
assert resp.json['data']['opened_events_available'] is False
|
||||
|
||||
# event3 is not full but too late
|
||||
Event.objects.filter(pk=event3.pk).update(full=False)
|
||||
event3.booking_set.all().delete()
|
||||
event3.refresh_from_db()
|
||||
assert event3.full is False
|
||||
agenda.maximal_booking_delay = 12
|
||||
agenda.save()
|
||||
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
||||
|
@ -415,7 +439,9 @@ def test_agenda_detail_api(app):
|
|||
assert resp.json['data']['opened_events_available'] is False
|
||||
|
||||
# events are not full but not published
|
||||
Event.objects.update(full=False)
|
||||
event2.booking_set.all().delete()
|
||||
event2.refresh_from_db()
|
||||
assert event2.full is False
|
||||
agenda.event_set.update(publication_date=now().date() + datetime.timedelta(days=20))
|
||||
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
||||
assert list(agenda.get_open_events()) == []
|
||||
|
|
|
@ -1130,7 +1130,7 @@ def test_multiple_booking_api(app, some_data, user):
|
|||
|
||||
resp3 = app.post('/api/agenda/%s/fillslot/%s/?count=5' % (agenda.slug, event.id))
|
||||
assert Event.objects.get(id=event.id).booked_places == 2
|
||||
assert Event.objects.get(id=event.id).waiting_list == 5
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
|
||||
|
||||
# check waiting list overflow
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/?count=5' % (agenda.slug, event.id))
|
||||
|
@ -1139,12 +1139,12 @@ def test_multiple_booking_api(app, some_data, user):
|
|||
assert resp.json['err_class'] == 'sold out'
|
||||
assert resp.json['err_desc'] == 'sold out'
|
||||
assert Event.objects.get(id=event.id).booked_places == 2
|
||||
assert Event.objects.get(id=event.id).waiting_list == 5
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
|
||||
|
||||
# accept the waiting list
|
||||
resp = app.post(resp3.json['api']['accept_url'])
|
||||
assert Event.objects.get(id=event.id).booked_places == 7
|
||||
assert Event.objects.get(id=event.id).waiting_list == 0
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
|
||||
|
||||
# check with a short waiting list
|
||||
Booking.objects.all().delete()
|
||||
|
@ -1161,7 +1161,7 @@ def test_multiple_booking_api(app, some_data, user):
|
|||
resp = app.post('/api/agenda/%s/fillslot/%s/?count=3' % (agenda.slug, event.id))
|
||||
assert resp.json['err'] == 0
|
||||
assert Event.objects.get(id=event.id).booked_places == 3
|
||||
assert Event.objects.get(id=event.id).waiting_list == 0
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/?count=3' % (agenda.slug, event.id))
|
||||
assert resp.json['err'] == 1
|
||||
|
@ -1172,7 +1172,7 @@ def test_multiple_booking_api(app, some_data, user):
|
|||
resp = app.post('/api/agenda/%s/fillslot/%s/?count=2' % (agenda.slug, event.id))
|
||||
assert resp.json['err'] == 0
|
||||
assert Event.objects.get(id=event.id).booked_places == 3
|
||||
assert Event.objects.get(id=event.id).waiting_list == 2
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 2
|
||||
|
||||
|
||||
def test_multiple_booking_api_fillslots(app, some_data, user):
|
||||
|
@ -1238,7 +1238,7 @@ def test_multiple_booking_api_fillslots(app, some_data, user):
|
|||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 2
|
||||
assert Event.objects.get(id=event.id).waiting_list == 5
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
|
||||
accept_url = resp.json['api']['accept_url']
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
|
||||
|
@ -1248,13 +1248,13 @@ def test_multiple_booking_api_fillslots(app, some_data, user):
|
|||
assert resp.json['err_desc'] == 'sold out'
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 2
|
||||
assert Event.objects.get(id=event.id).waiting_list == 5
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
|
||||
|
||||
# accept the waiting list
|
||||
resp = app.post(accept_url)
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 7
|
||||
assert Event.objects.get(id=event.id).waiting_list == 0
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
|
||||
|
||||
# check with a short waiting list
|
||||
Booking.objects.all().delete()
|
||||
|
@ -1273,7 +1273,7 @@ def test_multiple_booking_api_fillslots(app, some_data, user):
|
|||
assert resp.json['err'] == 0
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 3
|
||||
assert Event.objects.get(id=event.id).waiting_list == 0
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
|
||||
|
||||
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 3})
|
||||
assert resp.json['err'] == 1
|
||||
|
@ -1285,7 +1285,7 @@ def test_multiple_booking_api_fillslots(app, some_data, user):
|
|||
assert resp.json['err'] == 0
|
||||
for event in events:
|
||||
assert Event.objects.get(id=event.id).booked_places == 3
|
||||
assert Event.objects.get(id=event.id).waiting_list == 2
|
||||
assert Event.objects.get(id=event.id).booked_waiting_list_places == 2
|
||||
|
||||
|
||||
def test_multiple_booking_move_booking(app, user):
|
||||
|
@ -2143,8 +2143,8 @@ def test_recurring_events_api_fillslots(app, user, freezer):
|
|||
assert Booking.objects.filter(event__primary_event=event).count() == 104
|
||||
assert Booking.objects.filter(event__primary_event=sunday_event).count() == 52
|
||||
|
||||
events = Event.annotate_queryset(Event.objects.filter(primary_event__isnull=False))
|
||||
assert events.filter(booked_places_count=1).count() == 156
|
||||
events = Event.objects.filter(primary_event__isnull=False)
|
||||
assert events.filter(booked_places=1).count() == 156
|
||||
|
||||
# one recurrence is booked separately
|
||||
event = Event.objects.filter(primary_event__isnull=False).first()
|
||||
|
@ -2155,15 +2155,15 @@ def test_recurring_events_api_fillslots(app, user, freezer):
|
|||
assert resp.json['booking_count'] == 156
|
||||
assert not resp.json['full_events']
|
||||
assert Booking.objects.count() == 313
|
||||
events = Event.annotate_queryset(Event.objects.filter(primary_event__isnull=False))
|
||||
assert events.filter(booked_places_count=2).count() == 156
|
||||
events = Event.objects.filter(primary_event__isnull=False)
|
||||
assert events.filter(booked_places=2).count() == 156
|
||||
# one booking has been put in waiting list
|
||||
assert events.filter(waiting_list_count=1).count() == 1
|
||||
assert events.filter(booked_waiting_list_places=1).count() == 1
|
||||
|
||||
params['user_external_id'] = 'user_id_3'
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
# everything goes in waiting list
|
||||
assert events.filter(waiting_list_count=1).count() == 156
|
||||
assert events.filter(booked_waiting_list_places=1).count() == 156
|
||||
# but an event was full
|
||||
assert resp.json['booking_count'] == 155
|
||||
assert len(resp.json['full_events']) == 1
|
||||
|
@ -2221,14 +2221,14 @@ def test_recurring_events_api_fillslots_waiting_list(app, user, freezer):
|
|||
# create bookings in waiting list
|
||||
for recurrence in event.recurrences.all():
|
||||
Booking.objects.create(event=recurrence, in_waiting_list=True)
|
||||
events = Event.annotate_queryset(Event.objects.filter(primary_event__isnull=False))
|
||||
assert events.filter(waiting_list_count=1).count() == 5
|
||||
events = Event.objects.filter(primary_event__isnull=False)
|
||||
assert events.filter(booked_waiting_list_places=1).count() == 5
|
||||
|
||||
# check that new bookings are put in waiting list despite free slots on main list
|
||||
params = {'user_external_id': 'user_id', 'slots': 'event:0'}
|
||||
resp = app.post_json('/api/agenda/%s/recurring-events/fillslots/' % agenda.slug, params=params)
|
||||
assert resp.json['booking_count'] == 5
|
||||
assert events.filter(waiting_list_count=2).count() == 5
|
||||
assert events.filter(booked_waiting_list_places=2).count() == 5
|
||||
|
||||
|
||||
def test_recurring_events_api_fillslots_change_bookings(app, user, freezer):
|
||||
|
@ -2280,9 +2280,9 @@ def test_recurring_events_api_fillslots_change_bookings(app, user, freezer):
|
|||
assert Booking.objects.count() == 208
|
||||
assert Booking.objects.filter(event__start_datetime__week_day=2).count() == 104
|
||||
assert Booking.objects.filter(event__start_datetime__week_day=5).count() == 52
|
||||
events = Event.annotate_queryset(Event.objects.filter(primary_event__isnull=False))
|
||||
assert events.filter(booked_places_count=1).count() == 156
|
||||
assert events.filter(waiting_list_count=1).count() == 52
|
||||
events = Event.objects.filter(primary_event__isnull=False)
|
||||
assert events.filter(booked_places=1).count() == 156
|
||||
assert events.filter(booked_waiting_list_places=1).count() == 52
|
||||
|
||||
params['slots'] = 'event:1,event:4'
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
|
@ -2291,9 +2291,9 @@ def test_recurring_events_api_fillslots_change_bookings(app, user, freezer):
|
|||
assert Booking.objects.count() == 208
|
||||
assert Booking.objects.filter(event__start_datetime__week_day=3).count() == 104
|
||||
assert Booking.objects.filter(event__start_datetime__week_day=6).count() == 52
|
||||
events = Event.annotate_queryset(Event.objects.filter(primary_event__isnull=False))
|
||||
assert events.filter(booked_places_count=1).count() == 156
|
||||
assert events.filter(waiting_list_count=1).count() == 52
|
||||
events = Event.objects.filter(primary_event__isnull=False)
|
||||
assert events.filter(booked_places=1).count() == 156
|
||||
assert events.filter(booked_waiting_list_places=1).count() == 52
|
||||
|
||||
# passing empty slots cancels all bookings
|
||||
params['slots'] = ''
|
||||
|
@ -2335,8 +2335,8 @@ def test_api_events_fillslots(app, user):
|
|||
assert resp.json['booking_count'] == 2
|
||||
assert len(resp.json['waiting_list_events']) == 0
|
||||
|
||||
events = Event.annotate_queryset(Event.objects.all())
|
||||
assert events.filter(booked_places_count=1).count() == 2
|
||||
events = Event.objects.all()
|
||||
assert events.filter(booked_places=1).count() == 2
|
||||
|
||||
params['user_external_id'] = 'user_id_2'
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
|
@ -2415,22 +2415,22 @@ def test_api_events_fillslots_past_event(app, user):
|
|||
)
|
||||
|
||||
params = {'user_external_id': 'user_id', 'slots': ','.join((event1.slug, event2.slug))}
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params=params)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'event %s is not bookable' % event1.slug
|
||||
|
||||
params['events'] = 'future'
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params=params)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'event %s is not bookable' % event1.slug
|
||||
|
||||
params['events'] = 'past'
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params=params)
|
||||
assert resp.json['err'] == 1
|
||||
assert resp.json['err_desc'] == 'event %s is not bookable' % event2.slug
|
||||
|
||||
params['events'] = 'all'
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params)
|
||||
resp = app.post_json('/api/agenda/%s/events/fillslots/' % agenda.slug, params=params)
|
||||
assert resp.json['err'] == 0
|
||||
|
||||
|
||||
|
|
|
@ -879,33 +879,6 @@ def test_management_role_deletion():
|
|||
assert Agenda.objects.get(id=agenda.id).edit_role is None
|
||||
|
||||
|
||||
def test_event_bookings_annotation():
|
||||
agenda = Agenda(label='test', kind='events')
|
||||
agenda.save()
|
||||
event = Event(start_datetime=now(), label='foo', places=10, waiting_list_places=10, agenda=agenda)
|
||||
event.save()
|
||||
event2 = Event(start_datetime=now(), label='bar', places=10, waiting_list_places=10, agenda=agenda)
|
||||
event2.save()
|
||||
|
||||
Booking(event=event).save()
|
||||
Booking(event=event).save()
|
||||
Booking(event=event, cancellation_datetime=now()).save()
|
||||
Booking(event=event, in_waiting_list=True).save()
|
||||
Booking(event=event, in_waiting_list=True, cancellation_datetime=now()).save()
|
||||
|
||||
Booking(event=event2).save()
|
||||
Booking(event=event2).save()
|
||||
Booking(event=event2).save()
|
||||
|
||||
for event in Event.annotate_queryset(Event.objects.filter(agenda=agenda)):
|
||||
if event.label == 'foo':
|
||||
assert event.booked_places_count == 2
|
||||
assert event.waiting_list_count == 1
|
||||
elif event.label == 'bar':
|
||||
assert event.booked_places_count == 3
|
||||
assert event.waiting_list_count == 0
|
||||
|
||||
|
||||
def test_virtual_agenda_init():
|
||||
agenda1 = Agenda.objects.create(label='Agenda 1', kind='meetings')
|
||||
agenda2 = Agenda.objects.create(label='Agenda 2', kind='meetings')
|
||||
|
@ -2279,3 +2252,173 @@ def test_recurring_events_display(freezer):
|
|||
event.get_recurrence_display()
|
||||
== 'On Monday at 1:30 p.m., once every three weeks, from Jan. 7, 2021, until Jan. 14, 2021'
|
||||
)
|
||||
|
||||
|
||||
def test_event_triggered_fields():
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
||||
event = Event.objects.create(
|
||||
agenda=agenda, start_datetime=now() + datetime.timedelta(days=10), places=10, label='Event'
|
||||
)
|
||||
event2 = Event.objects.create(
|
||||
agenda=agenda, start_datetime=now() + datetime.timedelta(days=10), places=10, label='Event'
|
||||
)
|
||||
assert event.booked_places == 0
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is False
|
||||
assert event.full is False
|
||||
|
||||
event.booked_places = 42
|
||||
event.booked_waiting_list_places = 42
|
||||
event.almost_full = True
|
||||
event.full = True
|
||||
event.save()
|
||||
# computed by triggers
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 0
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is False
|
||||
assert event.full is False
|
||||
|
||||
# add bookings for other event: no impact
|
||||
for _ in range(10):
|
||||
Booking.objects.create(event=event2)
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 0
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is False
|
||||
assert event.full is False
|
||||
|
||||
# add bookings
|
||||
for _ in range(9):
|
||||
Booking.objects.create(event=event)
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 9
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
|
||||
Booking.objects.create(event=event)
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is True
|
||||
assert event.full is True
|
||||
|
||||
# cancel bookings for other event: no impact
|
||||
event2.booking_set.filter(cancellation_datetime__isnull=True).first().cancel()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is True
|
||||
assert event.full is True
|
||||
|
||||
# cancel bookings
|
||||
event.booking_set.filter(cancellation_datetime__isnull=True).first().cancel()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 9
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
event.booking_set.filter(cancellation_datetime__isnull=True).first().cancel()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 8
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is False
|
||||
assert event.full is False
|
||||
|
||||
# update places
|
||||
event.places = 20
|
||||
event.save()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 8
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is False
|
||||
assert event.full is False
|
||||
|
||||
Booking.objects.create(event=event)
|
||||
Booking.objects.create(event=event)
|
||||
event.places = 10
|
||||
event.save()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is True
|
||||
assert event.full is True
|
||||
|
||||
# with a waiting list
|
||||
event.waiting_list_places = 5
|
||||
event.save()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
|
||||
# add bookings for other event: no impact
|
||||
for _ in range(10):
|
||||
Booking.objects.create(event=event2, in_waiting_list=True)
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 0
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
|
||||
# add bookings
|
||||
Booking.objects.create(event=event, in_waiting_list=True)
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 1
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
for _ in range(1, 5):
|
||||
Booking.objects.create(event=event, in_waiting_list=True)
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 5
|
||||
assert event.almost_full is True
|
||||
assert event.full is True
|
||||
|
||||
# cancel bookings for other event: no impact
|
||||
event2.booking_set.filter(in_waiting_list=True, cancellation_datetime__isnull=True).first().cancel()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 5
|
||||
assert event.almost_full is True
|
||||
assert event.full is True
|
||||
|
||||
# cancel bookings
|
||||
event.booking_set.filter(in_waiting_list=True, cancellation_datetime__isnull=True).first().cancel()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 4
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
|
||||
# update waiting list places
|
||||
event.waiting_list_places = 4
|
||||
event.save()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 4
|
||||
assert event.almost_full is True
|
||||
assert event.full is True
|
||||
|
||||
# delete bookings
|
||||
event.booking_set.filter(in_waiting_list=True, cancellation_datetime__isnull=True).first().delete()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 10
|
||||
assert event.booked_waiting_list_places == 3
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
event.booking_set.filter(in_waiting_list=False, cancellation_datetime__isnull=True).first().delete()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 9
|
||||
assert event.booked_waiting_list_places == 3
|
||||
assert event.almost_full is True
|
||||
assert event.full is False
|
||||
event.booking_set.filter(in_waiting_list=False, cancellation_datetime__isnull=True).first().delete()
|
||||
event.refresh_from_db()
|
||||
assert event.booked_places == 8
|
||||
assert event.booked_waiting_list_places == 3
|
||||
assert event.almost_full is False
|
||||
assert event.full is False
|
||||
|
|
Loading…
Reference in New Issue