manager: add UI to export/import agendas (#25985)
This commit is contained in:
parent
34c13e96ed
commit
d0d7c3de1e
|
@ -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])
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue