manager: allow templated ICS URL (#66323)

This commit is contained in:
Valentin Deniaud 2022-06-27 15:20:09 +02:00
parent 2e9fe5b794
commit 9dbf6b1d5c
3 changed files with 57 additions and 4 deletions

View File

@ -2422,18 +2422,19 @@ class TimePeriodExceptionSource(models.Model):
def _check_ics_content(self):
if self.ics_url:
ics_url = Template(self.ics_url).render(Context(settings.TEMPLATE_VARS))
try:
response = requests.get(self.ics_url, proxies=settings.REQUESTS_PROXIES)
response = requests.get(ics_url, proxies=settings.REQUESTS_PROXIES)
response.raise_for_status()
except requests.HTTPError as e:
raise ICSError(
_('Failed to retrieve remote calendar (%(url)s, HTTP error %(status_code)s).')
% {'url': self.ics_url, 'status_code': e.response.status_code}
% {'url': ics_url, 'status_code': e.response.status_code}
)
except requests.RequestException as e:
raise ICSError(
_('Failed to retrieve remote calendar (%(url)s, %(exception)s).')
% {'url': self.ics_url, 'exception': e}
% {'url': ics_url, 'exception': e}
)
try:
# override response encoding received in HTTP headers as it may

View File

@ -28,9 +28,11 @@ from django import forms
from django.conf import settings
from django.contrib.auth.models import Group
from django.core.exceptions import FieldDoesNotExist
from django.core.validators import URLValidator
from django.db import transaction
from django.db.models import DurationField, ExpressionWrapper, F
from django.forms import ValidationError, formset_factory
from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist
from django.utils.encoding import force_text
from django.utils.formats import date_format
from django.utils.timezone import localtime, make_aware, now
@ -1195,8 +1197,9 @@ class ExceptionsImportForm(forms.ModelForm):
required=False,
help_text=_('ICS file containing events which will be considered as exceptions.'),
)
ics_url = forms.URLField(
ics_url = forms.CharField(
label=_('URL'),
max_length=200,
required=False,
help_text=_('URL to remote calendar which will be synchronised hourly.'),
)
@ -1206,6 +1209,20 @@ class ExceptionsImportForm(forms.ModelForm):
if not cleaned_data.get('ics_file') and not cleaned_data.get('ics_url'):
raise forms.ValidationError(_('Please provide an ICS File or an URL.'))
def clean_ics_url(self):
url = self.cleaned_data['ics_url']
if not url:
return url
try:
url = Template(url).render(Context(settings.TEMPLATE_VARS))
except (TemplateSyntaxError, VariableDoesNotExist) as e:
raise ValidationError(_('syntax error: %s') % e)
URLValidator()(url)
return self.cleaned_data['ics_url']
class DeskExceptionsImportForm(ExceptionsImportForm):
all_desks = forms.BooleanField(label=_('Apply exceptions on all desks of the agenda'), required=False)

View File

@ -772,6 +772,41 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mock
assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text
@override_settings(TEMPLATE_VARS={'passerelle_url': 'https://passerelle.publik.love/'})
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_with_remote_ics_templated_url(mocked_get, app, admin_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')
login(app)
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
resp.form['ics_url'] = '{{ passerelle_url }}holidays/test/holidays.ics'
mocked_response = mock.Mock()
mocked_response.text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
END:VEVENT
END:VCALENDAR"""
mocked_get.return_value = mocked_response
resp = resp.form.submit(status=302)
assert TimePeriodException.objects.filter(desk=desk).count() == 1
assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
assert mocked_get.call_args.args[0] == 'https://passerelle.publik.love/holidays/test/holidays.ics'
resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
resp.form['ics_url'] = '{{ unknown }}holidays/test/holidays.ics'
resp = resp.form.submit(status=200)
assert 'Enter a valid URL.' in resp.text
resp.form['ics_url'] = '{{ { }}holidays/test/holidays.ics'
resp = resp.form.submit(status=200)
assert 'syntax error' in resp.text
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_url_desk_all_desks(mocked_get, app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')