manager: show when an event is checked (#53707)

This commit is contained in:
Lauréline Guérin 2021-05-21 10:29:20 +02:00
parent dd424bc388
commit 3ba5dac136
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
12 changed files with 180 additions and 12 deletions

View File

@ -0,0 +1,24 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agendas', '0094_change_recurrence_fields'),
]
operations = [
migrations.AddField(
model_name='agenda',
name='mark_event_checked_auto',
field=models.BooleanField(
default=False,
verbose_name='Automatically mark event as checked when all bookings have been checked',
),
),
migrations.AddField(
model_name='event',
name='checked',
field=models.BooleanField(default=False),
),
]

View File

@ -205,6 +205,9 @@ class Agenda(models.Model):
_('Booking form URL'), max_length=200, blank=True, validators=[django_template_validator]
)
desk_simple_management = models.BooleanField(default=False)
mark_event_checked_auto = models.BooleanField(
_('Automatically mark event as checked when all bookings have been checked'), default=False
)
booking_check_filters = models.CharField(
_('Filters'),
max_length=250,
@ -222,7 +225,11 @@ class Agenda(models.Model):
max_length=256,
blank=True,
help_text=_(
'By default event labels will be displayed to users. This allows for a custom template to include additional informations. For example, "{{ event.label }} - {{ event.start_datetime }}" will show event datetime after label. Available variables: event.label (label), event.start_datetime (start date/time), event.places (places), event.remaining_places (remaining places), event.duration (duration), event.pricing (pricing).'
'By default event labels will be displayed to users. '
'This allows for a custom template to include additional informations. '
'For example, "{{ event.label }} - {{ event.start_datetime }}" will show event datetime after label. '
'Available variables: event.label (label), event.start_datetime (start date/time), event.places (places), '
'event.remaining_places (remaining places), event.duration (duration), event.pricing (pricing).'
),
)
@ -1263,6 +1270,7 @@ class Event(models.Model):
full = models.BooleanField(default=False)
cancelled = models.BooleanField(default=False)
cancellation_scheduled = models.BooleanField(default=False)
checked = models.BooleanField(default=False)
meeting_type = models.ForeignKey(MeetingType, null=True, on_delete=models.CASCADE)
desk = models.ForeignKey('Desk', null=True, on_delete=models.CASCADE)
resources = models.ManyToManyField('Resource')
@ -1312,6 +1320,21 @@ class Event(models.Model):
)
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
if self.checked:
return
booking_qs = self.booking_set.filter(
cancellation_datetime__isnull=True,
in_waiting_list=False,
user_was_present__isnull=True,
)
if booking_qs.exists():
return
self.checked = True
self.save(update_fields=['checked'])
def in_bookable_period(self):
if self.publication_date and localtime(now()).date() < self.publication_date:
return False
@ -1772,6 +1795,18 @@ class Booking(models.Model):
self.secondary_booking_set.update(in_waiting_list=True)
self.save()
def mark_user_absence(self, reason=None):
self.user_absence_reason = reason
self.user_was_present = False
self.save()
self.event.set_is_checked()
def mark_user_presence(self):
self.user_absence_reason = ''
self.user_was_present = True
self.save()
self.event.set_is_checked()
def get_user_block(self):
template_vars = Context(settings.TEMPLATE_VARS)
template_vars.update(
@ -2640,7 +2675,8 @@ class AgendaReminderSettings(models.Model):
choices=CHOICES,
verbose_name=_('Send reminder'),
help_text=_(
'In order to prevent users from getting a reminder shortly after booking, a reminder is sent less only if at least 12 hours have elapsed since booking time.'
'In order to prevent users from getting a reminder shortly after booking, '
'a reminder is sent less only if at least 12 hours have elapsed since booking time.'
),
)
send_email = models.BooleanField(default=False, verbose_name=_('Notify by email'))

View File

@ -1609,6 +1609,7 @@ class BookingAPI(APIView):
serializer.save()
if 'user_was_present' in request.data:
self.booking.secondary_booking_set.update(user_was_present=self.booking.user_was_present)
self.booking.event.set_is_checked()
if 'user_absence_reason' in request.data:
self.booking.secondary_booking_set.update(user_absence_reason=self.booking.user_absence_reason)
if 'extra_data' in request.data:

View File

@ -832,7 +832,12 @@ class EventCancelForm(forms.ModelForm):
class AgendaBookingCheckSettingsForm(forms.ModelForm):
class Meta:
model = Agenda
fields = ['absence_reasons_group', 'booking_check_filters', 'booking_user_block_template']
fields = [
'absence_reasons_group',
'booking_check_filters',
'booking_user_block_template',
'mark_event_checked_auto',
]
widgets = {'booking_user_block_template': forms.Textarea(attrs={'rows': 3})}
def __init__(self, *args, **kwargs):

View File

@ -11,8 +11,13 @@
><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 %}
<span class="tag">{{ event.cancellation_status }}</span>
{% elif event.main_list_full %}
<span class="full tag">{% trans "Full" %}</span>
{% else %}
{% if event.main_list_full %}
<span class="full tag">{% trans "Full" %}</span>
{% endif %}
{% if event.checked %}
<span class="checked tag">{% trans "Checked" %}</span>
{% endif %}
{% endif %}
<span class="event-info">
{% if view_mode == 'settings_view' %}

View File

@ -27,6 +27,16 @@
</form>
<table class="main check-bookings">
<tbody>
{% if booked and not event.checked %}
<tr class="booking all-bookings">
<td class="booking-actions">
<form method="post" action="{% url 'chrono-manager-event-checked' pk=agenda.pk event_pk=object.pk %}">
{% csrf_token %}
<button class="submit-button">{% trans "Mark the event as checked" %}</button>
</form>
</td>
</tr>
{% endif %}
{% if booked_without_status %}
<tr class="booking all-bookings">
<td colspan="2"><b>{% trans "Mark all bookings without status:" %}</b></td>

View File

@ -61,8 +61,10 @@
{% endif %}
{% endwith %}
<p>{% trans "User block template" %}:</p>
<p>{% trans "User block template:" %}</p>
<pre>{{ agenda.get_booking_user_block_template }}</pre>
<p>{% trans "Automatically mark events when they are checked:" %} {{ agenda.mark_event_checked_auto|yesno }}</p>
</div>
</div>

View File

@ -221,6 +221,11 @@ urlpatterns = [
views.event_absence,
name='chrono-manager-event-absence',
),
url(
r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/checked$',
views.event_checked,
name='chrono-manager-event-checked',
),
url(
r'^agendas/(?P<pk>\d+)/event_cancellation_report/(?P<report_pk>\d+)/$',
views.event_cancellation_report,

View File

@ -2024,6 +2024,7 @@ class EventPresenceView(EventCheckMixin, ViewableAgendaMixin, View):
def post(self, request, *args, **kwargs):
bookings = self.get_bookings()
bookings.update(user_absence_reason='', user_was_present=True)
self.event.set_is_checked()
return self.response(request)
@ -2045,12 +2046,24 @@ class EventAbsenceView(EventCheckMixin, ViewableAgendaMixin, FormView):
qs_kwargs['user_absence_reason'] = form.cleaned_data['reason']
bookings = self.get_bookings()
bookings.update(user_was_present=False, **qs_kwargs)
self.event.set_is_checked()
return self.response(request)
event_absence = EventAbsenceView.as_view()
class EventCheckedView(EventCheckMixin, ViewableAgendaMixin, View):
def post(self, request, *args, **kwargs):
if not self.event.checked:
self.event.checked = True
self.event.save(update_fields=['checked'])
return self.response(request)
event_checked = EventCheckedView.as_view()
class AgendaAddResourceView(ManagedAgendaMixin, FormView):
template_name = 'chrono/manager_agenda_resource_form.html'
form_class = AgendaResourceForm
@ -2750,9 +2763,7 @@ class BookingCheckMixin(object):
class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, View):
def post(self, request, *args, **kwargs):
booking = self.get_booking(**kwargs)
booking.user_absence_reason = ''
booking.user_was_present = True
booking.save()
booking.mark_user_presence()
return self.response(request, booking)
@ -2770,10 +2781,10 @@ class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
def post(self, request, *args, **kwargs):
booking = self.get_booking(**kwargs)
form = self.get_form()
reason = None
if form.is_valid():
booking.user_absence_reason = form.cleaned_data['reason']
booking.user_was_present = False
booking.save()
reason = form.cleaned_data['reason']
booking.mark_user_absence(reason=reason)
return self.response(request, booking)

View File

@ -410,6 +410,7 @@ def test_booking_patch_api_present(app, user, flag):
agenda = Agenda.objects.create(kind='events')
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
booking = Booking.objects.create(event=event)
assert agenda.mark_event_checked_auto is False
app.authorization = ('Basic', ('john.doe', 'password'))
@ -417,6 +418,16 @@ def test_booking_patch_api_present(app, user, flag):
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_was_present': flag})
booking.refresh_from_db()
assert booking.user_was_present == flag
event.refresh_from_db()
assert event.checked is False
agenda.mark_event_checked_auto = True
agenda.save()
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_was_present': flag})
booking.refresh_from_db()
assert booking.user_was_present == flag
event.refresh_from_db()
assert event.checked == (flag is not None)
# reset
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_was_present': None})

View File

@ -562,6 +562,14 @@ def test_options_agenda_booking_check_options(app, admin_user):
assert agenda.booking_check_filters == 'foo,bar,baz'
assert agenda.get_booking_check_filters() == ['foo', 'bar', 'baz']
# check auto checked
assert agenda.mark_event_checked_auto is False
resp = app.get(url)
resp.form['mark_event_checked_auto'] = True
resp = resp.form.submit()
agenda.refresh_from_db()
assert agenda.mark_event_checked_auto is True
# check kind
agenda.kind = 'meetings'
agenda.save()

View File

@ -1186,6 +1186,40 @@ def test_event_check(app, admin_user):
event.cancellation_datetime = None
def test_event_checked(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', booking_check_filters='foo,bar')
event = Event.objects.create(
label='xyz',
start_datetime=now() - datetime.timedelta(days=1),
places=10,
agenda=agenda,
)
login(app)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert 'Mark the event as checked' not in resp
Booking.objects.create(event=event, user_first_name='User')
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert 'Mark the event as checked' in resp
assert event.checked is False
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'checked tag' not in resp
token = resp.context['csrf_token']
resp = app.post(
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
params={'csrfmiddlewaretoken': token},
)
assert resp.location.endswith('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
event.refresh_from_db()
assert event.checked is True
resp = resp.follow()
assert 'Mark the event as checked' not in resp
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
assert 'checked tag' in resp
def test_event_check_filters(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events', booking_check_filters='foo,bar')
event = Event.objects.create(
@ -1282,6 +1316,7 @@ def test_event_check_booking(app, admin_user):
agenda=agenda,
)
booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42')
assert agenda.mark_event_checked_auto is False
login(app)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
@ -1302,6 +1337,11 @@ def test_event_check_booking(app, admin_user):
booking.refresh_from_db()
assert booking.user_was_present is True
assert booking.user_absence_reason == ''
event.refresh_from_db()
assert event.checked is False
agenda.mark_event_checked_auto = True
agenda.save()
# set as absent without reason
resp = app.post(
@ -1314,6 +1354,8 @@ def test_event_check_booking(app, admin_user):
booking.refresh_from_db()
assert booking.user_was_present is False
assert booking.user_absence_reason == ''
event.refresh_from_db()
assert event.checked is True
agenda.absence_reasons_group = group
agenda.save()
@ -1396,6 +1438,7 @@ def test_event_check_all_bookings(app, admin_user):
agenda=agenda,
)
booking1 = Booking.objects.create(event=event, user_first_name='User', user_last_name='42')
assert agenda.mark_event_checked_auto is False
login(app)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
@ -1410,6 +1453,11 @@ def test_event_check_all_bookings(app, admin_user):
booking1.refresh_from_db()
assert booking1.user_was_present is False
assert booking1.user_absence_reason == ''
event.refresh_from_db()
assert event.checked is False
agenda.mark_event_checked_auto = True
agenda.save()
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert 'Mark all bookings without status' not in resp
@ -1431,6 +1479,8 @@ def test_event_check_all_bookings(app, admin_user):
booking2.refresh_from_db()
assert booking2.user_was_present is True
assert booking2.user_absence_reason == ''
event.refresh_from_db()
assert event.checked is True
booking3 = Booking.objects.create(event=event, user_first_name='User', user_last_name='51')
app.post(