From aec5b30fd9beab5e0fb4b072c5cc9ae7612c7b53 Mon Sep 17 00:00:00 2001 From: Emmanuel Cazenave Date: Wed, 26 Feb 2020 11:52:05 +0100 Subject: [PATCH] manager: add excluded timeperiods management (#40058) --- chrono/manager/forms.py | 9 +++ .../chrono/manager_time_period_form.html | 2 +- .../manager_virtual_agenda_settings.html | 16 +++++ chrono/manager/urls.py | 5 ++ chrono/manager/views.py | 69 ++++++++++++++++--- tests/test_manager.py | 65 ++++++++++++++++- 6 files changed, 154 insertions(+), 12 deletions(-) diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py index 43c84132..32fe2497 100644 --- a/chrono/manager/forms.py +++ b/chrono/manager/forms.py @@ -150,9 +150,18 @@ class TimePeriodForm(forms.ModelForm): 'start_time': widgets.TimeWidget(), 'end_time': widgets.TimeWidget(), 'desk': forms.HiddenInput(), + 'agenda': forms.HiddenInput(), } exclude = [] + def __init__(self, *args, **kwargs): + has_desk = kwargs.pop('has_desk') + super(TimePeriodForm, self).__init__(*args, **kwargs) + if has_desk: + del self.fields['agenda'] + else: + del self.fields['desk'] + def clean_end_time(self): if self.cleaned_data['end_time'] <= self.cleaned_data['start_time']: raise ValidationError(_('End time must come after start time.')) diff --git a/chrono/manager/templates/chrono/manager_time_period_form.html b/chrono/manager/templates/chrono/manager_time_period_form.html index 7b809338..da2ba2b5 100644 --- a/chrono/manager/templates/chrono/manager_time_period_form.html +++ b/chrono/manager/templates/chrono/manager_time_period_form.html @@ -33,7 +33,7 @@ {{ form.as_p }}
- {% trans 'Cancel' %} + {% trans 'Cancel' %}
{% endblock %} diff --git a/chrono/manager/templates/chrono/manager_virtual_agenda_settings.html b/chrono/manager/templates/chrono/manager_virtual_agenda_settings.html index d0b594a6..8cd930a6 100644 --- a/chrono/manager/templates/chrono/manager_virtual_agenda_settings.html +++ b/chrono/manager/templates/chrono/manager_virtual_agenda_settings.html @@ -2,6 +2,7 @@ {% load i18n %} {% block agenda-extra-management-actions %} + {% trans 'Add Excluded Period' %} {% trans 'Include Agenda' %} {% endblock %} @@ -59,5 +60,20 @@ {% endif %} +{% if agenda.excluded_timeperiods.count %} +
+

{% trans 'Excluded Periods' %}

+
+ +
+
+{% endif %} {% endblock %} diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py index 382a5c19..56b2b9f0 100644 --- a/chrono/manager/urls.py +++ b/chrono/manager/urls.py @@ -74,6 +74,11 @@ urlpatterns = [ views.agenda_add_time_period, name='chrono-manager-agenda-add-time-period', ), + url( + r'^agendas/(?P\d+)/add-time-period$', + views.virtual_agenda_add_time_period, + name='chrono-manager-virtual-agenda-add-time-period', + ), url(r'^timeperiods/(?P\d+)/edit$', views.time_period_edit, name='chrono-manager-time-period-edit'), url( r'^timeperiods/(?P\d+)/delete$', diff --git a/chrono/manager/views.py b/chrono/manager/views.py index 9bd751fd..1be8a407 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -680,6 +680,30 @@ class ManagedDeskSubobjectMixin(object): return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda.id}) +class ManagedTimePeriodMixin(object): + agenda = None + + def dispatch(self, request, *args, **kwargs): + self.time_period = self.get_object() + self.agenda = self.time_period.agenda + self.has_desk = False + if self.time_period.desk: + self.agenda = self.time_period.desk.agenda + self.has_desk = True + + if not self.agenda.can_be_managed(request.user): + raise PermissionDenied() + return super(ManagedTimePeriodMixin, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(ManagedTimePeriodMixin, self).get_context_data(**kwargs) + context['agenda'] = self.agenda + return context + + def get_success_url(self): + return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id}) + + class AgendaSettings(ManagedAgendaMixin, DetailView): model = Agenda @@ -891,35 +915,60 @@ class MeetingTypeDeleteView(ManagedAgendaSubobjectMixin, DeleteView): meeting_type_delete = MeetingTypeDeleteView.as_view() +def process_time_period_add_form(form, desk=None, agenda=None): + assert desk or agenda, "a time period requires a desk or a agenda" + for weekday in form.cleaned_data.get('weekdays'): + period = TimePeriod( + weekday=weekday, + start_time=form.cleaned_data['start_time'], + end_time=form.cleaned_data['end_time'], + ) + if desk: + period.desk = desk + elif agenda: + period.agenda = agenda + period.save() + + class AgendaAddTimePeriodView(ManagedDeskMixin, FormView): template_name = 'chrono/manager_time_period_form.html' form_class = TimePeriodAddForm def form_valid(self, form): - for weekday in form.cleaned_data.get('weekdays'): - period = TimePeriod( - weekday=weekday, - start_time=form.cleaned_data['start_time'], - end_time=form.cleaned_data['end_time'], - desk=self.desk, - ) - period.save() + process_time_period_add_form(form, desk=self.desk) return super(AgendaAddTimePeriodView, self).form_valid(form) agenda_add_time_period = AgendaAddTimePeriodView.as_view() -class TimePeriodEditView(ManagedDeskSubobjectMixin, UpdateView): +class VirtualAgendaAddTimePeriodView(ManagedAgendaMixin, FormView): + template_name = 'chrono/manager_time_period_form.html' + form_class = TimePeriodAddForm + + def form_valid(self, form): + process_time_period_add_form(form, agenda=self.agenda) + return super(VirtualAgendaAddTimePeriodView, self).form_valid(form) + + +virtual_agenda_add_time_period = VirtualAgendaAddTimePeriodView.as_view() + + +class TimePeriodEditView(ManagedTimePeriodMixin, UpdateView): template_name = 'chrono/manager_time_period_form.html' model = TimePeriod form_class = TimePeriodForm + def get_form_kwargs(self): + kwargs = super(TimePeriodEditView, self).get_form_kwargs() + kwargs['has_desk'] = self.has_desk + return kwargs + time_period_edit = TimePeriodEditView.as_view() -class TimePeriodDeleteView(ManagedDeskSubobjectMixin, DeleteView): +class TimePeriodDeleteView(ManagedTimePeriodMixin, DeleteView): template_name = 'chrono/manager_confirm_delete.html' model = TimePeriod diff --git a/tests/test_manager.py b/tests/test_manager.py index b7bee820..73d1a1c4 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -2251,9 +2251,12 @@ def test_virtual_agenda_settings_empty(app, admin_user): assert 'Export' in resp.text assert 'Delete' in resp.text assert 'Included Agendas' in resp.text + assert 'Add Excluded Period' in resp.text assert "This virtual agenda doesn't include any agenda yet" in resp.text # No meeting types yet assert 'Meeting Types' not in resp.text + # No absence yet + assert 'Excluded Periods' not in resp.text def test_virtual_agenda_settings(app, admin_user): @@ -2264,7 +2267,9 @@ def test_virtual_agenda_settings(app, admin_user): VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meeting_agenda_2) MeetingType.objects.create(agenda=meeting_agenda_1, label='MT', slug='mt', duration=10) mt2 = MeetingType.objects.create(agenda=meeting_agenda_2, label='MT', slug='mt', duration=10) - + TimePeriod.objects.create( + agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0) + ) app = login(app) resp = app.get('/manage/agendas/%s/settings' % agenda.pk) assert "This virtual agenda doesn't include any agenda yet" not in resp.text @@ -2277,6 +2282,9 @@ def test_virtual_agenda_settings(app, admin_user): assert 'mt' in resp.text assert '10' in resp.text + assert 'Excluded Periods' in resp.text + assert 'Monday' in resp.text + # Error message when incompatible meeting types mt2.delete() resp = app.get('/manage/agendas/%s/settings' % agenda.pk) @@ -2307,6 +2315,61 @@ def test_virtual_agenda_settings_include(app, admin_user): assert len(resp.form['real_agenda'].options) == 2 +def test_virtual_agenda_settings_add_excluded_period(app, admin_user): + agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual') + + app = login(app) + resp = app.get('/manage/agendas/%s/settings' % agenda.pk) + resp = resp.click('Add Excluded Period') + + resp.form['weekdays-0'].checked = True + resp.form['start_time'] = '10:00' + resp.form['end_time'] = '17:00' + resp = resp.form.submit() + tp = TimePeriod.objects.get(agenda=agenda) + assert tp.weekday == 0 + assert tp.start_time.hour == 10 + assert tp.start_time.minute == 0 + assert tp.end_time.hour == 17 + assert tp.end_time.minute == 0 + + resp = resp.follow() + assert u'Monday / 10 a.m. → 5 p.m.' in resp.text + + +def test_virtual_agenda_settings_edit_excluded_period(app, admin_user): + agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual') + tp = TimePeriod.objects.create( + agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0) + ) + app = login(app) + resp = app.get('/manage/agendas/%s/settings' % agenda.pk) + url = '/manage/timeperiods/%s/edit' % tp.pk + resp = resp.click(href=url) + resp.form['start_time'] = '11:00' + resp = resp.form.submit() + tp = TimePeriod.objects.get(agenda=agenda) + assert tp.weekday == 0 + assert tp.start_time.hour == 11 + assert tp.start_time.minute == 0 + assert tp.end_time.hour == 18 + assert tp.end_time.minute == 0 + + +def test_virtual_agenda_settings_delete_excluded_period(app, admin_user): + agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual') + tp = TimePeriod.objects.create( + agenda=agenda, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0) + ) + app = login(app) + resp = app.get('/manage/agendas/%s/settings' % agenda.pk) + url = '/manage/timeperiods/%s/delete' % tp.pk + resp = resp.click(href=url) + resp = resp.form.submit() + assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id) + assert TimePeriod.objects.count() == 0 + + def test_virtual_agenda_settings_include_incompatible_agenda(app, admin_user): agenda = Agenda.objects.create(label='My Virtual agenda', kind='virtual') meeting_agenda_1 = Agenda.objects.create(label='Meeting agenda 1', kind='meetings')