manager: list subscriptions on event check page (#61069)

This commit is contained in:
Lauréline Guérin 2022-01-27 16:31:02 +01:00
parent 9789a77771
commit 5606182d55
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
5 changed files with 314 additions and 44 deletions

View File

@ -2977,3 +2977,23 @@ class Subscription(models.Model):
extra_data = JSONField(null=True)
date_start = models.DateField()
date_end = models.DateField()
@property
def user_name(self):
return ('%s %s' % (self.user_first_name, self.user_last_name)).strip()
@property
def label(self):
return _('Subscription')
def get_user_block(self):
template_vars = Context(settings.TEMPLATE_VARS)
template_vars.update(
{
'booking': self,
}
)
try:
return Template(self.agenda.get_booking_user_block_template()).render(template_vars)
except (VariableDoesNotExist, TemplateSyntaxError):
return

View File

@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import collections
import csv
import datetime
@ -43,6 +42,7 @@ from chrono.agendas.models import (
Event,
MeetingType,
Resource,
Subscription,
TimePeriod,
TimePeriodException,
TimePeriodExceptionSource,
@ -324,20 +324,9 @@ class BookingCheckFilterSet(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
self.agenda = kwargs.pop('agenda')
filters = kwargs.pop('filters')
super().__init__(*args, **kwargs)
# build filters from DB
agenda_filters = self.agenda.get_booking_check_filters()
filters = collections.defaultdict(set)
for extra_data in self.queryset.filter(extra_data__has_any_keys=agenda_filters).values_list(
'extra_data', flat=True
):
for k, v in extra_data.items():
if k in agenda_filters:
filters[k].add(v)
filters = sorted(filters.items())
filters = {k: sorted(list(v)) for k, v in filters}
# add filters to filterset
for key, values in filters.items():
self.filters[key] = django_filters.ChoiceFilter(
@ -350,6 +339,12 @@ class BookingCheckFilterSet(django_filters.FilterSet):
)
class SubscriptionCheckFilterSet(BookingCheckFilterSet):
class Meta:
model = Subscription
fields = []
class BookingAbsenceReasonForm(forms.Form):
reason = forms.ChoiceField(required=False)

View File

@ -27,7 +27,7 @@
</form>
<table class="main check-bookings">
<tbody>
{% if booked and not event.checked %}
{% if results and not event.checked %}
<tr class="booking">
<td class="booking-actions">
<form method="post" action="{% url 'chrono-manager-event-checked' pk=agenda.pk event_pk=object.pk %}">
@ -63,9 +63,17 @@
</tr>
{% endif %}
{% endif %}
{% for booking in booked %}
{% for result in results %}
<tr class="booking">
{% include "chrono/manager_event_check_booking_fragment.html" %}
{% if result.kind == 'booking' %}
{% with result as booking %}{% include "chrono/manager_event_check_booking_fragment.html" %}{% endwith %}
{% elif result.kind == 'subscription' %}
<td class="booking-username main-list">{{ result.get_user_block }}</td>
<td class="booking-status">({% trans "Not booked" %})</td>
{% if not event.checked or not agenda.disable_check_update %}
<td class="booking-actions"></td>
{% endif %}
{% endif %}
</tr>
{% endfor %}
</tbody>

View File

@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import collections
import copy
import csv
import datetime
@ -21,6 +22,7 @@ import itertools
import json
import math
import uuid
from operator import attrgetter
import requests
from django.contrib import messages
@ -102,6 +104,7 @@ from .forms import (
NewEventForm,
NewMeetingTypeForm,
NewTimePeriodExceptionForm,
SubscriptionCheckFilterSet,
TimePeriodAddForm,
TimePeriodExceptionForm,
TimePeriodExceptionSourceReplaceForm,
@ -2049,10 +2052,29 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
queryset = super().get_queryset()
return queryset.filter(agenda=self.agenda, start_datetime__date__lte=now().date(), cancelled=False)
def get_filters(self, booked_queryset, subscription_queryset):
agenda_filters = self.agenda.get_booking_check_filters()
filters = collections.defaultdict(set)
extra_data_from_booked = booked_queryset.filter(extra_data__has_any_keys=agenda_filters).values_list(
'extra_data', flat=True
)
extra_data_from_subscriptions = subscription_queryset.filter(
extra_data__has_any_keys=agenda_filters
).values_list('extra_data', flat=True)
for extra_data in list(extra_data_from_booked) + list(extra_data_from_subscriptions):
for k, v in extra_data.items():
if k in agenda_filters:
filters[k].add(v)
filters = sorted(filters.items())
filters = {k: sorted(list(v)) for k, v in filters}
return filters
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
event = self.object
# booking base queryset
booked_qs = event.booking_set.filter(
cancellation_datetime__isnull=True, in_waiting_list=False, primary_booking__isnull=True
)
@ -2060,18 +2082,8 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
places_count=Value(1)
+ Count('secondary_booking_set', filter=Q(cancellation_datetime__isnull=True))
)
filterset = BookingCheckFilterSet(self.request.GET, queryset=booked_qs, agenda=self.agenda)
context['filterset'] = filterset
# build booking list
context['booked'] = filterset.qs.order_by('user_last_name', 'user_first_name')
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)
for booking in context['booked']:
booking.form = BookingAbsenceReasonForm(
agenda=self.agenda, initial={'reason': booking.user_absence_reason}
)
# waiting list queryset
waiting_qs = event.booking_set.filter(
cancellation_datetime__isnull=True, in_waiting_list=True, primary_booking__isnull=True
).order_by('user_last_name', 'user_first_name')
@ -2079,6 +2091,51 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
places_count=Value(1)
+ Count('secondary_booking_set', filter=Q(cancellation_datetime__isnull=True))
)
# subscription base queryset
subscription_qs = (
self.agenda.subscriptions.filter(
date_start__lte=event.start_datetime, date_end__gte=event.start_datetime
)
# exclude user_external_id from booked_qs and waiting_qs
.exclude(user_external_id__in=booked_qs.values('user_external_id')).exclude(
user_external_id__in=waiting_qs.values('user_external_id')
)
)
# build filters from booked_qs and subscription_qs
filters = self.get_filters(booked_queryset=booked_qs, subscription_queryset=subscription_qs)
# and filter booked and subscriptions
booked_filterset = BookingCheckFilterSet(
self.request.GET, queryset=booked_qs, agenda=self.agenda, filters=filters
)
subscription_filterset = SubscriptionCheckFilterSet(
self.request.GET, queryset=subscription_qs, agenda=self.agenda, filters=filters
)
# build results from mixed booked and subscriptions
results = []
booked_without_status = False
for booking in booked_filterset.qs:
if booking.user_was_present is None:
booked_without_status = True
booking.form = BookingAbsenceReasonForm(
agenda=self.agenda, initial={'reason': booking.user_absence_reason}
)
booking.kind = 'booking'
results.append(booking)
for subscription in subscription_filterset.qs:
subscription.kind = 'subscription'
results.append(subscription)
# sort results
results = sorted(results, key=attrgetter('user_last_name', 'user_first_name'))
# set context
context['booked_without_status'] = booked_without_status
if context['booked_without_status']:
context['absence_form'] = BookingAbsenceReasonForm(agenda=self.agenda)
context['filterset'] = booked_filterset
context['results'] = results
context['waiting'] = waiting_qs
return context

View File

@ -10,7 +10,15 @@ from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from webtest import Upload
from chrono.agendas.models import AbsenceReason, AbsenceReasonGroup, Agenda, Booking, Desk, Event
from chrono.agendas.models import (
AbsenceReason,
AbsenceReasonGroup,
Agenda,
Booking,
Desk,
Event,
Subscription,
)
from tests.utils import login
pytestmark = pytest.mark.django_db
@ -1244,19 +1252,88 @@ def test_event_check(app, admin_user):
waiting_list_places=5,
agenda=agenda,
)
booking1 = Booking.objects.create(event=event, user_first_name='User', user_last_name='42')
Booking.objects.create(event=event, user_first_name='User', user_last_name='01')
Booking.objects.create(event=event, user_first_name='User', user_last_name='17')
Booking.objects.create(event=event, user_first_name='User', user_last_name='35')
Booking.objects.create(event=event, user_first_name='User', user_last_name='05')
booking6 = Booking.objects.create(event=event, user_first_name='User', user_last_name='Cancelled')
booking1 = Booking.objects.create(
event=event, user_external_id='user:1', user_first_name='User', user_last_name='42'
)
Booking.objects.create(
event=event, user_external_id='user:2', user_first_name='User', user_last_name='01'
)
Booking.objects.create(
event=event, user_external_id='user:3', user_first_name='User', user_last_name='17'
)
Booking.objects.create(
event=event, user_external_id='user:4', user_first_name='User', user_last_name='35'
)
Booking.objects.create(
event=event, user_external_id='user:5', user_first_name='User', user_last_name='05'
)
booking6 = Booking.objects.create(
event=event, user_external_id='user:6', user_first_name='User', user_last_name='Cancelled'
)
booking6.cancel()
booking7 = Booking.objects.create(
event=event, user_first_name='User', user_last_name='Waiting', in_waiting_list=True
event=event,
user_external_id='user:7',
user_first_name='User',
user_last_name='Waiting',
in_waiting_list=True,
)
booking8 = Booking.objects.create(
event=event, user_first_name='User', user_last_name='Secondary', primary_booking=booking1
event=event,
user_external_id='user:1',
user_first_name='User',
user_last_name='Secondary',
primary_booking=booking1,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='42',
date_start=now(),
date_end=now(),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:8',
user_first_name='Subscription',
user_last_name='43',
date_start=now(),
date_end=now(),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:9',
user_first_name='Subscription',
user_last_name='14',
date_start=now(),
date_end=now(),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:7',
user_first_name='Subscription',
user_last_name='Waiting',
date_start=now(),
date_end=now(),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:42',
user_first_name='Subscription',
user_last_name='Too soon',
date_start=now() - datetime.timedelta(days=1),
date_end=now() - datetime.timedelta(days=1),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:42',
user_first_name='Subscription',
user_last_name='Too late',
date_start=now() + datetime.timedelta(days=1),
date_end=now() + datetime.timedelta(days=1),
)
login(app)
# event not in past
@ -1275,24 +1352,29 @@ def test_event_check(app, admin_user):
app.get('/manage/agendas/%s/events/%s/check' % (agenda2.pk, event.pk), status=404)
resp = resp.click('Check')
assert 'Bookings (6/10)' in resp
assert 'Waiting List (1/5)' in resp
assert (
resp.text.index('Bookings (6/10)')
< resp.text.index('User 01')
< resp.text.index('User 05')
< resp.text.index('Subscription 14')
< resp.text.index('User 17')
< resp.text.index('User 35')
< resp.text.index('User 42')
< resp.text.index('Subscription 43')
< resp.text.index('Waiting List (1/5)')
< resp.text.index('User Waiting')
) # user ordering is not optimal ...
)
assert 'User Cancelled' not in resp
assert 'Subscription Waiting' not in resp
assert 'Subscription 42' not in resp
assert 'Subscription too soon' not in resp
assert 'Subscription too late' not in resp
agenda.booking_user_block_template = '{{ booking.user_name }} Foo Bar'
agenda.save()
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert 'User 01 Foo Bar' in resp
assert 'Subscription 14 Foo Bar' in resp
assert 'User Waiting Foo Bar' in resp
# cancelled booking
@ -1412,35 +1494,113 @@ def test_event_check_filters(app, admin_user):
places=10,
agenda=agenda,
)
Booking.objects.create(event=event, user_first_name='User', user_last_name='none')
Booking.objects.create(event=event, user_first_name='User', user_last_name='empty', extra_data={})
Booking.objects.create(
event=event, user_first_name='User', user_last_name='foo-val1 bar-none', extra_data={'foo': 'val1'}
event=event, user_external_id='user:none', user_first_name='User', user_last_name='none'
)
Booking.objects.create(
event=event,
user_external_id='user:empty',
user_first_name='User',
user_last_name='empty',
extra_data={},
)
Booking.objects.create(
event=event,
user_external_id='user:1',
user_first_name='User',
user_last_name='foo-val1 bar-none',
extra_data={'foo': 'val1'},
)
Booking.objects.create(
event=event,
user_external_id='user:2',
user_first_name='User',
user_last_name='foo-val2 bar-val1',
extra_data={'foo': 'val2', 'bar': 'val1'},
)
Booking.objects.create(
event=event,
user_external_id='user:3',
user_first_name='User',
user_last_name='foo-val1 bar-val2',
extra_data={'foo': 'val1', 'bar': 'val2'},
)
Booking.objects.create(
event=event, user_first_name='User', user_last_name='foo-none bar-val2', extra_data={'bar': 'val2'}
event=event,
user_external_id='user:4',
user_first_name='User',
user_last_name='foo-none bar-val2',
extra_data={'bar': 'val2'},
)
Subscription.objects.create(
agenda=agenda,
user_external_id='subscription:none',
user_first_name='Subscription',
user_last_name='none',
date_start=event.start_datetime,
date_end=event.start_datetime,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='subscription:empty',
user_first_name='Subscription',
user_last_name='empty',
extra_data={},
date_start=event.start_datetime,
date_end=event.start_datetime,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='subscription:1',
user_first_name='Subscription',
user_last_name='foo-val1 bar-none',
extra_data={'foo': 'val1'},
date_start=event.start_datetime,
date_end=event.start_datetime,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='subscription:2',
user_first_name='Subscription',
user_last_name='foo-val2 bar-val1',
extra_data={'foo': 'val2', 'bar': 'val1'},
date_start=event.start_datetime,
date_end=event.start_datetime,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='subscription:3',
user_first_name='Subscription',
user_last_name='foo-val1 bar-val2',
extra_data={'foo': 'val1', 'bar': 'val2'},
date_start=event.start_datetime,
date_end=event.start_datetime,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='subscription:4',
user_first_name='Subscription',
user_last_name='foo-none bar-val2',
extra_data={'bar': 'val2'},
date_start=event.start_datetime,
date_end=event.start_datetime,
)
login(app)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert 'User none' in resp
assert 'User empty' in resp
assert 'User foo-val1' in resp
assert 'User foo-val1 bar-none' in resp
assert 'User foo-val2 bar-val1' in resp
assert 'User foo-val1 bar-val2' in resp
assert 'User foo-none bar-val2' in resp
assert 'Subscription none' in resp
assert 'Subscription empty' in resp
assert 'Subscription foo-val1 bar-none' in resp
assert 'Subscription foo-val2 bar-val1' in resp
assert 'Subscription foo-val1 bar-val2' in resp
assert 'Subscription foo-none bar-val2' in resp
resp = app.get(
'/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'unknown': 'unknown'}
@ -1451,6 +1611,12 @@ def test_event_check_filters(app, admin_user):
assert 'User foo-val2 bar-val1' in resp
assert 'User foo-val1 bar-val2' in resp
assert 'User foo-none bar-val2' in resp
assert 'Subscription none' in resp
assert 'Subscription empty' in resp
assert 'Subscription foo-val1 bar-none' in resp
assert 'Subscription foo-val2 bar-val1' in resp
assert 'Subscription foo-val1 bar-val2' in resp
assert 'Subscription foo-none bar-val2' in resp
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'foo': 'unknown'})
assert 'User none' in resp
@ -1459,6 +1625,12 @@ def test_event_check_filters(app, admin_user):
assert 'User foo-val2 bar-val1' in resp
assert 'User foo-val1 bar-val2' in resp
assert 'User foo-none bar-val2' in resp
assert 'Subscription none' in resp
assert 'Subscription empty' in resp
assert 'Subscription foo-val1 bar-none' in resp
assert 'Subscription foo-val2 bar-val1' in resp
assert 'Subscription foo-val1 bar-val2' in resp
assert 'Subscription foo-none bar-val2' in resp
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'foo': 'val1'})
assert 'User none' not in resp
@ -1467,6 +1639,12 @@ def test_event_check_filters(app, admin_user):
assert 'User foo-val2 bar-val1' not in resp
assert 'User foo-val1 bar-val2' in resp
assert 'User foo-none bar-val2' not in resp
assert 'Subscription none' not in resp
assert 'Subscription empty' not in resp
assert 'Subscription foo-val1 bar-none' in resp
assert 'Subscription foo-val2 bar-val1' not in resp
assert 'Subscription foo-val1 bar-val2' in resp
assert 'Subscription foo-none bar-val2' not in resp
resp = app.get(
'/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'foo': 'val1', 'bar': 'val2'}
@ -1477,6 +1655,12 @@ def test_event_check_filters(app, admin_user):
assert 'User foo-val2 bar-val1' not in resp
assert 'User foo-val1 bar-val2' in resp
assert 'User foo-none bar-val2' not in resp
assert 'Subscription none' not in resp
assert 'Subscription empty' not in resp
assert 'Subscription foo-val1 bar-none' not in resp
assert 'Subscription foo-val2 bar-val1' not in resp
assert 'Subscription foo-val1 bar-val2' in resp
assert 'Subscription foo-none bar-val2' not in resp
resp = app.get(
'/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'foo': 'val2', 'bar': 'val2'}
@ -1487,6 +1671,12 @@ def test_event_check_filters(app, admin_user):
assert 'User foo-val2 bar-val1' not in resp
assert 'User foo-val1 bar-val2' not in resp
assert 'User foo-none bar-val2' not in resp
assert 'Subscription none' not in resp
assert 'Subscription empty' not in resp
assert 'Subscription foo-val1 bar-none' not in resp
assert 'Subscription foo-val2 bar-val1' not in resp
assert 'Subscription foo-val1 bar-val2' not in resp
assert 'Subscription foo-none bar-val2' not in resp
def test_event_check_booking(app, admin_user):