Projet

Général

Profil

0001-agenda-add-remote-timeperiods-url-for-desks-19070.patch

Serghei Mihai, 09 octobre 2017 11:04

Télécharger (9,61 ko)

Voir les différences:

Subject: [PATCH] agenda: add remote timeperiods url for desks (#19070)

Add command to synchronize time periods.
 .../0020_desk_timeperiod_exceptions_remote_url.py  | 19 ++++++++++
 chrono/agendas/models.py                           |  2 +
 chrono/manager/forms.py                            | 20 ++++++++--
 .../commands/sync_desks_timeperiod_exceptions.py   | 33 +++++++++++++++++
 tests/test_agendas.py                              | 23 ++++++++++++
 tests/test_manager.py                              | 43 ++++++++++++++++++++++
 6 files changed, 136 insertions(+), 4 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
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
359 359
    agenda = models.ForeignKey(Agenda)
360 360
    label = models.CharField(_('Label'), max_length=150)
361 361
    slug = models.SlugField(_('Identifier'), max_length=150)
362
    timeperiod_exceptions_remote_url = models.URLField(_('URL to fetch time period exceptions from'),
363
                                    null=True, blank=True)
362 364

  
363 365
    def __unicode__(self):
364 366
        return self.label
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
......
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 = []
88

  
89
    def clean_timeperiod_exceptions_remote_url(self):
90
        if not self.cleaned_data['timeperiod_exceptions_remote_url']:
91
            return
92
        try:
93
            response = requests.get(self.cleaned_data['timeperiod_exceptions_remote_url'])
94
            response.raise_for_status()
95
        except requests.exceptions.HTTPError as e:
96
            raise ValidationError(_('Error %s is returned while trying to get file.') % e.response.status_code)
97
        except requests.exceptions.ConnectionError:
98
            raise ValidationError(_('URL is unreachable.'))
87 99

  
88 100

  
89
class DeskForm(forms.ModelForm):
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):
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))
tests/test_agendas.py
2 2
import datetime
3 3
import mock
4 4
import requests
5
import logging
5 6

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

  
8 10
from chrono.agendas.models import (Agenda, Event, Booking, MeetingType,
9 11
                        Desk, TimePeriodException, ICSError)
......
267 269
    with pytest.raises(ICSError) as e:
268 270
        exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
269 271
    assert str(e.value) == "Error 403 is returned while trying to get file."
272

  
273

  
274
@mock.patch('chrono.agendas.models.requests.get')
275
def test_sync_desks_timeperiod_exceptions_from_ics(mocked_get, caplog):
276
    agenda = Agenda(label=u'Test 11 agenda')
277
    agenda.save()
278
    desk = Desk(label='Test 11 desk', agenda=agenda, timeperiod_exceptions_remote_url='http:example.com/sample.ics')
279
    desk.save()
280
    mocked_response = mock.Mock()
281
    mocked_response.status_code = 403
282
    mocked_get.return_value = mocked_response
283
    def mocked_requests_http_forbidden_error(*args, **kwargs):
284
        raise requests.exceptions.HTTPError(response=mocked_response)
285
    mocked_get.side_effect = mocked_requests_http_forbidden_error
286
    call_command('sync_desks_timeperiod_exceptions')
287
    records = caplog.records()
288
    assert len(records) == 1
289
    for record in records:
290
        assert record.name == 'chrono.manager.management.commands.sync_desks_timeperiod_exceptions'
291
        assert record.levelno == logging.WARNING
292
        assert record.getMessage() == 'unable to create timeperiod exceptions for "Test 11 desk": Error 403 is returned while trying to get file.'
tests/test_manager.py
701 701
    assert 'Desk A' in resp.text
702 702
    assert 'Desk B' in resp.text
703 703

  
704
@mock.patch('chrono.manager.forms.requests.get')
705
def test_meetings_agenda_add_desk_with_non_existing_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
    mocked_response = mock.Mock()
715
    mocked_response.status_code = 403
716
    mocked_get.return_value = mocked_response
717
    def mocked_requests_http_forbidden_error(*args, **kwargs):
718
        raise requests.exceptions.HTTPError(response=mocked_response)
719
    mocked_get.side_effect = mocked_requests_http_forbidden_error
720
    resp = resp.click('New Desk')
721
    resp.form['label'] = 'Desk A'
722
    resp.form['timeperiod_exceptions_remote_url'] = 'http://nowhere.com/unknown.ics'
723
    resp = resp.form.submit(status=200)
724
    assert 'Error 403 is returned while trying to get file.' in resp.text
725

  
726
@mock.patch('chrono.manager.forms.requests.get')
727
def test_meetings_agenda_add_desk_with_unreachable_exceptions_url(mocked_get, app, admin_user):
728
    app = login(app)
729
    resp = app.get('/manage/', status=200)
730
    resp = resp.click('New')
731
    resp.form['label'] = 'Foo bar'
732
    resp.form['kind'] = 'meetings'
733
    resp = resp.form.submit()
734
    agenda = Agenda.objects.get(slug='foo-bar')
735
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=200)
736
    mocked_response = mock.Mock()
737
    mocked_get.return_value = mocked_response
738
    def mocked_requests_connection_error(*args, **kwargs):
739
        raise requests.exceptions.ConnectionError('unreachable')
740
    mocked_get.side_effect = mocked_requests_connection_error
741
    resp = resp.click('New Desk')
742
    resp.form['label'] = 'Desk A'
743
    resp.form['timeperiod_exceptions_remote_url'] = 'http://nowhere.com/unknown.ics'
744
    resp = resp.form.submit(status=200)
745
    assert 'URL is unreachable.' in resp.text
746

  
704 747

  
705 748
def test_meetings_agenda_delete_desk(app, admin_user):
706 749
    app = login(app)
707
-