manager: add UI to export/import agendas (#25985)

This commit is contained in:
Frédéric Péters 2018-09-22 16:25:46 +02:00
parent 34c13e96ed
commit d0d7c3de1e
9 changed files with 140 additions and 2 deletions

View File

@ -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])

View File

@ -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'))

View File

@ -0,0 +1,17 @@
{% extends "chrono/manager_base.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans "Agendas Import" %}</h2>
{% endblock %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button class="submit-button">{% trans "Import" %}</button>
<a class="cancel" href="{% url 'chrono-manager-homepage' %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -15,6 +15,7 @@
<a rel="popup" href="{% url 'chrono-manager-agenda-delete' pk=object.id %}">{% trans 'Delete' %}</a>
{% endif %}
{% if user_can_manage %}
<a href="{% url 'chrono-manager-agenda-export' pk=object.id %}">{% trans 'Export' %}</a>
<a rel="popup" href="{% url 'chrono-manager-agenda-edit' pk=object.id %}">{% trans 'Options' %}</a>
{% if object.kind == "events" %}
<a rel="popup" href="{% url 'chrono-manager-agenda-import-events' pk=object.id %}">{% trans 'Import Events' %}</a>

View File

@ -5,6 +5,7 @@
<h2>{% trans 'Agendas' %}</h2>
{% if user.is_staff %}
<span class="actions">
<a rel="popup" href="{% url 'chrono-manager-agendas-import' %}">{% trans 'Import' %}</a>
<a rel="popup" href="{% url 'chrono-manager-agenda-add' %}">{% trans 'New' %}</a>
</span>
{% endif %}

View File

@ -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<pk>\w+)/$', views.agenda_view,
name='chrono-manager-agenda-view'),
url(r'^agendas/(?P<pk>\w+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$', views.agenda_monthly_view,
@ -34,6 +36,8 @@ urlpatterns = [
name='chrono-manager-agenda-edit'),
url(r'^agendas/(?P<pk>\w+)/delete$', views.agenda_delete,
name='chrono-manager-agenda-delete'),
url(r'^agendas/(?P<pk>\w+)/export$', views.agenda_export,
name='chrono-manager-agenda-export'),
url(r'^agendas/(?P<pk>\w+)/add-event$', views.agenda_add_event,
name='chrono-manager-agenda-add-event'),
url(r'^agendas/(?P<pk>\w+)/import-events$', views.agenda_import_events,

View File

@ -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

View File

@ -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

View File

@ -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('<div class="booking"') == 0
def test_import_agenda_as_manager(app, manager_user):
# open /manage/ access to manager_user, and check agenda import is not
# allowed.
agenda = Agenda(label='Foo bar')
agenda.view_role = manager_user.groups.all()[0]
agenda.save()
app = login(app, username='manager', password='manager')
resp = app.get('/manage/', status=200)
resp = app.get('/manage/agendas/import/', status=403)
def test_import_agenda(app, admin_user):
agenda = Agenda(label=u'Foo bar')
agenda.save()
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id)
resp = resp.click('Export')
assert resp.headers['content-type'] == 'application/json'
agenda_export = resp.body
# invalid json
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', b'garbage', 'application/json')
resp = resp.form.submit()
assert 'File is not in the expected JSON format.' in resp.text
# empty json
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', b'{}', 'application/json')
resp = resp.form.submit().follow()
assert 'No agendas were found.' in resp.text
# existing agenda
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json')
resp = resp.form.submit().follow()
assert 'No agenda created. An agenda has been updated.' in resp.text
assert Agenda.objects.count() == 1
# new agenda
Agenda.objects.all().delete()
resp = app.get('/manage/', status=200)
resp = resp.click('Import')
resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json')
resp = resp.form.submit().follow()
assert 'An agenda has been created. No agenda updated.' in resp.text
assert Agenda.objects.count() == 1