Projet

Général

Profil

0004-agendas-refresh-an-exception-source-29209.patch

Lauréline Guérin, 13 décembre 2019 22:19

Télécharger (11,6 ko)

Voir les différences:

Subject: [PATCH 4/4] agendas: refresh an exception source (#29209)

 chrono/manager/forms.py                       | 13 +++
 .../chrono/manager_import_exceptions.html     |  8 +-
 .../chrono/manager_replace_exceptions.html    | 28 ++++++
 chrono/manager/urls.py                        | 10 ++
 chrono/manager/views.py                       | 52 +++++++++++
 tests/test_manager.py                         | 91 +++++++++++++++++++
 6 files changed, 199 insertions(+), 3 deletions(-)
 create mode 100644 chrono/manager/templates/chrono/manager_replace_exceptions.html
chrono/manager/forms.py
34 34
    MeetingType,
35 35
    TimePeriod,
36 36
    TimePeriodException,
37
    TimePeriodExceptionSource,
37 38
)
38 39

  
39 40
from . import widgets
......
282 283
            raise forms.ValidationError(_('Please provide an ICS File or an URL.'))
283 284

  
284 285

  
286
class TimePeriodExceptionSourceReplaceForm(forms.ModelForm):
287
    ics_file = forms.FileField(
288
        label=_('ICS File'),
289
        required=False,
290
        help_text=_('ICS file containing events which will be considered as exceptions.'),
291
    )
292

  
293
    class Meta:
294
        model = TimePeriodExceptionSource
295
        fields = []
296

  
297

  
285 298
class AgendasImportForm(forms.Form):
286 299
    agendas_json = forms.FileField(label=_('Agendas Export File'))
chrono/manager/templates/chrono/manager_import_exceptions.html
31 31
          </span>
32 32
        </td>
33 33
        <td>
34
          <a rel="popup" href="">
35
            {% if object.ics_filename %}{% trans "replace" %}{% else %}{% trans "refresh" %}{% endif %}
36
          </a>
34
          {% if object.ics_filename %}
35
            <a rel="popup" href="{% url 'chrono-manager-time-period-exception-source-replace' object.pk %}">{% trans "replace" %}</a>
36
          {% else %}
37
            <a href="{% url 'chrono-manager-time-period-exception-source-refresh' object.pk %}">{% trans "refresh" %}</a>
38
          {% endif %}
37 39
        </td>
38 40
        <td><a rel="popup" href="{% url 'chrono-manager-time-period-exception-source-delete' object.pk %}">{% trans "remove" %}</a></td>
39 41
      </tr>
chrono/manager/templates/chrono/manager_replace_exceptions.html
1
{% extends "chrono/manager_import_exceptions.html" %}
2
{% load i18n %}
3

  
4
{% block appbar %}
5
<h2>{% if form.instance.ics_filename %}{% trans "Replace exceptions" %}{% else %}{% trans "Refresh exceptions" %}{% endif %}</h2>
6
{% endblock %}
7

  
8
{% block content %}
9
<form method="post" enctype="multipart/form-data">
10
  {% if form.instance.ics_filename %}
11
    <p class="notice">{% trans "To replace existing exceptions, please upload a new file." %}</p>
12
  {% else %}
13
  <p class="notice">
14
    {% trans 'Press the button "Refresh" to refresh existing exceptions from:' %}
15
    <br />
16
    <a href="{{ form.instance.ics_url }}">{{ form.instance.ics_url }}</a>
17
  </p>
18
  {% endif %}
19
  {% csrf_token %}
20
  {{ form.as_p }}
21
  <p>
22
  </p>
23
  <div class="buttons">
24
    <button>{% if form.instance.ics_filename %}{% trans "Replace" %}{% else %}{% trans "Refresh" %}{% endif %}</button>
25
    <a class="cancel" href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a>
26
  </div>
27
</form>
28
{% endblock %}
chrono/manager/urls.py
105 105
        views.time_period_exception_source_delete,
106 106
        name='chrono-manager-time-period-exception-source-delete',
107 107
    ),
108
    url(
109
        r'^time-period-exceptions-source/(?P<pk>\d+)/refresh$',
110
        views.time_period_exception_source_refresh,
111
        name='chrono-manager-time-period-exception-source-refresh',
112
    ),
113
    url(
114
        r'^time-period-exceptions-source/(?P<pk>\d+)/replace$',
115
        views.time_period_exception_source_replace,
116
        name='chrono-manager-time-period-exception-source-replace',
117
    ),
108 118
    url(
109 119
        r'^agendas/events.csv$',
110 120
        views.agenda_import_events_sample_csv,
chrono/manager/views.py
68 68
    NewMeetingTypeForm,
69 69
    TimePeriodAddForm,
70 70
    TimePeriodExceptionForm,
71
    TimePeriodExceptionSourceReplaceForm,
71 72
    TimePeriodForm,
72 73
)
73 74
from .utils import import_site
......
926 927
time_period_exception_source_delete = TimePeriodExceptionSourceDeleteView.as_view()
927 928

  
928 929

  
930
class TimePeriodExceptionSourceReplaceView(ManagedDeskSubobjectMixin, UpdateView):
931
    model = TimePeriodExceptionSource
932
    form_class = TimePeriodExceptionSourceReplaceForm
933
    template_name = 'chrono/manager_replace_exceptions.html'
934

  
935
    def form_valid(self, form):
936
        exceptions = None
937
        try:
938
            exceptions = form.instance.desk.import_timeperiod_exceptions_from_ics_file(
939
                form.cleaned_data['ics_file'], source=form.instance
940
            )
941
        except ICSError as e:
942
            form.add_error(None, force_text(e))
943
            return self.form_invalid(form)
944

  
945
        if exceptions is not None:
946
            message = ungettext(
947
                'An exception has been imported.', '%(count)d exceptions have been imported.', exceptions
948
            )
949
            message = message % {'count': exceptions}
950
            messages.info(self.request, message)
951
        return super(TimePeriodExceptionSourceReplaceView, self).form_valid(form)
952

  
953

  
954
time_period_exception_source_replace = TimePeriodExceptionSourceReplaceView.as_view()
955

  
956

  
957
class TimePeriodExceptionSourceRefreshView(ManagedDeskSubobjectMixin, DetailView):
958
    model = TimePeriodExceptionSource
959

  
960
    def get(self, request, *args, **kwargs):
961
        try:
962
            source = self.get_object()
963
            exceptions = source.desk.import_timeperiod_exceptions_from_remote_ics(
964
                source.ics_url, source=source
965
            )
966
        except ICSError as e:
967
            messages.error(self.request, force_text(e))
968
        else:
969
            message = ungettext(
970
                'An exception has been imported.', '%(count)d exceptions have been imported.', exceptions
971
            )
972
            message = message % {'count': exceptions}
973
            messages.info(self.request, message)
974
        # redirect to settings
975
        return HttpResponseRedirect(reverse('chrono-manager-agenda-settings', kwargs={'pk': source.desk.agenda_id}))
976

  
977

  
978
time_period_exception_source_refresh = TimePeriodExceptionSourceRefreshView.as_view()
979

  
980

  
929 981
def menu_json(request):
930 982
    label = _('Agendas')
931 983
    json_str = json.dumps(
tests/test_manager.py
1471 1471
    assert TimePeriodExceptionSource.objects.filter(pk=source1.pk).exists() is False
1472 1472

  
1473 1473

  
1474
def test_meetings_agenda_replace_time_period_exception_source(app, admin_user):
1475
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1476
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1477
    MeetingType(agenda=agenda, label='Blah').save()
1478
    TimePeriod.objects.create(
1479
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1480
    )
1481
    ics_file_content = b"""BEGIN:VCALENDAR
1482
VERSION:2.0
1483
PRODID:-//foo.bar//EN
1484
BEGIN:VEVENT
1485
DTSTART:20180101
1486
DTEND:20180101
1487
SUMMARY:New Year's Eve
1488
RRULE:FREQ=YEARLY
1489
END:VEVENT
1490
END:VCALENDAR"""
1491

  
1492
    login(app)
1493
    # import a source from a file
1494
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1495
    resp = resp.click('Settings')
1496
    resp = resp.click('upload')
1497
    resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
1498
    resp = resp.form.submit(status=302).follow()
1499
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
1500
    source = TimePeriodExceptionSource.objects.latest('pk')
1501
    assert source.timeperiodexception_set.count() == 2
1502
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
1503

  
1504
    # replace the source
1505
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1506
    resp = resp.click('Settings')
1507
    resp = resp.click('upload')
1508
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/replace' % source.pk)
1509
    resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
1510
    resp = resp.form.submit().follow()
1511
    assert TimePeriodException.objects.count() == 2
1512
    assert source.timeperiodexception_set.count() == 2
1513
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
1514
    assert exceptions[0].pk != new_exceptions[0].pk
1515
    assert exceptions[1].pk != new_exceptions[1].pk
1516

  
1517

  
1518
@mock.patch('chrono.agendas.models.requests.get')
1519
def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, app, admin_user):
1520
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1521
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1522
    MeetingType(agenda=agenda, label='Blah').save()
1523
    TimePeriod.objects.create(
1524
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1525
    )
1526
    ics_url_content = """BEGIN:VCALENDAR
1527
VERSION:2.0
1528
PRODID:-//foo.bar//EN
1529
BEGIN:VEVENT
1530
DTSTART:20180101
1531
DTEND:20180101
1532
SUMMARY:New Year's Eve
1533
END:VEVENT
1534
END:VCALENDAR"""
1535

  
1536
    login(app)
1537
    # import a source from an url
1538
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1539
    resp = resp.click('Settings')
1540
    resp = resp.click('upload')
1541
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1542
    mocked_response = mock.Mock()
1543
    mocked_response.text = ics_url_content
1544
    mocked_get.return_value = mocked_response
1545
    resp = resp.form.submit(status=302).follow()
1546
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
1547
    source = TimePeriodExceptionSource.objects.latest('pk')
1548
    assert source.timeperiodexception_set.count() == 1
1549
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
1550

  
1551
    # refresh the source
1552
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1553
    resp = resp.click('Settings')
1554
    resp = resp.click('upload')
1555
    mocked_response = mock.Mock()
1556
    mocked_response.text = ics_url_content
1557
    mocked_get.return_value = mocked_response
1558
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
1559
    assert TimePeriodException.objects.count() == 1
1560
    assert source.timeperiodexception_set.count() == 1
1561
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
1562
    assert exceptions[0].pk != new_exceptions[0].pk
1563

  
1564

  
1474 1565
def test_agenda_day_view(app, admin_user, manager_user, api_user):
1475 1566
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1476 1567
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
1477
-