From a123a9b69f3af478e7986c61104dc135a24c709e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sat, 22 Sep 2018 16:25:46 +0200 Subject: [PATCH] manager: add UI to export/import agendas (#25985) --- chrono/agendas/models.py | 1 + chrono/manager/forms.py | 4 ++ .../templates/chrono/agendas_import.html | 17 ++++++ .../chrono/manager_agenda_settings.html | 1 + .../templates/chrono/manager_home.html | 1 + chrono/manager/urls.py | 4 ++ chrono/manager/utils.py | 8 ++- chrono/manager/views.py | 53 ++++++++++++++++++- tests/test_manager.py | 53 +++++++++++++++++++ 9 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 chrono/manager/templates/chrono/agendas_import.html diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 16a2f06..fc09bb2 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -153,6 +153,7 @@ class Agenda(models.Model): for desk in desks: desk['agenda'] = agenda Desk.import_json(desk).save() + return created WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0]) diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py index 47bc8c5..296856f 100644 --- a/chrono/manager/forms.py +++ b/chrono/manager/forms.py @@ -201,3 +201,7 @@ class ExceptionsImportForm(forms.ModelForm): help_text=_('ICS file containing events which will be considered as exceptions.')) ics_url = forms.URLField(label=_('URL'), required=False, help_text=_('URL to remote calendar which will be synchronised hourly.')) + + +class AgendasImportForm(forms.Form): + agendas_json = forms.FileField(label=_('Agendas Export File')) diff --git a/chrono/manager/templates/chrono/agendas_import.html b/chrono/manager/templates/chrono/agendas_import.html new file mode 100644 index 0000000..62b47f2 --- /dev/null +++ b/chrono/manager/templates/chrono/agendas_import.html @@ -0,0 +1,17 @@ +{% extends "chrono/manager_base.html" %} +{% load i18n %} + +{% block appbar %} +

{% trans "Agendas Import" %}

+{% endblock %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} +
+ + {% trans 'Cancel' %} +
+
+{% endblock %} diff --git a/chrono/manager/templates/chrono/manager_agenda_settings.html b/chrono/manager/templates/chrono/manager_agenda_settings.html index a252ed2..433667e 100644 --- a/chrono/manager/templates/chrono/manager_agenda_settings.html +++ b/chrono/manager/templates/chrono/manager_agenda_settings.html @@ -15,6 +15,7 @@ {% trans 'Delete' %} {% endif %} {% if user_can_manage %} + {% trans 'Export' %} {% trans 'Options' %} {% if object.kind == "events" %} {% trans 'Import Events' %} diff --git a/chrono/manager/templates/chrono/manager_home.html b/chrono/manager/templates/chrono/manager_home.html index a0804eb..b8fc057 100644 --- a/chrono/manager/templates/chrono/manager_home.html +++ b/chrono/manager/templates/chrono/manager_home.html @@ -5,6 +5,7 @@

{% trans 'Agendas' %}

{% if user.is_staff %} +{% trans 'Import' %} {% trans 'New' %} {% endif %} diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py index ba89398..d304ab9 100644 --- a/chrono/manager/urls.py +++ b/chrono/manager/urls.py @@ -22,6 +22,8 @@ urlpatterns = [ url(r'^$', views.homepage, name='chrono-manager-homepage'), url(r'^agendas/add/$', views.agenda_add, name='chrono-manager-agenda-add'), + url(r'^agendas/import/$', views.agendas_import, + name='chrono-manager-agendas-import'), url(r'^agendas/(?P\w+)/$', views.agenda_view, name='chrono-manager-agenda-view'), url(r'^agendas/(?P\w+)/(?P[0-9]{4})/(?P[0-9]+)/$', views.agenda_monthly_view, @@ -34,6 +36,8 @@ urlpatterns = [ name='chrono-manager-agenda-edit'), url(r'^agendas/(?P\w+)/delete$', views.agenda_delete, name='chrono-manager-agenda-delete'), + url(r'^agendas/(?P\w+)/export$', views.agenda_export, + name='chrono-manager-agenda-export'), url(r'^agendas/(?P\w+)/add-event$', views.agenda_add_event, name='chrono-manager-agenda-add-event'), url(r'^agendas/(?P\w+)/import-events$', views.agenda_import_events, diff --git a/chrono/manager/utils.py b/chrono/manager/utils.py index 30ddf01..32ec50d 100644 --- a/chrono/manager/utils.py +++ b/chrono/manager/utils.py @@ -33,6 +33,12 @@ def import_site(data, if_empty=False, clean=False, overwrite=False): if clean: Agenda.objects.all().delete() + results = {'created': 0, 'updated': 0} with transaction.atomic(): for data in data.get('agendas', []): - Agenda.import_json(data, overwrite=overwrite) + created = Agenda.import_json(data, overwrite=overwrite) + if created: + results['created'] += 1 + else: + results['updated'] += 1 + return results diff --git a/chrono/manager/views.py b/chrono/manager/views.py index bd187a3..06d6d20 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -37,7 +37,8 @@ from chrono.agendas.models import (Agenda, Event, MeetingType, TimePeriod, from .forms import (AgendaAddForm, AgendaEditForm, EventForm, NewMeetingTypeForm, MeetingTypeForm, TimePeriodForm, ImportEventsForm, NewDeskForm, DeskForm, TimePeriodExceptionForm, - ExceptionsImportForm) + ExceptionsImportForm, AgendasImportForm) +from .utils import import_site class HomepageView(ListView): @@ -77,6 +78,45 @@ class AgendaAddView(CreateView): agenda_add = AgendaAddView.as_view() +class AgendasImportView(FormView): + form_class = AgendasImportForm + template_name = 'chrono/agendas_import.html' + success_url = reverse_lazy('chrono-manager-homepage') + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_staff: + raise PermissionDenied() + return super(AgendasImportView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + try: + agendas_json = json.load(self.request.FILES['agendas_json']) + except ValueError: + form.add_error('agendas_json', _('File is not in the expected JSON format.')) + return self.form_invalid(form) + + results = import_site(agendas_json, overwrite=True) + if results.get('created') == 0 and results.get('updated') == 0: + messages.info(self.request, _('No agendas were found.')) + else: + if results.get('created') == 0: + message1 = _('No agenda created.') + else: + message1 = ungettext('An agenda has been created.', + '%(count)d agendas have been created.', results['created']) + + if results.get('updated') == 0: + message2 = _('No agenda updated.') + else: + message2 = ungettext('An agenda has been updated.', + '%(count)d agendas have been updated.', results['updated']) + messages.info(self.request, '%s %s' % (message1, message2)) + + return super(AgendasImportView, self).form_valid(form) + +agendas_import = AgendasImportView.as_view() + + class AgendaEditView(UpdateView): template_name = 'chrono/manager_agenda_form.html' model = Agenda @@ -488,6 +528,17 @@ class AgendaSettings(ManagedAgendaMixin, DetailView): agenda_settings = AgendaSettings.as_view() +class AgendaExport(ManagedAgendaMixin, DetailView): + model = Agenda + + def get(self, request, *args, **kwargs): + response = HttpResponse(content_type='application/json') + json.dump({'agendas': [self.get_object().export_json()]}, response, indent=2) + return response + +agenda_export = AgendaExport.as_view() + + class AgendaAddEventView(ManagedAgendaMixin, CreateView): template_name = 'chrono/manager_event_form.html' model = Event diff --git a/tests/test_manager.py b/tests/test_manager.py index e064481..79d02be 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import json from django.contrib.auth.models import User, Group from django.utils.timezone import make_aware, now, localtime @@ -14,6 +15,7 @@ from chrono.wsgi import application from chrono.agendas.models import (Agenda, Event, Booking, MeetingType, TimePeriod, Desk, TimePeriodException) +from chrono.manager.utils import export_site pytestmark = pytest.mark.django_db @@ -1447,3 +1449,54 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user): login(app) resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) assert resp.text.count('