Projet

Général

Profil

0001-agenda-add-support-for-remote-calendar-file-with-exc.patch

Serghei Mihai, 11 octobre 2017 14:08

Télécharger (25,1 ko)

Voir les différences:

Subject: [PATCH] agenda: add support for remote calendar file with exceptions
 (#19070)

Remote calendars can be specified by desk and updated hourly, or used once.
 .../0020_desk_timeperiod_exceptions_remote_url.py  |  19 +++
 chrono/agendas/models.py                           |  18 +++
 chrono/manager/forms.py                            |  30 +++-
 .../commands/sync_desks_timeperiod_exceptions.py   |  33 ++++
 .../chrono/manager_import_exceptions.html          |   4 +-
 chrono/manager/views.py                            |   5 +-
 debian/chrono.cron.hourly                          |   3 +
 debian/control                                     |   3 +-
 requirements.txt                                   |   1 +
 setup.py                                           |   3 +-
 tests/test_agendas.py                              | 101 ++++++++++++
 tests/test_manager.py                              | 170 +++++++++++++++++++++
 12 files changed, 378 insertions(+), 12 deletions(-)
 create mode 100644 chrono/agendas/migrations/0020_desk_timeperiod_exceptions_remote_url.py
 create mode 100644 chrono/manager/management/commands/sync_desks_timeperiod_exceptions.py
 create mode 100644 debian/chrono.cron.hourly
chrono/agendas/migrations/0020_desk_timeperiod_exceptions_remote_url.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('agendas', '0019_timeperiodexception'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='desk',
16
            name='timeperiod_exceptions_remote_url',
17
            field=models.URLField(null=True, verbose_name='URL to fetch time period exceptions from', blank=True),
18
        ),
19
    ]
chrono/agendas/models.py
17 17

  
18 18
import datetime
19 19
import vobject
20
import requests
20 21

  
21 22
from django.contrib.auth.models import Group
22 23
from django.core.exceptions import ValidationError
......
43 44
    dtime = localtime(dtime)
44 45
    return dtime.hour == 0 and dtime.minute == 0
45 46

  
47
def get_remote_calendar(url):
48
    try:
49
        response = requests.get(url)
50
        response.raise_for_status()
51
    except requests.exceptions.HTTPError as e:
52
        raise ICSError(_('Failed to retrieve remote calendar (HTTP error %s).') % e.response.status_code)
53
    except requests.exceptions.ConnectionError:
54
        raise ICSError(_('Failed to retrieve remote calendar (connection error).'))
55
    except requests.exceptions.Timeout:
56
        raise ICSError(_('Failed to retrieve remote calendar (HTTP timeout).'))
57
    return response.text
58

  
46 59

  
47 60
class ICSError(Exception):
48 61
    pass
......
358 371
    agenda = models.ForeignKey(Agenda)
359 372
    label = models.CharField(_('Label'), max_length=150)
360 373
    slug = models.SlugField(_('Identifier'), max_length=150)
374
    timeperiod_exceptions_remote_url = models.URLField(_('URL to fetch time period exceptions from'),
375
                                    null=True, blank=True)
361 376

  
362 377
    def __unicode__(self):
363 378
        return self.label
......
417 432
        in_two_weeks = self.get_exceptions_within_two_weeks()
418 433
        return self.timeperiodexception_set.count() == len(in_two_weeks)
419 434

  
435
    def create_timeperiod_exceptions_from_remote_ics(self, url):
436
        return self.create_timeperiod_exceptions_from_ics(get_remote_calendar(url))
437

  
420 438
    def create_timeperiod_exceptions_from_ics(self, data):
421 439
        try:
422 440
            parsed = vobject.readOne(data)
chrono/manager/forms.py
16 16

  
17 17
import csv
18 18
import datetime
19
import requests
19 20

  
20 21
from django import forms
21 22
from django.forms import ValidationError
22 23
from django.utils.translation import ugettext_lazy as _
23 24

  
24 25
from chrono.agendas.models import (Event, MeetingType, TimePeriod, Desk,
25
                                   TimePeriodException)
26
                                   TimePeriodException, ICSError, get_remote_calendar)
26 27

  
27 28
from . import widgets
28 29

  
......
77 78
        exclude = []
78 79

  
79 80

  
80
class NewDeskForm(forms.ModelForm):
81
class DeskForm(forms.ModelForm):
81 82
    class Meta:
82 83
        model = Desk
83 84
        widgets = {
84 85
            'agenda': forms.HiddenInput(),
85 86
        }
86
        exclude = ['slug']
87
        exclude = []
87 88

  
89
    def is_valid(self):
90
        if not self.data['timeperiod_exceptions_remote_url']:
91
            return super(DeskForm, self).is_valid()
88 92

  
89
class DeskForm(forms.ModelForm):
93
        try:
94
            get_remote_calendar(self.data['timeperiod_exceptions_remote_url'])
95
        except ICSError as e:
96
            self.add_error('timeperiod_exceptions_remote_url', e)
97
            return False
98
        return super(DeskForm, self).is_valid()
99

  
100

  
101
class NewDeskForm(DeskForm):
90 102
    class Meta:
91 103
        model = Desk
92 104
        widgets = {
93 105
            'agenda': forms.HiddenInput(),
94 106
        }
95
        exclude = []
107
        exclude = ['slug']
96 108

  
97 109

  
98 110
class TimePeriodExceptionForm(forms.ModelForm):
......
170 182
        model = Desk
171 183
        fields = []
172 184

  
173
    ics_file = forms.FileField(label=_('ICS File'),
185
    ics_file = forms.FileField(label=_('ICS File'), required=False,
174 186
                               help_text=_('ICS file containing events which will be considered as exceptions'))
187
    ics_url = forms.URLField(label=_('URL'), required=False)
188

  
189
    def clean(self):
190
        cleaned_data = super(ExceptionsImportForm, self).clean()
191
        if not cleaned_data['ics_file'] and not cleaned_data['ics_url']:
192
            raise ValidationError(_('A file or an url should be filled.'))
chrono/manager/management/commands/sync_desks_timeperiod_exceptions.py
1
# chrono - agendas system
2
# Copyright (C) 2016-2017  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import sys
18
import logging
19

  
20
from django.core.management.base import BaseCommand
21
from chrono.agendas.models import Desk, ICSError
22

  
23

  
24
class Command(BaseCommand):
25
    help = 'Synchronize time period exceptions from desks remote ics'
26

  
27
    def handle(self, **options):
28
        logger = logging.getLogger(__name__)
29
        for desk in Desk.objects.filter(timeperiod_exceptions_remote_url__isnull=False):
30
            try:
31
                desk.create_timeperiod_exceptions_from_remote_ics(desk.timeperiod_exceptions_remote_url)
32
            except ICSError as e:
33
                logger.warning(u'unable to create timeperiod exceptions for "%s": %s' % (desk, e))
chrono/manager/templates/chrono/manager_import_exceptions.html
11 11
{% endblock %}
12 12

  
13 13
{% block content %}
14

  
15 14
<form method="post" enctype="multipart/form-data">
15
  <p>{% trans "You can upload a file or specify an address to remote calendar." %}</p>
16 16
  {% csrf_token %}
17 17
  {{ form.as_p }}
18
  <p>
19
  </p>
20 18
  <div class="buttons">
21 19
    <button>{% trans "Import" %}</button>
22 20
    <a class="cancel" href="{% url 'chrono-manager-agenda-view' pk=agenda.id %}">{% trans 'Cancel' %}</a>
chrono/manager/views.py
396 396

  
397 397
    def form_valid(self, form):
398 398
        try:
399
            exceptions = form.instance.create_timeperiod_exceptions_from_ics(form.cleaned_data['ics_file'])
399
            if form.cleaned_data['ics_file']:
400
                exceptions = form.instance.create_timeperiod_exceptions_from_ics(form.cleaned_data['ics_file'])
401
            else:
402
                exceptions = form.instance.create_timeperiod_exceptions_from_remote_ics(form.cleaned_data['ics_url'])
400 403
        except ICSError as e:
401 404
            form.add_error(None, unicode(e))
402 405
            return self.form_invalid(form)
debian/chrono.cron.hourly
1
#!/bin/sh
2

  
3
/sbin/runuser -u chrono /usr/bin/chrono-manage -- tenant_command sync_desks_timeperiod_exceptions --all-tenants
debian/control
11 11
Depends: ${misc:Depends}, ${python:Depends},
12 12
    python-django (>= 1.8),
13 13
    python-gadjo,
14
    python-intervaltree
14
    python-intervaltree,
15
    python-requests
15 16
Recommends: python-django-mellon
16 17
Description: Agendas System (Python module)
17 18

  
requirements.txt
3 3
djangorestframework>=3.1, <3.7
4 4
django-jsonfield >= 0.9.3
5 5
intervaltree
6
requests
setup.py
107 107
        'djangorestframework>=3.1, <3.7',
108 108
        'django-jsonfield >= 0.9.3',
109 109
        'intervaltree',
110
        'vobject'
110
        'vobject',
111
        'requests'
111 112
        ],
112 113
    zip_safe=False,
113 114
    cmdclass={
tests/test_agendas.py
1 1
import pytest
2 2
import datetime
3
import mock
4
import requests
5
import logging
3 6

  
4 7
from django.utils.timezone import now, make_aware, localtime
8
from django.core.management import call_command
5 9

  
6 10
from chrono.agendas.models import (Agenda, Event, Booking, MeetingType,
7 11
                        Desk, TimePeriodException, ICSError)
......
220 224
    with pytest.raises(ICSError) as e:
221 225
        exceptions_count = desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_NO_EVENTS)
222 226
    assert str(e.value) == "The file doesn't contain any events."
227

  
228
@mock.patch('chrono.agendas.models.requests.get')
229
def test_timeperiodexception_creation_from_remote_ics(mocked_get):
230
    agenda = Agenda(label=u'Test 8 agenda')
231
    agenda.save()
232
    desk = Desk(label='Test 8 desk', agenda=agenda)
233
    desk.save()
234
    mocked_response = mock.Mock()
235
    mocked_response.text = ICS_SAMPLE
236
    mocked_get.return_value = mocked_response
237
    exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
238
    assert exceptions_count == 2
239

  
240
@mock.patch('chrono.agendas.models.requests.get')
241
def test_timeperiodexception_creation_from_unreachable_remote_ics(mocked_get):
242
    agenda = Agenda(label=u'Test 9 agenda')
243
    agenda.save()
244
    desk = Desk(label='Test 9 desk', agenda=agenda)
245
    desk.save()
246
    mocked_response = mock.Mock()
247
    mocked_response.content.return_value = ICS_SAMPLE
248
    mocked_get.return_value = mocked_response
249
    def mocked_requests_connection_error(*args, **kwargs):
250
        raise requests.exceptions.ConnectionError('unreachable')
251
    mocked_get.side_effect = mocked_requests_connection_error
252
    with pytest.raises(ICSError) as e:
253
        exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
254
    assert str(e.value) == "Failed to retrieve remote calendar (connection error)."
255

  
256
@mock.patch('chrono.agendas.models.requests.get')
257
def test_timeperiodexception_creation_from_forbidden_remote_ics(mocked_get):
258
    agenda = Agenda(label=u'Test 10 agenda')
259
    agenda.save()
260
    desk = Desk(label='Test 10 desk', agenda=agenda)
261
    desk.save()
262
    mocked_response = mock.Mock()
263
    mocked_response.status_code = 403
264
    mocked_get.return_value = mocked_response
265
    def mocked_requests_http_forbidden_error(*args, **kwargs):
266
        raise requests.exceptions.HTTPError(response=mocked_response)
267
    mocked_get.side_effect = mocked_requests_http_forbidden_error
268

  
269
    with pytest.raises(ICSError) as e:
270
        exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
271
    assert str(e.value) == "Failed to retrieve remote calendar (HTTP error 403)."
272

  
273
@mock.patch('chrono.agendas.models.requests.get')
274
def test_timeperiodexception_creation_from_remote_ics_with_timeout_error(mocked_get):
275
    agenda = Agenda(label=u'Test 11 agenda')
276
    agenda.save()
277
    desk = Desk(label='Test 11 desk', agenda=agenda)
278
    desk.save()
279
    mocked_response = mock.Mock()
280
    mocked_get.return_value = mocked_response
281
    def mocked_requests_http_timeout_error(*args, **kwargs):
282
        raise requests.exceptions.Timeout(response=mocked_response)
283
    mocked_get.side_effect = mocked_requests_http_timeout_error
284

  
285
    with pytest.raises(ICSError) as e:
286
        exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
287
    assert str(e.value) == "Failed to retrieve remote calendar (HTTP timeout)."
288

  
289
@mock.patch('chrono.agendas.models.requests.get')
290
def test_timeperiodexception_creation_from_remote_ics_with_ssl_error(mocked_get):
291
    agenda = Agenda(label=u'Test 11 agenda')
292
    agenda.save()
293
    desk = Desk(label='Test 11 desk', agenda=agenda)
294
    desk.save()
295
    mocked_response = mock.Mock()
296
    mocked_get.return_value = mocked_response
297
    def mocked_requests_http_ssl_error(*args, **kwargs):
298
        raise requests.exceptions.SSLError(response=mocked_response)
299
    mocked_get.side_effect = mocked_requests_http_ssl_error
300

  
301
    with pytest.raises(ICSError) as e:
302
        exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
303
    assert str(e.value) == "Failed to retrieve remote calendar (connection error)."
304

  
305
@mock.patch('chrono.agendas.models.requests.get')
306
def test_sync_desks_timeperiod_exceptions_from_ics(mocked_get, caplog):
307
    agenda = Agenda(label=u'Test 11 agenda')
308
    agenda.save()
309
    desk = Desk(label='Test 11 desk', agenda=agenda, timeperiod_exceptions_remote_url='http:example.com/sample.ics')
310
    desk.save()
311
    mocked_response = mock.Mock()
312
    mocked_response.status_code = 403
313
    mocked_get.return_value = mocked_response
314
    def mocked_requests_http_forbidden_error(*args, **kwargs):
315
        raise requests.exceptions.HTTPError(response=mocked_response)
316
    mocked_get.side_effect = mocked_requests_http_forbidden_error
317
    call_command('sync_desks_timeperiod_exceptions')
318
    records = caplog.records()
319
    assert len(records) == 1
320
    for record in records:
321
        assert record.name == 'chrono.manager.management.commands.sync_desks_timeperiod_exceptions'
322
        assert record.levelno == logging.WARNING
323
        assert record.getMessage() == 'unable to create timeperiod exceptions for "Test 11 desk": Failed to retrieve remote calendar (HTTP error 403).'
tests/test_manager.py
5 5
import datetime
6 6
import pytest
7 7
from webtest import TestApp, Upload
8
import mock
9
import requests
8 10

  
9 11
from chrono.wsgi import application
10 12

  
......
699 701
    assert 'Desk A' in resp.text
700 702
    assert 'Desk B' in resp.text
701 703

  
704
@mock.patch('chrono.manager.forms.requests.get')
705
def test_meetings_agenda_add_desk_with_exceptions_url(mocked_get, app, admin_user):
706
    app = login(app)
707
    resp = app.get('/manage/', status=200)
708
    resp = resp.click('New')
709
    resp.form['label'] = 'Foo bar'
710
    resp.form['kind'] = 'meetings'
711
    resp = resp.form.submit()
712
    agenda = Agenda.objects.get(slug='foo-bar')
713
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=200)
714
    ICS_SAMPLE = """BEGIN:VCALENDAR
715
VERSION:2.0
716
PRODID:-//foo.bar//EN
717
BEGIN:VEVENT
718
DTSTART:20180101
719
DTEND:20180101
720
SUMMARY:New eve
721
END:VEVENT
722
END:VCALENDAR"""
723
    mocked_response = mock.Mock()
724
    mocked_response.content.return_value = ICS_SAMPLE
725
    mocked_get.return_value = mocked_response
726
    resp = resp.click('New Desk')
727
    resp.form['label'] = 'Desk A'
728
    resp.form['timeperiod_exceptions_remote_url'] = 'http://nowhere.com/unknown.ics'
729
    resp = resp.form.submit(status=302)
730

  
731
@mock.patch('chrono.manager.forms.requests.get')
732
def test_meetings_agenda_add_desk_with_non_existing_exceptions_url(mocked_get, app, admin_user):
733
    app = login(app)
734
    resp = app.get('/manage/', status=200)
735
    resp = resp.click('New')
736
    resp.form['label'] = 'Foo bar'
737
    resp.form['kind'] = 'meetings'
738
    resp = resp.form.submit()
739
    agenda = Agenda.objects.get(slug='foo-bar')
740
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=200)
741
    mocked_response = mock.Mock()
742
    mocked_response.status_code = 403
743
    mocked_get.return_value = mocked_response
744
    def mocked_requests_http_forbidden_error(*args, **kwargs):
745
        raise requests.exceptions.HTTPError(response=mocked_response)
746
    mocked_get.side_effect = mocked_requests_http_forbidden_error
747
    resp = resp.click('New Desk')
748
    resp.form['label'] = 'Desk A'
749
    resp.form['timeperiod_exceptions_remote_url'] = 'http://nowhere.com/unknown.ics'
750
    resp = resp.form.submit(status=200)
751
    assert 'Failed to retrieve remote calendar (HTTP error 403).' in resp.text
752

  
753
@mock.patch('chrono.manager.forms.requests.get')
754
def test_meetings_agenda_add_desk_with_unreachable_exceptions_url(mocked_get, app, admin_user):
755
    app = login(app)
756
    resp = app.get('/manage/', status=200)
757
    resp = resp.click('New')
758
    resp.form['label'] = 'Foo bar'
759
    resp.form['kind'] = 'meetings'
760
    resp = resp.form.submit()
761
    agenda = Agenda.objects.get(slug='foo-bar')
762
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=200)
763
    mocked_response = mock.Mock()
764
    mocked_get.return_value = mocked_response
765
    def mocked_requests_connection_error(*args, **kwargs):
766
        raise requests.exceptions.ConnectionError('unreachable')
767
    mocked_get.side_effect = mocked_requests_connection_error
768
    resp = resp.click('New Desk')
769
    resp.form['label'] = 'Desk A'
770
    resp.form['timeperiod_exceptions_remote_url'] = 'http://nowhere.com/unknown.ics'
771
    resp = resp.form.submit(status=200)
772
    assert 'Failed to retrieve remote calendar (connection error).' in resp.text
773

  
702 774

  
703 775
def test_meetings_agenda_delete_desk(app, admin_user):
704 776
    app = login(app)
......
840 912
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
841 913
    assert 'Import exceptions from .ics' in resp.content
842 914
    resp = resp.click('upload')
915
    assert "You can upload a file or specify an address to remote calendar." in resp
916
    resp = resp.form.submit(status=200)
917
    assert "A file or an url should be filled." in resp.body
843 918
    resp.form['ics_file'] = Upload('exceptions.ics', 'invalid content', 'text/calendar')
844 919
    resp = resp.form.submit(status=200)
845 920
    assert 'File format is invalid' in resp.content
......
889 964
    assert TimePeriodException.objects.count() == 1
890 965
    resp = resp.follow()
891 966
    assert 'An exception has been imported.' in resp.content
967

  
968
@mock.patch('chrono.agendas.models.requests.get')
969
def test_agenda_import_time_period_exception_from_remote_ics_with_connection_error(mocked_get, app, admin_user):
970
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
971
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
972
    MeetingType(agenda=agenda, label='Bar').save()
973
    login(app)
974
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
975
    assert 'Import exceptions from .ics' not in resp.content
976

  
977
    TimePeriod.objects.create(weekday=1, desk=desk,
978
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
979

  
980
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
981
    resp = resp.click('upload')
982

  
983
    assert 'ics_file' in resp.form.fields
984
    assert 'ics_url' in resp.form.fields
985
    resp.form['ics_url'] = 'http://example.com/foo.ics'
986
    mocked_response = mock.Mock()
987
    mocked_get.return_value = mocked_response
988
    def mocked_requests_connection_error(*args, **kwargs):
989
        raise requests.exceptions.ConnectionError('unreachable')
990
    mocked_get.side_effect = mocked_requests_connection_error
991
    resp = resp.form.submit(status=200)
992
    assert 'Failed to retrieve remote calendar (connection error).' in resp.content
993

  
994
@mock.patch('chrono.agendas.models.requests.get')
995
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user):
996
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
997
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
998
    MeetingType(agenda=agenda, label='Bar').save()
999
    login(app)
1000
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
1001
    assert 'Import exceptions from .ics' not in resp.content
1002

  
1003
    TimePeriod.objects.create(weekday=1, desk=desk,
1004
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
1005

  
1006
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
1007
    resp = resp.click('upload')
1008
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1009
    mocked_response = mock.Mock()
1010
    mocked_response.status_code = 403
1011
    mocked_get.return_value = mocked_response
1012
    def mocked_requests_http_forbidden_error(*args, **kwargs):
1013
        raise requests.exceptions.HTTPError(response=mocked_response)
1014
    mocked_get.side_effect = mocked_requests_http_forbidden_error
1015
    resp = resp.form.submit(status=200)
1016
    assert 'Failed to retrieve remote calendar (HTTP error 403).' in resp.content
1017

  
1018
@mock.patch('chrono.agendas.models.requests.get')
1019
def test_agenda_import_time_period_exception_from_remote_ics_with_timeout_error(mocked_get, app, admin_user):
1020
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1021
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
1022
    MeetingType(agenda=agenda, label='Bar').save()
1023
    login(app)
1024
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
1025
    assert 'Import exceptions from .ics' not in resp.content
1026

  
1027
    TimePeriod.objects.create(weekday=1, desk=desk,
1028
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
1029

  
1030
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
1031
    resp = resp.click('upload')
1032
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1033
    mocked_response = mock.Mock()
1034
    mocked_get.return_value = mocked_response
1035
    def mocked_requests_http_timeout_error(*args, **kwargs):
1036
        raise requests.exceptions.Timeout(response=mocked_response)
1037
    mocked_get.side_effect = mocked_requests_http_timeout_error
1038
    resp = resp.form.submit(status=200)
1039
    assert 'Failed to retrieve remote calendar (HTTP timeout).' in resp.content
1040

  
1041
@mock.patch('chrono.agendas.models.requests.get')
1042
def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mocked_get, app, admin_user):
1043
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1044
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
1045
    MeetingType(agenda=agenda, label='Bar').save()
1046
    login(app)
1047
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
1048
    assert 'Import exceptions from .ics' not in resp.content
1049
    TimePeriod.objects.create(weekday=1, desk=desk,
1050
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
1051

  
1052
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
1053
    resp = resp.click('upload')
1054
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1055
    mocked_response = mock.Mock()
1056
    mocked_get.return_value = mocked_response
1057
    def mocked_requests_http_ssl_error(*args, **kwargs):
1058
        raise requests.exceptions.SSLError
1059
    mocked_get.side_effect = mocked_requests_http_ssl_error
1060
    resp = resp.form.submit(status=200)
1061
    assert 'Failed to retrieve remote calendar (connection error).' in resp.content
892
-