Projet

Général

Profil

0001-manager-add-support-for-remote-ICS-file-with-excepti.patch

Serghei Mihai, 04 octobre 2017 12:33

Télécharger (10,8 ko)

Voir les différences:

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

 chrono/agendas/models.py                           | 11 +++++
 chrono/manager/forms.py                            |  8 +++-
 .../chrono/manager_import_exceptions.html          |  4 +-
 chrono/manager/views.py                            |  5 +-
 requirements.txt                                   |  1 +
 setup.py                                           |  3 +-
 tests/test_agendas.py                              | 47 +++++++++++++++++++
 tests/test_manager.py                              | 54 ++++++++++++++++++++++
 8 files changed, 127 insertions(+), 6 deletions(-)
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
......
417 418
        in_two_weeks = self.get_exceptions_within_two_weeks()
418 419
        return self.timeperiodexception_set.count() == len(in_two_weeks)
419 420

  
421
    def create_timeperiod_exceptions_from_remote_ics(self, url):
422
        try:
423
            response = requests.get(url)
424
            response.raise_for_status()
425
        except requests.exceptions.HTTPError as e:
426
            raise ICSError(_('Error %s is returned while trying to get file.') % e.response.status_code)
427
        except requests.exceptions.ConnectionError:
428
            raise ICSError(_('URL is unreachable.'))
429
        return self.create_timeperiod_exceptions_from_ics(response.text)
430

  
420 431
    def create_timeperiod_exceptions_from_ics(self, data):
421 432
        try:
422 433
            parsed = vobject.readOne(data)
chrono/manager/forms.py
170 170
        model = Desk
171 171
        fields = []
172 172

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

  
177
    def clean(self):
178
        cleaned_data = super(ExceptionsImportForm, self).clean()
179
        if not cleaned_data['ics_file'] and not cleaned_data['ics_url']:
180
            raise ValidationError(_('A file or an url should be filled'))
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 could upload a local file or specify an address to remote file." %}</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)
requirements.txt
3 3
djangorestframework>=3.1
4 4
django-jsonfield >= 0.9.3
5 5
intervaltree
6
requests
setup.py
107 107
        'djangorestframework>=3.1',
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
3 5

  
4 6
from django.utils.timezone import now, make_aware, localtime
5 7

  
......
220 222
    with pytest.raises(ICSError) as e:
221 223
        exceptions_count = desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_NO_EVENTS)
222 224
    assert str(e.value) == "The file doesn't contain any events."
225

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

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

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

  
267
    with pytest.raises(ICSError) as e:
268
        exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
269
    assert str(e.value) == "Error 403 is returned while trying to get file."
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

  
......
840 842
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
841 843
    assert 'Import exceptions from .ics' in resp.content
842 844
    resp = resp.click('upload')
845
    assert "You could upload a local file or specify an address to remote file." in resp
843 846
    resp.form['ics_file'] = Upload('exceptions.ics', 'invalid content', 'text/calendar')
844 847
    resp = resp.form.submit(status=200)
845 848
    assert 'File format is invalid' in resp.content
......
889 892
    assert TimePeriodException.objects.count() == 1
890 893
    resp = resp.follow()
891 894
    assert 'An exception has been imported.' in resp.content
895

  
896
@mock.patch('chrono.agendas.models.requests.get')
897
def test_agenda_import_time_period_exception_from_remote_ics(mocked_get, app, admin_user):
898
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
899
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
900
    MeetingType(agenda=agenda, label='Bar').save()
901
    login(app)
902
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
903
    assert 'Import exceptions from .ics' not in resp.content
904

  
905
    TimePeriod.objects.create(weekday=1, desk=desk,
906
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
907

  
908
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
909
    resp = resp.click('upload')
910

  
911
    assert 'ics_file' in resp.form.fields
912
    assert 'ics_url' in resp.form.fields
913
    resp.form['ics_url'] = 'http://example.com/foo.ics'
914
    mocked_response = mock.Mock()
915
    mocked_get.return_value = mocked_response
916
    def mocked_requests_connection_error(*args, **kwargs):
917
        raise requests.exceptions.ConnectionError('unreachable')
918
    mocked_get.side_effect = mocked_requests_connection_error
919
    resp = resp.form.submit(status=200)
920
    assert 'URL is unreachable.' in resp.content
921

  
922
@mock.patch('chrono.agendas.models.requests.get')
923
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user):
924

  
925
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
926
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
927
    MeetingType(agenda=agenda, label='Bar').save()
928
    login(app)
929
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
930
    assert 'Import exceptions from .ics' not in resp.content
931

  
932
    TimePeriod.objects.create(weekday=1, desk=desk,
933
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
934

  
935
    resp = app.get('/manage/agendas/%d/' % agenda.pk)
936
    resp = resp.click('upload')
937
    resp.form['ics_url'] = 'http://example.com/foo.ics'
938
    mocked_response = mock.Mock()
939
    mocked_response.status_code = 403
940
    mocked_get.return_value = mocked_response
941
    def mocked_requests_http_forbidden_error(*args, **kwargs):
942
        raise requests.exceptions.HTTPError(response=mocked_response)
943
    mocked_get.side_effect = mocked_requests_http_forbidden_error
944
    resp = resp.form.submit(status=200)
945
    assert 'Error 403 is returned while trying to get' in resp.content
892
-