diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 113bf2fa..c3e66c4f 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -33,6 +33,7 @@ from chrono.agendas.models import (
TimePeriod,
Desk,
TimePeriodException,
+ TimePeriodExceptionSource,
WEEKDAYS_LIST,
)
@@ -283,5 +284,17 @@ class ExceptionsImportForm(forms.ModelForm):
raise forms.ValidationError(_('Please provide an ICS File or an URL.'))
+class TimePeriodExceptionSourceReplaceForm(forms.ModelForm):
+ ics_file = forms.FileField(
+ label=_('ICS File'),
+ required=False,
+ help_text=_('ICS file containing events which will be considered as exceptions.'),
+ )
+
+ class Meta:
+ model = TimePeriodExceptionSource
+ fields = []
+
+
class AgendasImportForm(forms.Form):
agendas_json = forms.FileField(label=_('Agendas Export File'))
diff --git a/chrono/manager/templates/chrono/manager_import_exceptions.html b/chrono/manager/templates/chrono/manager_import_exceptions.html
index a6358279..dda92e38 100644
--- a/chrono/manager/templates/chrono/manager_import_exceptions.html
+++ b/chrono/manager/templates/chrono/manager_import_exceptions.html
@@ -31,9 +31,11 @@
-
- {% if object.ics_filename %}{% trans "replace" %}{% else %}{% trans "refresh" %}{% endif %}
-
+ {% if object.ics_filename %}
+ {% trans "replace" %}
+ {% else %}
+ {% trans "refresh" %}
+ {% endif %}
|
{% trans "remove" %} |
diff --git a/chrono/manager/templates/chrono/manager_replace_exceptions.html b/chrono/manager/templates/chrono/manager_replace_exceptions.html
new file mode 100644
index 00000000..31fe4e5f
--- /dev/null
+++ b/chrono/manager/templates/chrono/manager_replace_exceptions.html
@@ -0,0 +1,28 @@
+{% extends "chrono/manager_import_exceptions.html" %}
+{% load i18n %}
+
+{% block appbar %}
+{% if form.instance.ics_filename %}{% trans "Replace exceptions" %}{% else %}{% trans "Refresh exceptions" %}{% endif %}
+{% endblock %}
+
+{% block content %}
+
+{% endblock %}
diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py
index 5ccd361e..a163f0a0 100644
--- a/chrono/manager/urls.py
+++ b/chrono/manager/urls.py
@@ -105,6 +105,16 @@ urlpatterns = [
views.time_period_exception_source_delete,
name='chrono-manager-time-period-exception-source-delete',
),
+ url(
+ r'^time-period-exceptions-source/(?P\d+)/refresh$',
+ views.time_period_exception_source_refresh,
+ name='chrono-manager-time-period-exception-source-refresh',
+ ),
+ url(
+ r'^time-period-exceptions-source/(?P\d+)/replace$',
+ views.time_period_exception_source_replace,
+ name='chrono-manager-time-period-exception-source-replace',
+ ),
url(
r'^agendas/events.csv$',
views.agenda_import_events_sample_csv,
diff --git a/chrono/manager/views.py b/chrono/manager/views.py
index 3e90ae0f..e3888f0a 100644
--- a/chrono/manager/views.py
+++ b/chrono/manager/views.py
@@ -69,6 +69,7 @@ from .forms import (
ExceptionsImportForm,
AgendasImportForm,
TimePeriodAddForm,
+ TimePeriodExceptionSourceReplaceForm,
)
from .utils import import_site
@@ -926,6 +927,59 @@ class TimePeriodExceptionSourceDeleteView(ManagedDeskSubobjectMixin, DeleteView)
time_period_exception_source_delete = TimePeriodExceptionSourceDeleteView.as_view()
+class TimePeriodExceptionSourceReplaceView(ManagedDeskSubobjectMixin, UpdateView):
+ model = TimePeriodExceptionSource
+ form_class = TimePeriodExceptionSourceReplaceForm
+ template_name = 'chrono/manager_replace_exceptions.html'
+
+ def form_valid(self, form):
+ exceptions = None
+ try:
+ exceptions = form.instance.desk.import_timeperiod_exceptions_from_ics_file(
+ form.cleaned_data['ics_file'], source=form.instance
+ )
+ except ICSError as e:
+ form.add_error(None, force_text(e))
+ return self.form_invalid(form)
+
+ if exceptions is not None:
+ message = ungettext(
+ 'An exception has been imported.', '%(count)d exceptions have been imported.', exceptions
+ )
+ message = message % {'count': exceptions}
+ messages.info(self.request, message)
+ return super(TimePeriodExceptionSourceReplaceView, self).form_valid(form)
+
+
+time_period_exception_source_replace = TimePeriodExceptionSourceReplaceView.as_view()
+
+
+class TimePeriodExceptionSourceRefreshView(ManagedDeskSubobjectMixin, DetailView):
+ model = TimePeriodExceptionSource
+
+ def get(self, request, *args, **kwargs):
+ try:
+ source = self.get_object()
+ exceptions = source.desk.import_timeperiod_exceptions_from_remote_ics(
+ source.ics_url, source=source
+ )
+ except ICSError as e:
+ messages.error(self.request, force_text(e))
+ else:
+ message = ungettext(
+ 'An exception has been imported.', '%(count)d exceptions have been imported.', exceptions
+ )
+ message = message % {'count': exceptions}
+ messages.info(self.request, message)
+ # redirect to settings
+ return HttpResponseRedirect(
+ reverse('chrono-manager-agenda-settings', kwargs={'pk': source.desk.agenda_id})
+ )
+
+
+time_period_exception_source_refresh = TimePeriodExceptionSourceRefreshView.as_view()
+
+
def menu_json(request):
label = _('Agendas')
json_str = json.dumps(
diff --git a/tests/test_manager.py b/tests/test_manager.py
index eb219893..4a326709 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -1470,6 +1470,97 @@ END:VCALENDAR"""
assert TimePeriodExceptionSource.objects.filter(pk=source1.pk).exists() is False
+def test_meetings_agenda_replace_time_period_exception_source(app, admin_user):
+ agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
+ desk = Desk.objects.create(agenda=agenda, label='Desk A')
+ MeetingType(agenda=agenda, label='Blah').save()
+ TimePeriod.objects.create(
+ weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
+ )
+ ics_file_content = b"""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//foo.bar//EN
+BEGIN:VEVENT
+DTSTART:20180101
+DTEND:20180101
+SUMMARY:New Year's Eve
+RRULE:FREQ=YEARLY
+END:VEVENT
+END:VCALENDAR"""
+
+ login(app)
+ # import a source from a file
+ resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
+ resp = resp.click('Settings')
+ resp = resp.click('upload')
+ resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
+ resp = resp.form.submit(status=302).follow()
+ assert TimePeriodException.objects.filter(desk=desk).count() == 2
+ source = TimePeriodExceptionSource.objects.latest('pk')
+ assert source.timeperiodexception_set.count() == 2
+ exceptions = list(source.timeperiodexception_set.order_by('pk'))
+
+ # replace the source
+ resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
+ resp = resp.click('Settings')
+ resp = resp.click('upload')
+ resp = resp.click(href='/manage/time-period-exceptions-source/%d/replace' % source.pk)
+ resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
+ resp = resp.form.submit().follow()
+ assert TimePeriodException.objects.count() == 2
+ assert source.timeperiodexception_set.count() == 2
+ new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
+ assert exceptions[0].pk != new_exceptions[0].pk
+ assert exceptions[1].pk != new_exceptions[1].pk
+
+
+@mock.patch('chrono.agendas.models.requests.get')
+def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, app, admin_user):
+ agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
+ desk = Desk.objects.create(agenda=agenda, label='Desk A')
+ MeetingType(agenda=agenda, label='Blah').save()
+ TimePeriod.objects.create(
+ weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
+ )
+ ics_url_content = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//foo.bar//EN
+BEGIN:VEVENT
+DTSTART:20180101
+DTEND:20180101
+SUMMARY:New Year's Eve
+END:VEVENT
+END:VCALENDAR"""
+
+ login(app)
+ # import a source from an url
+ resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
+ resp = resp.click('Settings')
+ resp = resp.click('upload')
+ resp.form['ics_url'] = 'http://example.com/foo.ics'
+ mocked_response = mock.Mock()
+ mocked_response.text = ics_url_content
+ mocked_get.return_value = mocked_response
+ resp = resp.form.submit(status=302).follow()
+ assert TimePeriodException.objects.filter(desk=desk).count() == 1
+ source = TimePeriodExceptionSource.objects.latest('pk')
+ assert source.timeperiodexception_set.count() == 1
+ exceptions = list(source.timeperiodexception_set.order_by('pk'))
+
+ # refresh the source
+ resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
+ resp = resp.click('Settings')
+ resp = resp.click('upload')
+ mocked_response = mock.Mock()
+ mocked_response.text = ics_url_content
+ mocked_get.return_value = mocked_response
+ resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
+ assert TimePeriodException.objects.count() == 1
+ assert source.timeperiodexception_set.count() == 1
+ new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
+ assert exceptions[0].pk != new_exceptions[0].pk
+
+
def test_agenda_day_view(app, admin_user, manager_user, api_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')