0001-manager-add-UI-to-export-import-agendas-25985.patch
chrono/agendas/models.py | ||
---|---|---|
153 | 153 |
for desk in desks: |
154 | 154 |
desk['agenda'] = agenda |
155 | 155 |
Desk.import_json(desk).save() |
156 |
return created |
|
156 | 157 | |
157 | 158 |
WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0]) |
158 | 159 |
chrono/manager/forms.py | ||
---|---|---|
201 | 201 |
help_text=_('ICS file containing events which will be considered as exceptions.')) |
202 | 202 |
ics_url = forms.URLField(label=_('URL'), required=False, |
203 | 203 |
help_text=_('URL to remote calendar which will be synchronised hourly.')) |
204 | ||
205 | ||
206 |
class AgendasImportForm(forms.Form): |
|
207 |
agendas_json = forms.FileField(label=_('Agendas Export File')) |
chrono/manager/templates/chrono/agendas_import.html | ||
---|---|---|
1 |
{% extends "chrono/manager_base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block appbar %} |
|
5 |
<h2>{% trans "Agendas Import" %}</h2> |
|
6 |
{% endblock %} |
|
7 | ||
8 |
{% block content %} |
|
9 |
<form method="post" enctype="multipart/form-data"> |
|
10 |
{% csrf_token %} |
|
11 |
{{ form.as_p }} |
|
12 |
<div class="buttons"> |
|
13 |
<button class="submit-button">{% trans "Import" %}</button> |
|
14 |
<a class="cancel" href="{% url 'chrono-manager-homepage' %}">{% trans 'Cancel' %}</a> |
|
15 |
</div> |
|
16 |
</form> |
|
17 |
{% endblock %} |
chrono/manager/templates/chrono/manager_agenda_settings.html | ||
---|---|---|
15 | 15 |
<a rel="popup" href="{% url 'chrono-manager-agenda-delete' pk=object.id %}">{% trans 'Delete' %}</a> |
16 | 16 |
{% endif %} |
17 | 17 |
{% if user_can_manage %} |
18 |
<a href="{% url 'chrono-manager-agenda-export' pk=object.id %}">{% trans 'Export' %}</a> |
|
18 | 19 |
<a rel="popup" href="{% url 'chrono-manager-agenda-edit' pk=object.id %}">{% trans 'Options' %}</a> |
19 | 20 |
{% if object.kind == "events" %} |
20 | 21 |
<a rel="popup" href="{% url 'chrono-manager-agenda-import-events' pk=object.id %}">{% trans 'Import Events' %}</a> |
chrono/manager/templates/chrono/manager_home.html | ||
---|---|---|
5 | 5 |
<h2>{% trans 'Agendas' %}</h2> |
6 | 6 |
{% if user.is_staff %} |
7 | 7 |
<span class="actions"> |
8 |
<a rel="popup" href="{% url 'chrono-manager-agendas-import' %}">{% trans 'Import' %}</a> |
|
8 | 9 |
<a rel="popup" href="{% url 'chrono-manager-agenda-add' %}">{% trans 'New' %}</a> |
9 | 10 |
</span> |
10 | 11 |
{% endif %} |
chrono/manager/urls.py | ||
---|---|---|
22 | 22 |
url(r'^$', views.homepage, name='chrono-manager-homepage'), |
23 | 23 |
url(r'^agendas/add/$', views.agenda_add, |
24 | 24 |
name='chrono-manager-agenda-add'), |
25 |
url(r'^agendas/import/$', views.agendas_import, |
|
26 |
name='chrono-manager-agendas-import'), |
|
25 | 27 |
url(r'^agendas/(?P<pk>\w+)/$', views.agenda_view, |
26 | 28 |
name='chrono-manager-agenda-view'), |
27 | 29 |
url(r'^agendas/(?P<pk>\w+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$', views.agenda_monthly_view, |
... | ... | |
34 | 36 |
name='chrono-manager-agenda-edit'), |
35 | 37 |
url(r'^agendas/(?P<pk>\w+)/delete$', views.agenda_delete, |
36 | 38 |
name='chrono-manager-agenda-delete'), |
39 |
url(r'^agendas/(?P<pk>\w+)/export$', views.agenda_export, |
|
40 |
name='chrono-manager-agenda-export'), |
|
37 | 41 |
url(r'^agendas/(?P<pk>\w+)/add-event$', views.agenda_add_event, |
38 | 42 |
name='chrono-manager-agenda-add-event'), |
39 | 43 |
url(r'^agendas/(?P<pk>\w+)/import-events$', views.agenda_import_events, |
chrono/manager/utils.py | ||
---|---|---|
33 | 33 |
if clean: |
34 | 34 |
Agenda.objects.all().delete() |
35 | 35 | |
36 |
results = {'created': 0, 'updated': 0} |
|
36 | 37 |
with transaction.atomic(): |
37 | 38 |
for data in data.get('agendas', []): |
38 |
Agenda.import_json(data, overwrite=overwrite) |
|
39 |
created = Agenda.import_json(data, overwrite=overwrite) |
|
40 |
if created: |
|
41 |
results['created'] += 1 |
|
42 |
else: |
|
43 |
results['updated'] += 1 |
|
44 |
return results |
chrono/manager/views.py | ||
---|---|---|
37 | 37 | |
38 | 38 |
from .forms import (AgendaAddForm, AgendaEditForm, EventForm, NewMeetingTypeForm, MeetingTypeForm, |
39 | 39 |
TimePeriodForm, ImportEventsForm, NewDeskForm, DeskForm, TimePeriodExceptionForm, |
40 |
ExceptionsImportForm) |
|
40 |
ExceptionsImportForm, AgendasImportForm) |
|
41 |
from .utils import import_site |
|
41 | 42 | |
42 | 43 | |
43 | 44 |
class HomepageView(ListView): |
... | ... | |
77 | 78 |
agenda_add = AgendaAddView.as_view() |
78 | 79 | |
79 | 80 | |
81 |
class AgendasImportView(FormView): |
|
82 |
form_class = AgendasImportForm |
|
83 |
template_name = 'chrono/agendas_import.html' |
|
84 |
success_url = reverse_lazy('chrono-manager-homepage') |
|
85 | ||
86 |
def dispatch(self, request, *args, **kwargs): |
|
87 |
if not request.user.is_staff: |
|
88 |
raise PermissionDenied() |
|
89 |
return super(AgendasImportView, self).dispatch(request, *args, **kwargs) |
|
90 | ||
91 |
def form_valid(self, form): |
|
92 |
try: |
|
93 |
agendas_json = json.load(self.request.FILES['agendas_json']) |
|
94 |
except ValueError: |
|
95 |
form.add_error('agendas_json', _('File is not in the expected JSON format.')) |
|
96 |
return self.form_invalid(form) |
|
97 | ||
98 |
results = import_site(agendas_json, overwrite=True) |
|
99 |
if results.get('created') == 0 and results.get('updated') == 0: |
|
100 |
messages.info(self.request, _('No agendas were found.')) |
|
101 |
else: |
|
102 |
if results.get('created') == 0: |
|
103 |
message1 = _('No agenda created.') |
|
104 |
else: |
|
105 |
message1 = ungettext('An agenda has been created.', |
|
106 |
'%(count)d agendas have been created.', results['created']) |
|
107 | ||
108 |
if results.get('updated') == 0: |
|
109 |
message2 = _('No agenda updated.') |
|
110 |
else: |
|
111 |
message2 = ungettext('An agenda has been updated.', |
|
112 |
'%(count)d agendas have been updated.', results['updated']) |
|
113 |
messages.info(self.request, '%s %s' % (message1, message2)) |
|
114 | ||
115 |
return super(AgendasImportView, self).form_valid(form) |
|
116 | ||
117 |
agendas_import = AgendasImportView.as_view() |
|
118 | ||
119 | ||
80 | 120 |
class AgendaEditView(UpdateView): |
81 | 121 |
template_name = 'chrono/manager_agenda_form.html' |
82 | 122 |
model = Agenda |
... | ... | |
488 | 528 |
agenda_settings = AgendaSettings.as_view() |
489 | 529 | |
490 | 530 | |
531 |
class AgendaExport(ManagedAgendaMixin, DetailView): |
|
532 |
model = Agenda |
|
533 | ||
534 |
def get(self, request, *args, **kwargs): |
|
535 |
response = HttpResponse(content_type='application/json') |
|
536 |
json.dump({'agendas': [self.get_object().export_json()]}, response, indent=2) |
|
537 |
return response |
|
538 | ||
539 |
agenda_export = AgendaExport.as_view() |
|
540 | ||
541 | ||
491 | 542 |
class AgendaAddEventView(ManagedAgendaMixin, CreateView): |
492 | 543 |
template_name = 'chrono/manager_event_form.html' |
493 | 544 |
model = Event |
tests/test_manager.py | ||
---|---|---|
1 | 1 |
# -*- coding: utf-8 -*- |
2 | 2 | |
3 | 3 |
from __future__ import unicode_literals |
4 |
import json |
|
4 | 5 | |
5 | 6 |
from django.contrib.auth.models import User, Group |
6 | 7 |
from django.utils.timezone import make_aware, now, localtime |
... | ... | |
14 | 15 | |
15 | 16 |
from chrono.agendas.models import (Agenda, Event, Booking, MeetingType, |
16 | 17 |
TimePeriod, Desk, TimePeriodException) |
18 |
from chrono.manager.utils import export_site |
|
17 | 19 | |
18 | 20 |
pytestmark = pytest.mark.django_db |
19 | 21 | |
... | ... | |
1447 | 1449 |
login(app) |
1448 | 1450 |
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) |
1449 | 1451 |
assert resp.text.count('<div class="booking"') == 0 |
1452 | ||
1453 |
def test_import_agenda_as_manager(app, manager_user): |
|
1454 |
# open /manage/ access to manager_user, and check agenda import is not |
|
1455 |
# allowed. |
|
1456 |
agenda = Agenda(label='Foo bar') |
|
1457 |
agenda.view_role = manager_user.groups.all()[0] |
|
1458 |
agenda.save() |
|
1459 |
app = login(app, username='manager', password='manager') |
|
1460 |
resp = app.get('/manage/', status=200) |
|
1461 |
resp = app.get('/manage/agendas/import/', status=403) |
|
1462 | ||
1463 |
def test_import_agenda(app, admin_user): |
|
1464 |
agenda = Agenda(label=u'Foo bar') |
|
1465 |
agenda.save() |
|
1466 | ||
1467 |
app = login(app) |
|
1468 |
resp = app.get('/manage/agendas/%s/settings' % agenda.id) |
|
1469 |
resp = resp.click('Export') |
|
1470 |
assert resp.headers['content-type'] == 'application/json' |
|
1471 |
agenda_export = resp.body |
|
1472 | ||
1473 |
# invalid json |
|
1474 |
resp = app.get('/manage/', status=200) |
|
1475 |
resp = resp.click('Import') |
|
1476 |
resp.form['agendas_json'] = Upload('export.json', b'garbage', 'application/json') |
|
1477 |
resp = resp.form.submit() |
|
1478 |
assert 'File is not in the expected JSON format.' in resp.text |
|
1479 | ||
1480 |
# empty json |
|
1481 |
resp = app.get('/manage/', status=200) |
|
1482 |
resp = resp.click('Import') |
|
1483 |
resp.form['agendas_json'] = Upload('export.json', b'{}', 'application/json') |
|
1484 |
resp = resp.form.submit().follow() |
|
1485 |
assert 'No agendas were found.' in resp.text |
|
1486 | ||
1487 |
# existing agenda |
|
1488 |
resp = app.get('/manage/', status=200) |
|
1489 |
resp = resp.click('Import') |
|
1490 |
resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json') |
|
1491 |
resp = resp.form.submit().follow() |
|
1492 |
assert 'No agenda created. An agenda has been updated.' in resp.text |
|
1493 |
assert Agenda.objects.count() == 1 |
|
1494 | ||
1495 |
# new agenda |
|
1496 |
Agenda.objects.all().delete() |
|
1497 |
resp = app.get('/manage/', status=200) |
|
1498 |
resp = resp.click('Import') |
|
1499 |
resp.form['agendas_json'] = Upload('export.json', agenda_export, 'application/json') |
|
1500 |
resp = resp.form.submit().follow() |
|
1501 |
assert 'An agenda has been created. No agenda updated.' in resp.text |
|
1502 |
assert Agenda.objects.count() == 1 |
|
1450 |
- |