diff --git a/chrono/manager/templates/chrono/manager_agenda_month_view.html b/chrono/manager/templates/chrono/manager_agenda_month_view.html index 976aafc5..8adcf605 100644 --- a/chrono/manager/templates/chrono/manager_agenda_month_view.html +++ b/chrono/manager/templates/chrono/manager_agenda_month_view.html @@ -26,56 +26,6 @@ {% trans 'Settings' %} {% endif %} {% trans 'Print' %} -{% trans 'Day view' %} -{% endblock %} +{% block extra-actions %}{% endblock %} - -{% block content %} -{% for week_days in view.get_timetable_infos %} -{% if forloop.first %} - - -{% endif %} - - - {% for day in week_days.days %} - - {% endfor %} - - {% for hour in week_days.periods %} - - - {% for day in week_days.days %} - - {% endfor %} - - {% endfor %} - {% resetcycle %} -{% if forloop.last %} - -
{% if not day.other_month %}{{ day.date|date:"l j" }}{% endif %}
{{ hour|date:"TIME_FORMAT" }} - {% if forloop.parentloop.first %} - {% for slot in day.infos.opening_hours %} -
- {% endfor %} - {% for slot in day.infos.booked_slots %} - - {% endfor %} - {% endif %} -
-{% endif %} - -{% empty %} -
-

{% trans "No opening hours this month." %}

-
-{% endfor %} - {% endblock %} diff --git a/chrono/manager/templates/chrono/manager_events_agenda_month_view.html b/chrono/manager/templates/chrono/manager_events_agenda_month_view.html new file mode 100644 index 00000000..08a2a533 --- /dev/null +++ b/chrono/manager/templates/chrono/manager_events_agenda_month_view.html @@ -0,0 +1,53 @@ +{% extends "chrono/manager_agenda_month_view.html" %} +{% load i18n %} + +{% block content %} +
+

{% trans "Events" %}

+
+{% if object_list %} + + {% include "gadjo/pagination.html" %} +{% else %} +
+ {% blocktrans %} + This month doesn't have any event configured. + {% endblocktrans %} +
+{% endif %} +
+
+ +{% endblock %} diff --git a/chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html b/chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html new file mode 100644 index 00000000..7f0b9637 --- /dev/null +++ b/chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html @@ -0,0 +1,56 @@ +{% extends "chrono/manager_agenda_month_view.html" %} +{% load i18n %} + +{% block extra-actions %} +{% trans 'Day view' %} +{% endblock %} + +{% block content %} +{% for week_days in view.get_timetable_infos %} +{% if forloop.first %} + + +{% endif %} + + + {% for day in week_days.days %} + + {% endfor %} + + {% for hour in week_days.periods %} + + + {% for day in week_days.days %} + + {% endfor %} + + {% endfor %} + {% resetcycle %} +{% if forloop.last %} + +
{% if not day.other_month %}{{ day.date|date:"l j" }}{% endif %}
{{ hour|date:"TIME_FORMAT" }} + {% if forloop.parentloop.first %} + {% for slot in day.infos.opening_hours %} +
+ {% endfor %} + {% for slot in day.infos.booked_slots %} + + {% endfor %} + {% endif %} +
+{% endif %} + +{% empty %} +
+

{% trans "No opening hours this month." %}

+
+{% endfor %} + +{% endblock %} diff --git a/chrono/manager/views.py b/chrono/manager/views.py index 3a695914..030f99ee 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -39,6 +39,7 @@ from django.views.generic import ( TemplateView, DayArchiveView, MonthArchiveView, + View, ) from chrono.agendas.models import ( @@ -160,17 +161,42 @@ class AgendasImportView(FormView): agendas_import = AgendasImportView.as_view() -class AgendaEditView(UpdateView): +class ViewableAgendaMixin(object): + agenda = None + + def dispatch(self, request, *args, **kwargs): + self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk')) + if not self.check_permissions(request.user): + raise PermissionDenied() + return super(ViewableAgendaMixin, self).dispatch(request, *args, **kwargs) + + def check_permissions(self, user): + return self.agenda.can_be_viewed(user) + + def get_context_data(self, **kwargs): + context = super(ViewableAgendaMixin, self).get_context_data(**kwargs) + context['agenda'] = self.agenda + return context + + +class ManagedAgendaMixin(ViewableAgendaMixin): + def check_permissions(self, user): + return self.agenda.can_be_managed(user) + + def get_initial(self): + initial = super(ManagedAgendaMixin, self).get_initial() + initial['agenda'] = self.agenda.id + return initial + + def get_success_url(self): + return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id}) + + +class AgendaEditView(ManagedAgendaMixin, UpdateView): template_name = 'chrono/manager_agenda_form.html' model = Agenda form_class = AgendaEditForm - def get_object(self, queryset=None): - obj = super(AgendaEditView, self).get_object(queryset=queryset) - if not obj.can_be_managed(self.request.user): - raise PermissionDenied() - return obj - def get_success_url(self): return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.object.id}) @@ -208,35 +234,44 @@ class AgendaDeleteView(DeleteView): agenda_delete = AgendaDeleteView.as_view() -class AgendaView(DetailView): - model = Agenda - +class AgendaView(ViewableAgendaMixin, View): def get(self, request, *args, **kwargs): - try: - agenda = Agenda.objects.get(id=kwargs.get('pk')) - except Agenda.DoesNotExist: - raise Http404() - if not agenda.can_be_viewed(self.request.user): - raise PermissionDenied() - - if agenda.kind == 'meetings': + today = datetime.date.today() + if self.agenda.kind == 'meetings': # redirect to today view - today = datetime.date.today() return HttpResponseRedirect( reverse( 'chrono-manager-agenda-day-view', - kwargs={'pk': agenda.id, 'year': today.year, 'month': today.month, 'day': today.day}, + kwargs={'pk': self.agenda.id, 'year': today.year, 'month': today.month, 'day': today.day}, ) ) - # redirect to settings - return HttpResponseRedirect(reverse('chrono-manager-agenda-settings', kwargs={'pk': agenda.id})) + if self.agenda.kind == 'events': + # redirect to monthly view, to first month where there are events, + # otherwise to latest month with events, otherwise to this month. + event = self.agenda.event_set.filter( + start_datetime__gte=datetime.date(today.year, today.month, 1) + ).first() + if not event: + event = self.agenda.event_set.filter( + start_datetime__lte=datetime.date(today.year, today.month, 1) + ).last() + if event: + day = event.start_datetime + else: + day = today + return HttpResponseRedirect( + reverse( + 'chrono-manager-agenda-month-view', + kwargs={'pk': self.agenda.id, 'year': day.year, 'month': day.month}, + ) + ) agenda_view = AgendaView.as_view() -class AgendaDateView(object): +class AgendaDateView(ViewableAgendaMixin): model = Event month_format = '%m' date_field = 'start_datetime' @@ -244,12 +279,6 @@ class AgendaDateView(object): allow_future = True def dispatch(self, request, *args, **kwargs): - self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk')) - if self.agenda.kind != 'meetings': - raise Http404() - if not self.agenda.can_be_viewed(request.user): - raise PermissionDenied() - # specify 6am time to get the expected timezone on daylight saving time # days. try: @@ -267,7 +296,7 @@ class AgendaDateView(object): return HttpResponseRedirect( reverse( 'chrono-manager-agenda-day-view', - kwargs={'pk': self.agenda.id, 'year': date.year, 'month': date.month, 'day': date.day}, + kwargs={'pk': kwargs['pk'], 'year': date.year, 'month': date.month, 'day': date.day}, ) ) return super(AgendaDateView, self).dispatch(request, *args, **kwargs) @@ -275,10 +304,11 @@ class AgendaDateView(object): def get_context_data(self, **kwargs): context = super(AgendaDateView, self).get_context_data(**kwargs) context['agenda'] = self.agenda - try: - context['hour_span'] = max(60 // self.agenda.get_base_meeting_duration(), 1) - except ValueError: # no meeting types defined - context['hour_span'] = 1 + if self.agenda.kind == 'meetings': + try: + context['hour_span'] = max(60 // self.agenda.get_base_meeting_duration(), 1) + except ValueError: # no meeting types defined + context['hour_span'] = 1 context['user_can_manage'] = self.agenda.can_be_managed(self.request.user) return context @@ -301,6 +331,11 @@ class AgendaDateView(object): class AgendaDayView(AgendaDateView, DayArchiveView): template_name = 'chrono/manager_agenda_day_view.html' + def dispatch(self, request, *args, **kwargs): + # day view should only exist for meetings kind. + get_object_or_404(Agenda, id=kwargs.get('pk'), kind='meetings') + return super(AgendaDayView, self).dispatch(request, *args, **kwargs) + def get_previous_day_url(self): previous_day = self.date.date() - datetime.timedelta(days=1) return reverse( @@ -385,7 +420,8 @@ agenda_day_view = AgendaDayView.as_view() class AgendaMonthView(AgendaDateView, MonthArchiveView): - template_name = 'chrono/manager_agenda_month_view.html' + def get_template_names(self): + return ['chrono/manager_%s_agenda_month_view.html' % self.agenda.kind] def get_previous_month_url(self): previous_month = self.get_previous_month(self.date.date()) @@ -518,32 +554,6 @@ class AgendaMonthView(AgendaDateView, MonthArchiveView): agenda_monthly_view = AgendaMonthView.as_view() -class ManagedAgendaMixin(object): - agenda = None - - def dispatch(self, request, *args, **kwargs): - try: - self.agenda = Agenda.objects.get(id=kwargs.get('pk')) - except Agenda.DoesNotExist: - raise Http404() - if not self.agenda.can_be_managed(request.user): - raise PermissionDenied() - return super(ManagedAgendaMixin, self).dispatch(request, *args, **kwargs) - - def get_context_data(self, **kwargs): - context = super(ManagedAgendaMixin, self).get_context_data(**kwargs) - context['agenda'] = self.agenda - return context - - def get_initial(self): - initial = super(ManagedAgendaMixin, self).get_initial() - initial['agenda'] = self.agenda.id - return initial - - def get_success_url(self): - return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.agenda.id}) - - class ManagedAgendaSubobjectMixin(object): agenda = None @@ -611,22 +621,9 @@ class ManagedDeskSubobjectMixin(object): class AgendaSettings(ManagedAgendaMixin, DetailView): model = Agenda - def dispatch(self, request, *args, **kwargs): - try: - self.agenda = Agenda.objects.get(id=kwargs.get('pk')) - except Agenda.DoesNotExist: - raise Http404() - if not self.agenda.can_be_managed(request.user): - # "events" agendas settings page can be access by user with the - # view permission as there are no other "view" page for this type - # of agenda. - if self.agenda.kind != 'events' or not self.agenda.can_be_viewed(request.user): - raise PermissionDenied() - return super(DetailView, self).dispatch(request, *args, **kwargs) - def get_context_data(self, **kwargs): context = super(AgendaSettings, self).get_context_data(**kwargs) - context['user_can_manage'] = self.get_object().can_be_managed(self.request.user) + context['user_can_manage'] = True return context def get_template_names(self): diff --git a/tests/test_manager.py b/tests/test_manager.py index 68933802..271ffe11 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -159,11 +159,8 @@ def test_view_agendas_as_manager(app, manager_user): # check user doesn't have access app.get('/manage/agendas/%s/' % agenda2.id, status=403) - # check view gives access to the settings page for "events" agenda - resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) - # but there's no links to actions - assert not '>New Event<' in resp.text - assert not '>Options<' in resp.text + # check there's no access to the settings page for "events" agenda + resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403) app.get('/manage/agendas/%s/add-event' % agenda.id, status=403) app.get('/manage/agendas/%s/edit' % agenda.id, status=403) @@ -207,6 +204,7 @@ def test_options_agenda(app, admin_user): app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() + resp = resp.click('Settings') resp = resp.click('Options') assert resp.form['label'].value == 'Foo bar' resp.form['label'] = 'Foo baz' @@ -225,7 +223,7 @@ def test_options_agenda_as_manager(app, manager_user): resp = app.get('/manage/', status=200) resp = resp.click('Foo bar') assert not 'Settings' in resp.text - resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) # ok for "events" agendas + resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403) resp = app.get('/manage/agendas/%s/edit' % agenda.id, status=403) agenda.kind = 'meetings' agenda.save() @@ -241,6 +239,7 @@ def test_options_agenda_as_manager(app, manager_user): resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() + resp = resp.click('Settings') resp = resp.click('Options') assert resp.form['label'].value == 'Foo bar' resp.form['label'] = 'Foo baz' @@ -257,6 +256,7 @@ def test_delete_agenda(app, admin_user): app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() + resp = resp.click('Settings') resp = resp.click('Delete') resp = resp.form.submit() assert resp.location.endswith('/manage/') @@ -273,6 +273,7 @@ def test_delete_busy_agenda(app, admin_user): app = login(app) resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() + resp = resp.click('Settings') resp = resp.click('Delete') assert 'Are you sure you want to delete this?' in resp.text @@ -280,6 +281,7 @@ def test_delete_busy_agenda(app, admin_user): booking.save() resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() + resp = resp.click('Settings') resp = resp.click('Delete') assert 'This cannot be removed' in resp.text @@ -287,6 +289,7 @@ def test_delete_busy_agenda(app, admin_user): booking.save() resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() + resp = resp.click('Settings') resp = resp.click('Delete') assert 'Are you sure you want to delete this?' in resp.text @@ -304,6 +307,7 @@ def test_delete_agenda_as_manager(app, manager_user): app = login(app, username='manager', password='manager') resp = app.get('/manage/', status=200) resp = resp.click('Foo bar').follow() + resp = resp.click('Settings') assert 'Options' in resp.text assert 'Delete' not in resp.text resp = app.get('/manage/agendas/%s/delete' % agenda.id, status=403) @@ -391,6 +395,7 @@ def test_add_event_as_manager(app, manager_user): agenda.save() resp = app.get('/manage/agendas/%s/' % agenda.id).follow() + resp = resp.click('Settings') assert '

Settings' in resp.text resp = resp.click('New Event') resp.form['start_datetime'] = '2016-02-15 17:00' @@ -1685,6 +1690,44 @@ def test_agenda_invalid_day_view(app, admin_user, manager_user, api_user): assert resp.location.endswith('2018/11/30/') +def test_agenda_events_month_view(app, admin_user, manager_user, api_user): + agenda = Agenda.objects.create(label='Events', kind='events') + + login(app) + resp = app.get('/manage/agendas/%s/' % agenda.id, status=302) + resp = resp.follow() + assert "This month doesn't have any event configured." in resp.text + today = datetime.date.today() + assert resp.request.url.endswith('%s/%s/' % (today.year, today.month)) + + # add event in a future month + event = Event(label='xyz', start_datetime=now() + datetime.timedelta(days=40), places=10, agenda=agenda) + event.save() + resp = app.get('/manage/agendas/%s/' % agenda.id).follow() + assert 'xyz' in resp.text + day = event.start_datetime + assert resp.request.url.endswith('%s/%s/' % (day.year, day.month)) + + # current month still doesn't have events + resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) + assert "This month doesn't have any event configured." in resp.text + + # add event in the past + event2 = Event(label='zyx', start_datetime=now() - datetime.timedelta(days=40), places=10, agenda=agenda) + event2.save() + resp = app.get('/manage/agendas/%s/' % agenda.id).follow() + assert 'xyz' in resp.text + day = event.start_datetime # still the future event + assert resp.request.url.endswith('%s/%s/' % (day.year, day.month)) + + # remove future event + event.delete() + resp = app.get('/manage/agendas/%s/' % agenda.id).follow() + assert 'zyx' in resp.text + day = event2.start_datetime # now the past event + assert resp.request.url.endswith('%s/%s/' % (day.year, day.month)) + + def test_agenda_month_view(app, admin_user, manager_user, api_user): agenda = Agenda.objects.create(label='Passeports', kind='meetings') desk = Desk.objects.create(agenda=agenda, label='Desk A')