0001-manager-add-support-for-remote-ICS-file-with-excepti.patch
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, <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 |
|
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 |
|
846 |
resp = resp.form.submit(status=200) |
|
847 |
assert "A file or an url should be filled." in resp.body |
|
843 | 848 |
resp.form['ics_file'] = Upload('exceptions.ics', 'invalid content', 'text/calendar') |
844 | 849 |
resp = resp.form.submit(status=200) |
845 | 850 |
assert 'File format is invalid' in resp.content |
... | ... | |
889 | 894 |
assert TimePeriodException.objects.count() == 1 |
890 | 895 |
resp = resp.follow() |
891 | 896 |
assert 'An exception has been imported.' in resp.content |
897 | ||
898 |
@mock.patch('chrono.agendas.models.requests.get') |
|
899 |
def test_agenda_import_time_period_exception_from_remote_ics(mocked_get, app, admin_user): |
|
900 |
agenda = Agenda.objects.create(label='New Example', kind='meetings') |
|
901 |
desk = Desk.objects.create(agenda=agenda, label='New Desk') |
|
902 |
MeetingType(agenda=agenda, label='Bar').save() |
|
903 |
login(app) |
|
904 |
resp = app.get('/manage/agendas/%d/' % agenda.pk) |
|
905 |
assert 'Import exceptions from .ics' not in resp.content |
|
906 | ||
907 |
TimePeriod.objects.create(weekday=1, desk=desk, |
|
908 |
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)) |
|
909 | ||
910 |
resp = app.get('/manage/agendas/%d/' % agenda.pk) |
|
911 |
resp = resp.click('upload') |
|
912 | ||
913 |
assert 'ics_file' in resp.form.fields |
|
914 |
assert 'ics_url' in resp.form.fields |
|
915 |
resp.form['ics_url'] = 'http://example.com/foo.ics' |
|
916 |
mocked_response = mock.Mock() |
|
917 |
mocked_get.return_value = mocked_response |
|
918 |
def mocked_requests_connection_error(*args, **kwargs): |
|
919 |
raise requests.exceptions.ConnectionError('unreachable') |
|
920 |
mocked_get.side_effect = mocked_requests_connection_error |
|
921 |
resp = resp.form.submit(status=200) |
|
922 |
assert 'URL is unreachable.' in resp.content |
|
923 | ||
924 |
@mock.patch('chrono.agendas.models.requests.get') |
|
925 |
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user): |
|
926 | ||
927 |
agenda = Agenda.objects.create(label='New Example', kind='meetings') |
|
928 |
desk = Desk.objects.create(agenda=agenda, label='New Desk') |
|
929 |
MeetingType(agenda=agenda, label='Bar').save() |
|
930 |
login(app) |
|
931 |
resp = app.get('/manage/agendas/%d/' % agenda.pk) |
|
932 |
assert 'Import exceptions from .ics' not in resp.content |
|
933 | ||
934 |
TimePeriod.objects.create(weekday=1, desk=desk, |
|
935 |
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)) |
|
936 | ||
937 |
resp = app.get('/manage/agendas/%d/' % agenda.pk) |
|
938 |
resp = resp.click('upload') |
|
939 |
resp.form['ics_url'] = 'http://example.com/foo.ics' |
|
940 |
mocked_response = mock.Mock() |
|
941 |
mocked_response.status_code = 403 |
|
942 |
mocked_get.return_value = mocked_response |
|
943 |
def mocked_requests_http_forbidden_error(*args, **kwargs): |
|
944 |
raise requests.exceptions.HTTPError(response=mocked_response) |
|
945 |
mocked_get.side_effect = mocked_requests_http_forbidden_error |
|
946 |
resp = resp.form.submit(status=200) |
|
947 |
assert 'Error 403 is returned while trying to get' in resp.content |
|
892 |
- |