0001-manager-allow-templated-ICS-URL-66323.patch
chrono/agendas/models.py | ||
---|---|---|
2422 | 2422 | |
2423 | 2423 |
def _check_ics_content(self): |
2424 | 2424 |
if self.ics_url: |
2425 |
ics_url = Template(self.ics_url).render(Context(settings.TEMPLATE_VARS)) |
|
2425 | 2426 |
try: |
2426 |
response = requests.get(self.ics_url, proxies=settings.REQUESTS_PROXIES)
|
|
2427 |
response = requests.get(ics_url, proxies=settings.REQUESTS_PROXIES) |
|
2427 | 2428 |
response.raise_for_status() |
2428 | 2429 |
except requests.HTTPError as e: |
2429 | 2430 |
raise ICSError( |
2430 | 2431 |
_('Failed to retrieve remote calendar (%(url)s, HTTP error %(status_code)s).') |
2431 |
% {'url': self.ics_url, 'status_code': e.response.status_code}
|
|
2432 |
% {'url': ics_url, 'status_code': e.response.status_code} |
|
2432 | 2433 |
) |
2433 | 2434 |
except requests.RequestException as e: |
2434 | 2435 |
raise ICSError( |
2435 | 2436 |
_('Failed to retrieve remote calendar (%(url)s, %(exception)s).') |
2436 |
% {'url': self.ics_url, 'exception': e}
|
|
2437 |
% {'url': ics_url, 'exception': e} |
|
2437 | 2438 |
) |
2438 | 2439 |
try: |
2439 | 2440 |
# override response encoding received in HTTP headers as it may |
chrono/manager/forms.py | ||
---|---|---|
28 | 28 |
from django.conf import settings |
29 | 29 |
from django.contrib.auth.models import Group |
30 | 30 |
from django.core.exceptions import FieldDoesNotExist |
31 |
from django.core.validators import URLValidator |
|
31 | 32 |
from django.db import transaction |
32 | 33 |
from django.db.models import DurationField, ExpressionWrapper, F |
33 | 34 |
from django.forms import ValidationError, formset_factory |
35 |
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist |
|
34 | 36 |
from django.utils.encoding import force_text |
35 | 37 |
from django.utils.formats import date_format |
36 | 38 |
from django.utils.timezone import localtime, make_aware, now |
... | ... | |
1195 | 1197 |
required=False, |
1196 | 1198 |
help_text=_('ICS file containing events which will be considered as exceptions.'), |
1197 | 1199 |
) |
1198 |
ics_url = forms.URLField(
|
|
1200 |
ics_url = forms.CharField(
|
|
1199 | 1201 |
label=_('URL'), |
1202 |
max_length=200, |
|
1200 | 1203 |
required=False, |
1201 | 1204 |
help_text=_('URL to remote calendar which will be synchronised hourly.'), |
1202 | 1205 |
) |
... | ... | |
1206 | 1209 |
if not cleaned_data.get('ics_file') and not cleaned_data.get('ics_url'): |
1207 | 1210 |
raise forms.ValidationError(_('Please provide an ICS File or an URL.')) |
1208 | 1211 | |
1212 |
def clean_ics_url(self): |
|
1213 |
url = self.cleaned_data['ics_url'] |
|
1214 |
if not url: |
|
1215 |
return url |
|
1216 | ||
1217 |
try: |
|
1218 |
url = Template(url).render(Context(settings.TEMPLATE_VARS)) |
|
1219 |
except (TemplateSyntaxError, VariableDoesNotExist) as e: |
|
1220 |
raise ValidationError(_('syntax error: %s') % e) |
|
1221 | ||
1222 |
URLValidator()(url) |
|
1223 | ||
1224 |
return self.cleaned_data['ics_url'] |
|
1225 | ||
1209 | 1226 | |
1210 | 1227 |
class DeskExceptionsImportForm(ExceptionsImportForm): |
1211 | 1228 |
all_desks = forms.BooleanField(label=_('Apply exceptions on all desks of the agenda'), required=False) |
tests/manager/test_exception.py | ||
---|---|---|
772 | 772 |
assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text |
773 | 773 | |
774 | 774 | |
775 |
@override_settings(TEMPLATE_VARS={'passerelle_url': 'https://passerelle.publik.love/'}) |
|
776 |
@mock.patch('chrono.agendas.models.requests.get') |
|
777 |
def test_agenda_import_time_period_exception_with_remote_ics_templated_url(mocked_get, app, admin_user): |
|
778 |
agenda = Agenda.objects.create(label='New Example', kind='meetings') |
|
779 |
desk = Desk.objects.create(agenda=agenda, label='New Desk') |
|
780 |
login(app) |
|
781 | ||
782 |
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk) |
|
783 |
resp.form['ics_url'] = '{{ passerelle_url }}holidays/test/holidays.ics' |
|
784 |
mocked_response = mock.Mock() |
|
785 |
mocked_response.text = """BEGIN:VCALENDAR |
|
786 |
VERSION:2.0 |
|
787 |
PRODID:-//foo.bar//EN |
|
788 |
BEGIN:VEVENT |
|
789 |
DTSTART:20180101 |
|
790 |
DTEND:20180101 |
|
791 |
SUMMARY:New Year's Eve |
|
792 |
END:VEVENT |
|
793 |
END:VCALENDAR""" |
|
794 |
mocked_get.return_value = mocked_response |
|
795 |
resp = resp.form.submit(status=302) |
|
796 |
assert TimePeriodException.objects.filter(desk=desk).count() == 1 |
|
797 |
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1 |
|
798 |
assert mocked_get.call_args.args[0] == 'https://passerelle.publik.love/holidays/test/holidays.ics' |
|
799 | ||
800 |
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk) |
|
801 |
resp.form['ics_url'] = '{{ unknown }}holidays/test/holidays.ics' |
|
802 |
resp = resp.form.submit(status=200) |
|
803 |
assert 'Enter a valid URL.' in resp.text |
|
804 | ||
805 |
resp.form['ics_url'] = '{{ { }}holidays/test/holidays.ics' |
|
806 |
resp = resp.form.submit(status=200) |
|
807 |
assert 'syntax error' in resp.text |
|
808 | ||
809 | ||
775 | 810 |
@mock.patch('chrono.agendas.models.requests.get') |
776 | 811 |
def test_agenda_import_time_period_exception_url_desk_all_desks(mocked_get, app, admin_user): |
777 | 812 |
agenda = Agenda.objects.create(label='Foo bar', kind='meetings') |
778 |
- |