manager: event check page (#51535)
This commit is contained in:
parent
eb9b9f98ca
commit
ca514f6d11
|
@ -1176,6 +1176,9 @@ class Event(models.Model):
|
|||
return False
|
||||
return True
|
||||
|
||||
def is_day_past(self):
|
||||
return self.start_datetime.date() <= now().date()
|
||||
|
||||
@staticmethod
|
||||
def annotate_queryset(qs):
|
||||
if django.VERSION < (2, 0):
|
||||
|
|
|
@ -31,6 +31,7 @@ from django.utils.timezone import now, make_aware, make_naive
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from chrono.agendas.models import (
|
||||
AbsenceReason,
|
||||
Agenda,
|
||||
Booking,
|
||||
Event,
|
||||
|
@ -224,6 +225,18 @@ class EventForm(forms.ModelForm):
|
|||
return self.instance
|
||||
|
||||
|
||||
class BookingAbsenceReasonForm(forms.Form):
|
||||
reason = forms.ChoiceField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
agenda = kwargs.pop('agenda')
|
||||
super().__init__(*args, **kwargs)
|
||||
if agenda.absence_reasons_group:
|
||||
self.fields['reason'].choices = [('', '---------')] + [
|
||||
(r.label, r.label) for r in agenda.absence_reasons_group.absence_reasons.all()
|
||||
]
|
||||
|
||||
|
||||
class AgendaResourceForm(forms.Form):
|
||||
resource = forms.ModelChoiceField(label=_('Resource'), queryset=Resource.objects.none())
|
||||
|
||||
|
|
|
@ -9,22 +9,24 @@ function autorefresh() {
|
|||
}
|
||||
|
||||
$(function() {
|
||||
/* refresh every 30 seconds (idle_id) after any user activity
|
||||
* on inactivity for more than 5 minutes (longidle_id), stop refreshing (clear idle_id)
|
||||
*/
|
||||
var idle_id = null;
|
||||
var longidle_id = null;
|
||||
$(window).on('mousemove keydown mousedown touchstart', function() {
|
||||
/* if refresh timer exists, clear it */
|
||||
if (idle_id) window.clearInterval(idle_id);
|
||||
/* if stop refreshing timer exists, clear it */
|
||||
if (longidle_id) window.clearTimeout(longidle_id);
|
||||
/* launch timer to refresh every 30 seconds */
|
||||
idle_id = setInterval(autorefresh, 30000);
|
||||
/* launch timer to stop refreshing after 5 minutes idle */
|
||||
longidle_id = setTimeout(function () {
|
||||
if (idle_id) idle_id = window.clearInterval(idle_id);
|
||||
longidle_id = undefined;
|
||||
}, 300 * 1000);
|
||||
});
|
||||
if ($('#event_details')) {
|
||||
/* refresh every 30 seconds (idle_id) after any user activity
|
||||
* on inactivity for more than 5 minutes (longidle_id), stop refreshing (clear idle_id)
|
||||
*/
|
||||
var idle_id = null;
|
||||
var longidle_id = null;
|
||||
$(window).on('mousemove keydown mousedown touchstart', function() {
|
||||
/* if refresh timer exists, clear it */
|
||||
if (idle_id) window.clearInterval(idle_id);
|
||||
/* if stop refreshing timer exists, clear it */
|
||||
if (longidle_id) window.clearTimeout(longidle_id);
|
||||
/* launch timer to refresh every 30 seconds */
|
||||
idle_id = setInterval(autorefresh, 30000);
|
||||
/* launch timer to stop refreshing after 5 minutes idle */
|
||||
longidle_id = setTimeout(function () {
|
||||
if (idle_id) idle_id = window.clearInterval(idle_id);
|
||||
longidle_id = undefined;
|
||||
}, 300 * 1000);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
{% extends "chrono/manager_event_detail.html" %}
|
||||
{% load staticfiles i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-event-check' pk=agenda.pk event_pk=object.pk %}">{% trans "Check" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar_actions %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<h3>
|
||||
{% blocktrans with places=object.places booked_places=booked|length %}Bookings ({{ booked_places }}/{{ places }}){% endblocktrans %}
|
||||
</h3>
|
||||
<div>
|
||||
<table class="main">
|
||||
<tbody>
|
||||
{% for booking in booked %}
|
||||
<tr>
|
||||
<td>{{ booking.user_name|default:booking.label|default:_('Unknown') }}</td>
|
||||
<td>
|
||||
{% trans "Status:" %} {{ booking.user_was_present|yesno:_('Present,Absent,Unknown') }}
|
||||
{% if booking.user_was_present is False %}
|
||||
<br />
|
||||
{{ booking.user_absence_reason }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if booking.user_was_present is not True %}
|
||||
<form method="post" action="{% url 'chrono-manager-booking-presence' pk=agenda.pk booking_pk=booking.pk %}">
|
||||
{% csrf_token %}
|
||||
<button class="submit-button">{% trans "Presence" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if booking.user_was_present is not False or booking.form.reason.field.choices.1 %}
|
||||
<form method="post" action="{% url 'chrono-manager-booking-absence' pk=agenda.pk booking_pk=booking.pk %}">
|
||||
{% csrf_token %}
|
||||
{% if booking.form.reason.field.choices.1 %}{{ booking.form.reason }}{% endif %}
|
||||
<button class="submit-button">{% if booking.user_was_present is False %}{% trans "Set absence reason" %}{% else %}{% trans "Absence" %}{% endif %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.waiting_list_places %}
|
||||
<div class="section">
|
||||
<h3>
|
||||
{% blocktrans with places=object.waiting_list_places booked_places=waiting|length %}Waiting List ({{ booked_places }}/{{ places }}){% endblocktrans %}
|
||||
</h3>
|
||||
<div>
|
||||
<ul class="objects-list single-links">
|
||||
{% for booking in waiting %}
|
||||
<li><a>{{ booking.user_name|default:booking.label|default:_('Unknown') }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -27,6 +27,7 @@
|
|||
({{ object.cancellation_status }})
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% block appbar_actions %}
|
||||
<span class="actions">
|
||||
{% if user_can_manage %}
|
||||
{% if not object.primary_event %}
|
||||
|
@ -35,6 +36,9 @@
|
|||
{% if not event.cancellation_status %}
|
||||
<a rel="popup" href="{% url 'chrono-manager-event-cancel' pk=agenda.pk event_pk=event.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a>
|
||||
{% endif %}
|
||||
{% if object.is_day_past and not object.cancelled %}
|
||||
<a href="{% url 'chrono-manager-event-check' pk=agenda.pk event_pk=object.pk %}">{% trans "Check" %}</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'chrono-manager-event-edit' pk=agenda.id event_pk=object.id %}">{% trans "Options" %}</a>
|
||||
{% endif %}
|
||||
{% if object.agenda.booking_form_url %}
|
||||
|
@ -42,6 +46,7 @@
|
|||
{% endif %}
|
||||
</span>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="event_details">
|
||||
|
|
|
@ -206,6 +206,11 @@ urlpatterns = [
|
|||
views.event_cancel,
|
||||
name='chrono-manager-event-cancel',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/check$',
|
||||
views.event_check,
|
||||
name='chrono-manager-event-check',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/event_cancellation_report/(?P<report_pk>\d+)/$',
|
||||
views.event_cancellation_report,
|
||||
|
@ -331,6 +336,16 @@ urlpatterns = [
|
|||
views.booking_cancel,
|
||||
name='chrono-manager-booking-cancel',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/bookings/(?P<booking_pk>\d+)/presence$',
|
||||
views.booking_presence,
|
||||
name='chrono-manager-booking-presence',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/bookings/(?P<booking_pk>\d+)/absence$',
|
||||
views.booking_absence,
|
||||
name='chrono-manager-booking-absence',
|
||||
),
|
||||
url(
|
||||
r'^agendas/events.csv$',
|
||||
views.agenda_import_events_sample_csv,
|
||||
|
|
|
@ -106,6 +106,7 @@ from .forms import (
|
|||
UnavailabilityCalendarAddForm,
|
||||
UnavailabilityCalendarEditForm,
|
||||
AgendasExportForm,
|
||||
BookingAbsenceReasonForm,
|
||||
)
|
||||
from .utils import import_site, export_site
|
||||
|
||||
|
@ -1898,6 +1899,41 @@ class EventDeleteView(ManagedAgendaMixin, DeleteView):
|
|||
event_delete = EventDeleteView.as_view()
|
||||
|
||||
|
||||
class EventCheckView(ViewableAgendaMixin, DetailView):
|
||||
template_name = 'chrono/manager_event_check.html'
|
||||
model = Event
|
||||
pk_url_kwarg = 'event_pk'
|
||||
|
||||
def set_agenda(self, **kwargs):
|
||||
self.agenda = get_object_or_404(
|
||||
Agenda.objects.prefetch_related('absence_reasons_group__absence_reasons'),
|
||||
pk=kwargs.get('pk'),
|
||||
kind='events',
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
return queryset.filter(agenda=self.agenda, start_datetime__date__lte=now().date(), cancelled=False)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
event = self.object
|
||||
context['booked'] = event.booking_set.filter(
|
||||
cancellation_datetime__isnull=True, in_waiting_list=False
|
||||
).order_by('user_name')
|
||||
context['waiting'] = event.booking_set.filter(
|
||||
cancellation_datetime__isnull=True, in_waiting_list=True
|
||||
).order_by('user_name')
|
||||
for booking in context['booked']:
|
||||
booking.form = BookingAbsenceReasonForm(
|
||||
agenda=self.agenda, initial={'reason': booking.user_absence_reason}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
event_check = EventCheckView.as_view()
|
||||
|
||||
|
||||
class AgendaAddResourceView(ManagedAgendaMixin, FormView):
|
||||
template_name = 'chrono/manager_agenda_resource_form.html'
|
||||
form_class = AgendaResourceForm
|
||||
|
@ -2560,6 +2596,65 @@ class BookingCancelView(ViewableAgendaMixin, UpdateView):
|
|||
booking_cancel = BookingCancelView.as_view()
|
||||
|
||||
|
||||
class BookingPresenceView(ViewableAgendaMixin, View):
|
||||
def post(self, request, *args, **kwargs):
|
||||
booking = get_object_or_404(
|
||||
Booking,
|
||||
pk=kwargs['booking_pk'],
|
||||
event__agenda=self.agenda,
|
||||
event__start_datetime__date__lte=now().date(),
|
||||
event__cancelled=False,
|
||||
cancellation_datetime__isnull=True,
|
||||
in_waiting_list=False,
|
||||
)
|
||||
booking.user_absence_reason = ''
|
||||
booking.user_was_present = True
|
||||
booking.save()
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'chrono-manager-event-check',
|
||||
kwargs={'pk': booking.event.agenda_id, 'event_pk': booking.event.pk},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
booking_presence = BookingPresenceView.as_view()
|
||||
|
||||
|
||||
class BookingAbsenceView(ViewableAgendaMixin, FormView):
|
||||
form_class = BookingAbsenceReasonForm
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['agenda'] = self.agenda
|
||||
return kwargs
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
booking = get_object_or_404(
|
||||
Booking,
|
||||
pk=kwargs['booking_pk'],
|
||||
event__agenda=self.agenda,
|
||||
event__start_datetime__lt=now(),
|
||||
event__cancelled=False,
|
||||
cancellation_datetime__isnull=True,
|
||||
in_waiting_list=False,
|
||||
)
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
booking.user_absence_reason = form.cleaned_data['reason']
|
||||
booking.user_was_present = False
|
||||
booking.save()
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'chrono-manager-event-check',
|
||||
kwargs={'pk': booking.event.agenda_id, 'event_pk': booking.event.pk},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
booking_absence = BookingAbsenceView.as_view()
|
||||
|
||||
|
||||
class EventCancelView(ViewableAgendaMixin, UpdateView):
|
||||
template_name = 'chrono/manager_confirm_event_cancellation.html'
|
||||
model = Event
|
||||
|
|
|
@ -15,7 +15,7 @@ from django.test.utils import CaptureQueriesContext
|
|||
from django.utils.timezone import localtime, make_aware, now
|
||||
from webtest import Upload
|
||||
|
||||
from chrono.agendas.models import Agenda, Booking, Event
|
||||
from chrono.agendas.models import Agenda, Booking, Event, AbsenceReasonGroup, AbsenceReason
|
||||
|
||||
from .test_all import login
|
||||
|
||||
|
@ -1047,3 +1047,186 @@ def test_booking_cancellation_events_agenda(app, admin_user):
|
|||
secondary.refresh_from_db()
|
||||
assert primary.cancellation_datetime
|
||||
assert secondary.cancellation_datetime
|
||||
|
||||
|
||||
def test_event_check(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
agenda2 = Agenda.objects.create(label='Events', kind='events')
|
||||
event = Event.objects.create(
|
||||
label='xyz',
|
||||
start_datetime=now() + datetime.timedelta(days=1),
|
||||
places=10,
|
||||
waiting_list_places=5,
|
||||
agenda=agenda,
|
||||
)
|
||||
booking1 = Booking.objects.create(event=event, user_name='User 42')
|
||||
Booking.objects.create(event=event, user_name='User 1')
|
||||
Booking.objects.create(event=event, user_name='User 17')
|
||||
Booking.objects.create(event=event, user_name='User 35')
|
||||
Booking.objects.create(event=event, user_name='User 5')
|
||||
booking6 = Booking.objects.create(event=event, user_name='User Cancelled')
|
||||
booking6.cancel()
|
||||
booking7 = Booking.objects.create(event=event, user_name='User Waiting', in_waiting_list=True)
|
||||
login(app)
|
||||
|
||||
# event not in past
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) not in resp
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), status=404)
|
||||
|
||||
event.start_datetime = localtime(now()).replace(hour=22, minute=0) # it'os ok all the day
|
||||
event.save()
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) in resp
|
||||
|
||||
# unknown agenda
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (0, event.pk), status=404)
|
||||
# wrong agenda
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (agenda2.pk, event.pk), status=404)
|
||||
|
||||
resp = resp.click('Check')
|
||||
assert 'Bookings (5/10)' in resp
|
||||
assert 'Waiting List (1/5)' in resp
|
||||
assert (
|
||||
resp.text.index('Bookings (5/10)')
|
||||
< resp.text.index('User 1')
|
||||
< resp.text.index('User 17')
|
||||
< resp.text.index('User 35')
|
||||
< resp.text.index('User 42')
|
||||
< resp.text.index('User 5')
|
||||
< resp.text.index('Waiting List (1/5)')
|
||||
< resp.text.index('User Waiting')
|
||||
) # user ordering is not optimal ...
|
||||
assert 'User Cancelled' not in resp
|
||||
|
||||
# cancelled booking
|
||||
token = resp.context['csrf_token']
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking6.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking6.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# booking in waiting list
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking7.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking7.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# unknown agenda
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (0, booking1.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (0, booking1.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
# wrong agenda
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda2.pk, booking1.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda2.pk, booking1.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# cancelled event
|
||||
event.cancel()
|
||||
resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk) not in resp
|
||||
app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), status=404)
|
||||
event.cancellation_datetime = None
|
||||
|
||||
|
||||
def test_event_check_booking(app, admin_user):
|
||||
group = AbsenceReasonGroup.objects.create(label='Foo bar')
|
||||
agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
event = Event.objects.create(
|
||||
label='xyz',
|
||||
start_datetime=now() - datetime.timedelta(days=1),
|
||||
places=10,
|
||||
waiting_list_places=5,
|
||||
agenda=agenda,
|
||||
)
|
||||
booking = Booking.objects.create(event=event, user_name='User 42')
|
||||
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert 'Status: Unknown' in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp
|
||||
assert 'Presence' in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
assert 'Absence' in resp
|
||||
|
||||
# set as present
|
||||
token = resp.context['csrf_token']
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
).follow()
|
||||
assert 'Status: Present' in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) not in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
assert 'Absence' in resp
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_was_present is True
|
||||
assert booking.user_absence_reason == ''
|
||||
|
||||
# set as absent without reason
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
).follow()
|
||||
assert 'Status: Absent' in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk) in resp
|
||||
assert 'Presence' in resp
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) not in resp
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_was_present is False
|
||||
assert booking.user_absence_reason == ''
|
||||
|
||||
agenda.absence_reasons_group = group
|
||||
agenda.save()
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) not in resp
|
||||
|
||||
AbsenceReason.objects.create(label='Foo reason', group=group)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk) in resp
|
||||
assert 'Set absence reason' in resp
|
||||
|
||||
# set as absent with reason
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/bookings/%s/absence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token, 'reason': 'Foo reason'},
|
||||
).follow()
|
||||
assert 'Foo reason' in resp
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_was_present is False
|
||||
assert booking.user_absence_reason == 'Foo reason'
|
||||
|
||||
# set as present
|
||||
app.post(
|
||||
'/manage/agendas/%s/bookings/%s/presence' % (agenda.pk, booking.pk),
|
||||
params={'csrfmiddlewaretoken': token},
|
||||
)
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_was_present is True
|
||||
assert booking.user_absence_reason == ''
|
||||
|
|
Loading…
Reference in New Issue