Projet

Général

Profil

0001-agendas-use-custom-urls-in-bookings-56820.patch

Emmanuel Cazenave, 22 septembre 2021 13:13

Télécharger (29,1 ko)

Voir les différences:

Subject: [PATCH] agendas: use custom urls in bookings (#56820)

 chrono/agendas/models.py                      |  7 ++
 .../agendas/events_reminder_body.html         |  2 +-
 .../agendas/events_reminder_body.txt          |  2 +-
 chrono/api/views.py                           |  8 +-
 .../manager_confirm_booking_cancellation.html |  8 +-
 .../manager_event_cancellation_report.html    |  2 +-
 .../chrono/manager_event_detail_fragment.html |  4 +-
 .../manager_meetings_agenda_day_view.html     |  2 +-
 .../manager_meetings_agenda_month_view.html   |  2 +-
 .../chrono/manager_resource_day_view.html     |  2 +-
 .../chrono/manager_resource_month_view.html   |  2 +-
 chrono/utils/publik_urls.py                   | 53 +++++++++++
 chrono/utils/requests_wrapper.py              |  2 +
 tests/api/test_fillslot.py                    | 34 +++++++
 tests/manager/test_all.py                     | 88 +++++++++++++++++++
 tests/manager/test_event.py                   | 58 ++++++++++++
 tests/manager/test_resource.py                | 38 ++++++++
 tests/test_agendas.py                         | 13 +++
 18 files changed, 311 insertions(+), 16 deletions(-)
 create mode 100644 chrono/utils/publik_urls.py
chrono/agendas/models.py
49 49
from django.utils.translation import ungettext
50 50

  
51 51
from chrono.interval import Interval, IntervalSet
52
from chrono.utils.publik_urls import translate_from_publik_url
52 53
from chrono.utils.requests_wrapper import requests as requests_wrapper
53 54

  
54 55
AGENDA_KINDS = (
......
1898 1899
        else:
1899 1900
            return ugettext('booked')
1900 1901

  
1902
    def get_form_url(self):
1903
        return translate_from_publik_url(self.form_url)
1904

  
1905
    def get_backoffice_url(self):
1906
        return translate_from_publik_url(self.backoffice_url)
1907

  
1901 1908

  
1902 1909
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])
1903 1910

  
chrono/agendas/templates/agendas/events_reminder_body.html
30 30

  
31 31
{% if booking.form_url %}
32 32
{% with _("Edit or cancel booking") as button_label %}
33
{% include "emails/button-link.html" with url=booking.form_url label=button_label %}
33
{% include "emails/button-link.html" with url=booking.get_form_url label=button_label %}
34 34
{% endwith %}
35 35
{% endif %}
36 36
{% endblock %}
chrono/agendas/templates/agendas/events_reminder_body.txt
20 20
{% trans "More information:" %} {{ booking.event.url }}
21 21
{% endif %}
22 22
{% if booking.form_url %}
23
{% trans "If in need to cancel it, you can do so here:" %} {{ booking.form_url }}
23
{% trans "If in need to cancel it, you can do so here:" %} {{ booking.get_form_url }}
24 24
{% endif %}
25 25
{% endautoescape %}
26 26
{% endblock %}
chrono/api/views.py
52 52
from chrono.api import serializers
53 53
from chrono.api.utils import APIError, Response
54 54
from chrono.interval import IntervalSet
55
from chrono.utils.publik_urls import translate_to_publik_url
55 56

  
56 57

  
57 58
def format_response_datetime(dt):
......
644 645

  
645 646

  
646 647
def make_booking(event, payload, extra_data, primary_booking=None, in_waiting_list=False, color=None):
648

  
647 649
    return Booking(
648 650
        event_id=event.pk,
649 651
        in_waiting_list=getattr(event, 'in_waiting_list', in_waiting_list),
......
654 656
        user_last_name=payload.get('user_last_name') or payload.get('user_name') or '',
655 657
        user_email=payload.get('user_email', ''),
656 658
        user_phone_number=payload.get('user_phone_number', ''),
657
        form_url=payload.get('form_url', ''),
658
        backoffice_url=payload.get('backoffice_url', ''),
659
        cancel_callback_url=payload.get('cancel_callback_url', ''),
659
        form_url=translate_to_publik_url(payload.get('form_url', '')),
660
        backoffice_url=translate_to_publik_url(payload.get('backoffice_url', '')),
661
        cancel_callback_url=translate_to_publik_url(payload.get('cancel_callback_url', '')),
660 662
        user_display_label=payload.get('user_display_label', ''),
661 663
        extra_data=extra_data,
662 664
        color=color,
chrono/manager/templates/chrono/manager_confirm_booking_cancellation.html
7 7

  
8 8
{% block content %}
9 9
<form method="post">
10
  {% if object.backoffice_url and not object.cancel_callback_url %}
10
  {% if object.get_backoffice_url and not object.cancel_callback_url %}
11 11
  {% if not user.is_staff %}
12 12
  <p>{% trans "This booking has no callback url configured, cancellation must be handled from corresponding form." %}</p>
13
  <p><a href="{{ object.backoffice_url }}">{% trans "Open form" %}</a></p>
13
  <p><a href="{{ object.get_backoffice_url }}">{% trans "Open form" %}</a></p>
14 14
  {% else %}
15 15
  <div class="warningnotice">
16 16
  <p>{% trans "This booking has no callback url configured, cancellation should be handled from corresponding form in order to garantee a coherent situation." %}</p>
17 17
  </div>
18
  <p><a href="{{ object.backoffice_url }}">{% trans "Open form" %}</a></p>
18
  <p><a href="{{ object.get_backoffice_url }}">{% trans "Open form" %}</a></p>
19 19
  <p>{% trans "However, since you are an administrator, you can choose to cancel it anyway." %}</p>
20 20
  {% endif %}
21 21
  {% else %}
......
26 26
  </p>
27 27
  {% endif %}
28 28

  
29
  {% if not object.backoffice_url or object.cancel_callback_url or user.is_staff %}
29
  {% if not object.get_backoffice_url or object.cancel_callback_url or user.is_staff %}
30 30
  {% csrf_token %}
31 31
  <input type="hidden" name="next" value="{% firstof request.POST.next request.GET.next %}">
32 32
  {{ form.as_p }}
chrono/manager/templates/chrono/manager_event_cancellation_report.html
21 21
<p>{% trans "Cancellation failed for the following bookings:" %}</p>
22 22
<ul>
23 23
{% for booking, error in errors.items %}
24
<li><a href="{{ booking.backoffice_url }}">{{ booking.events_display }}</a>: {{ error }}</li>
24
<li><a href="{{ booking.get_backoffice_url }}">{{ booking.events_display }}</a>: {{ error }}</li>
25 25
{% endfor %}
26 26
</ul>
27 27
{% endblock %}
chrono/manager/templates/chrono/manager_event_detail_fragment.html
29 29
  <ul class="objects-list single-links">
30 30
    {% for booking in booked %}
31 31
    <li>
32
    <a {% if booking.backoffice_url %}href="{{ booking.backoffice_url }}"{% endif %}>{{ booking.events_display }}</a>
32
    <a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.events_display }}</a>
33 33
    {% if not booking.primary_booking %}
34 34
    <a rel="popup" class="delete" href="{% url 'chrono-manager-booking-cancel' pk=agenda.id booking_pk=booking.id %}?next={{ request.path }}">{% trans "Cancel" %}</a>
35 35
    {% else %}
......
53 53
<div>
54 54
  <ul class="objects-list single-links">
55 55
    {% for booking in waiting %}
56
    <li><a {% if booking.backoffice_url %}href="{{ booking.backoffice_url }}"{% endif %}>{{ booking.events_display }}</a></li>
56
    <li><a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.events_display }}</a></li>
57 57
    {% endfor %}
58 58
  </ul>
59 59
</div>
chrono/manager/templates/chrono/manager_meetings_agenda_day_view.html
46 46
          <div class="booking{% if booking.color %} booking-color-{{ booking.color.index }}{% endif %}"
47 47
              style="height: {{ booking.css_height }}%; min-height: {{ booking.css_height }}%; top: {{ booking.css_top }}%;"
48 48
            ><span class="start-time">{{booking.event.start_datetime|date:"TIME_FORMAT"}}</span>
49
            <a {% if booking.backoffice_url %}href="{{booking.backoffice_url}}"{% endif %}>{{ booking.meetings_display }}</a>
49
            <a {% if booking.get_backoffice_url %}href="{{booking.get_backoffice_url}}"{% endif %}>{{ booking.meetings_display }}</a>
50 50
            <a rel="popup" class="cancel" href="{% url 'chrono-manager-booking-cancel' pk=booking.event.agenda_id booking_pk=booking.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a>
51 51
            {% if booking.color %}<span class="booking-color-label booking-bg-color-{{ booking.color.index }}">{{ booking.color }}</span>{% endif %}
52 52
          </div>
chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html
36 36
      {% for slot in day.infos.booked_slots %}
37 37
      <div class="booking{% if slot.booking.color %} booking-color-{{ slot.booking.color.index }}{% endif %}" style="left:{{ slot.css_left|stringformat:".1f" }}%;height:{{ slot.css_height|stringformat:".1f" }}%;min-height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%;">
38 38
        <span class="start-time">{{slot.booking.event.start_datetime|date:"TIME_FORMAT"}}</span>
39
        <a {% if slot.booking.backoffice_url %}href="{{slot.booking.backoffice_url}}"{% endif %}>{{ slot.booking.meetings_display }}</a>
39
        <a {% if slot.booking.get_backoffice_url %}href="{{slot.booking.get_backoffice_url}}"{% endif %}>{{ slot.booking.meetings_display }}</a>
40 40
          <a rel="popup" class="cancel" href="{% url 'chrono-manager-booking-cancel' pk=slot.booking.event.agenda_id booking_pk=slot.booking.id %}?next={{ request.path }}">{% trans "Cancel" %}</a>
41 41
        {% if not single_desk %}<span class="desk">{{ slot.desk }}</span>{% endif %}
42 42
        {% if slot.booking.color %}<span class="booking-color-label booking-bg-color-{{ slot.booking.color.index }}">{{ slot.booking.color }}</span>{% endif %}
chrono/manager/templates/chrono/manager_resource_day_view.html
40 40
                      <div class="booking"
41 41
                          style="height: {{ booking.css_height }}%; min-height: {{ booking.css_height }}%; top: {{ booking.css_top }}%;"
42 42
                        ><span class="start-time">{{ booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
43
                        <a {% if booking.backoffice_url %}href="{{ booking.backoffice_url }}"{% endif %}>{{ booking.meetings_display }}</a>
43
                        <a {% if booking.get_backoffice_url %}href="{{ booking.get_backoffice_url }}"{% endif %}>{{ booking.meetings_display }}</a>
44 44
                      </div>
45 45
                    {% endfor %}
46 46
                </td>
chrono/manager/templates/chrono/manager_resource_month_view.html
49 49
                {% for slot in day.infos.booked_slots %}
50 50
                <div class="booking" style="height:{{ slot.css_height|stringformat:".1f" }}%;min-height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%">
51 51
                    <span class="start-time">{{ slot.booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
52
                    <a {% if slot.booking.backoffice_url %}href="{{ slot.booking.backoffice_url }}"{% endif %}>{{ booking.meetings_display }}</a>
52
                    <a {% if slot.booking.get_backoffice_url %}href="{{ slot.booking.get_backoffice_url }}"{% endif %}>{{ booking.meetings_display }}</a>
53 53
                </div>
54 54
                {% endfor %}
55 55
                {% endif %}
chrono/utils/publik_urls.py
1
# chrono - agendas system
2
# Copyright (C) 2021  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 urllib.parse
18

  
19
from django.conf import settings
20

  
21

  
22
def translate_from_publik_url(url):
23
    if not url:
24
        return ''
25
    source_url = urllib.parse.urlparse(url)
26
    if source_url.scheme != 'publik':
27
        return url
28
    known_services = getattr(settings, 'KNOWN_SERVICES', None)
29
    if not known_services:
30
        return url
31
    for data in known_services.values():
32
        for slug, service_data in data.items():
33
            if slug == source_url.netloc:
34
                service_url = urllib.parse.urlparse(service_data['url'])
35
                return urllib.parse.urlunparse(
36
                    (service_url.scheme, service_url.netloc, source_url.path, '', '', None)
37
                )
38
    return ''
39

  
40

  
41
def translate_to_publik_url(url):
42
    if not url:
43
        return ''
44
    known_services = getattr(settings, 'KNOWN_SERVICES', None)
45
    if not known_services:
46
        return url
47
    source_url = urllib.parse.urlparse(url)
48
    for data in known_services.values():
49
        for slug, service_data in data.items():
50
            service_url = urllib.parse.urlparse(service_data['url'])
51
            if source_url.netloc == service_url.netloc and source_url.scheme == service_url.scheme:
52
                return str(urllib.parse.urlunparse(('publik', slug, source_url.path, '', '', None)))
53
    return url
chrono/utils/requests_wrapper.py
27 27
from requests import Session as RequestsSession
28 28
from requests.auth import AuthBase
29 29

  
30
from .publik_urls import translate_from_publik_url
30 31
from .signature import sign_url
31 32

  
32 33

  
......
45 46

  
46 47
class Requests(RequestsSession):
47 48
    def request(self, method, url, **kwargs):
49
        url = translate_from_publik_url(url)
48 50
        remote_service = kwargs.pop('remote_service', None)
49 51
        cache_duration = kwargs.pop('cache_duration', 15)
50 52
        invalidate_cache = kwargs.pop('invalidate_cache', False)
tests/api/test_fillslot.py
2507 2507
        resp.json['err_desc']
2508 2508
        == 'Some events belong to agendas that are not present in querystring: second-agenda'
2509 2509
    )
2510

  
2511

  
2512
def test_url_translation(app, some_data, user):
2513
    app.authorization = ('Basic', ('john.doe', 'password'))
2514
    agenda_id = Agenda.objects.filter(label='Foo bar')[0].id
2515
    assert Booking.objects.count() == 0
2516
    resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
2517
    fillslot_url = resp.json['data'][0]['api']['fillslot_url']
2518

  
2519
    params = {
2520
        'backoffice_url': 'https://demarches.example.com/backoffice/foo/bar',
2521
        'cancel_callback_url': 'https://demarches.example.com/foo/bar/jump',
2522
        'form_url': 'https://demarches.example.com/foo/bar',
2523
    }
2524

  
2525
    # https://demarches.example.com not in KNOWN_SERVICES, no URL translation
2526
    resp = app.post_json(fillslot_url, params=params)
2527
    booking = Booking.objects.get(pk=resp.json['booking_id'])
2528
    assert booking.backoffice_url == 'https://demarches.example.com/backoffice/foo/bar'
2529
    assert booking.cancel_callback_url == 'https://demarches.example.com/foo/bar/jump'
2530
    assert booking.form_url == 'https://demarches.example.com/foo/bar'
2531

  
2532
    # http://example.org/ is in KNOWN_SERVICES, translation happens
2533
    params = {
2534
        'backoffice_url': 'http://example.org/backoffice/foo/bar',
2535
        'cancel_callback_url': 'http://example.org/foo/bar/jump',
2536
        'form_url': 'http://example.org/foo/bar',
2537
    }
2538

  
2539
    resp = app.post_json(fillslot_url, params=params)
2540
    booking = Booking.objects.get(pk=resp.json['booking_id'])
2541
    assert booking.backoffice_url == 'publik://default/backoffice/foo/bar'
2542
    assert booking.cancel_callback_url == 'publik://default/foo/bar/jump'
2543
    assert booking.form_url == 'publik://default/foo/bar'
tests/manager/test_all.py
1195 1195
    assert resp.pyquery.find('.exception-hours span')[1].text == 'Exception for the afternoon'
1196 1196

  
1197 1197

  
1198
@pytest.mark.parametrize(
1199
    'view',
1200
    (
1201
        '/manage/agendas/%(agenda)s/%(year)d/%(month)d/%(day)d/',
1202
        '/manage/agendas/%(agenda)s/%(year)d/%(month)d/',
1203
    ),
1204
)
1205
def test_agenda_day_month_view_backoffice_url_translation(
1206
    app, admin_user, manager_user, api_user, settings, view
1207
):
1208
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1209
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
1210
    desk.save()
1211
    today = datetime.date.today()
1212
    meetingtype = MeetingType(agenda=agenda, label='Bar', duration=30)
1213
    meetingtype.save()
1214
    timeperiod = TimePeriod.objects.create(
1215
        desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 30)
1216
    )
1217
    timeperiod.save()
1218
    app.authorization = ('Basic', ('john.doe', 'password'))
1219
    login(app)
1220

  
1221
    resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
1222
    booking_url = resp.json['data'][0]['api']['fillslot_url']
1223

  
1224
    # unkown service, backoffice url stored and displayed as is
1225
    backoffice_url = 'http://example.net/foo/bar/'
1226
    resp = app.post(booking_url, params={'backoffice_url': backoffice_url})
1227
    cancel_url = resp.json['api']['cancel_url']
1228
    booking_id = resp.json['booking_id']
1229
    booking = Booking.objects.get(pk=booking_id)
1230
    assert booking.backoffice_url == backoffice_url
1231
    date = booking.event.start_datetime
1232
    url = view % {'agenda': agenda.id, 'year': date.year, 'month': date.month, 'day': date.day}
1233
    resp = app.get(url)
1234
    assert resp.text.count('div class="booking') == 1
1235
    assert backoffice_url in resp.text
1236

  
1237
    # reset booking
1238
    resp = app.post(cancel_url)
1239
    assert resp.json['err'] == 0
1240

  
1241
    # known service, backoffice url stored translated and displayed as it was passed
1242
    backoffice_url = 'http://example.org/backoffice/bar/'
1243
    resp = app.post(booking_url, params={'backoffice_url': backoffice_url})
1244
    cancel_url = resp.json['api']['cancel_url']
1245
    booking_id = resp.json['booking_id']
1246
    booking = Booking.objects.get(pk=booking_id)
1247
    assert booking.backoffice_url == 'publik://default/backoffice/bar/'
1248
    date = booking.event.start_datetime
1249
    resp = app.get(url)
1250
    assert resp.text.count('div class="booking') == 1
1251
    assert backoffice_url in resp.text
1252

  
1253

  
1198 1254
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
1199 1255
def test_agenda_day_view_late_meeting(app, admin_user, kind):
1200 1256
    today = datetime.date.today()
......
2673 2729
    app.get('/manage/agendas/%s/bookings/%s/cancel' % (agenda.pk, booking4.pk), status=404)
2674 2730

  
2675 2731

  
2732
def test_booking_cancellation_meetings_agenda_backoffice_url_translation(
2733
    app, admin_user, manager_user, managers_group, api_user
2734
):
2735
    agenda = Agenda.objects.create(label='Passeports', kind='meetings', view_role=managers_group)
2736
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2737
    meetingtype = MeetingType(agenda=agenda, label='passeport', duration=20)
2738
    meetingtype.save()
2739
    today = datetime.date(2018, 11, 10)  # fixed day
2740
    timeperiod_weekday = today.weekday()
2741
    timeperiod = TimePeriod(
2742
        desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
2743
    )
2744
    timeperiod.save()
2745

  
2746
    # book a slot
2747
    app.authorization = ('Basic', ('john.doe', 'password'))
2748
    bookings_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
2749
    booking_url = bookings_resp.json['data'][0]['api']['fillslot_url']
2750
    booking_json = app.post_json(booking_url, params={'backoffice_url': 'http://example.org/'}).json
2751

  
2752
    booking = Booking.objects.get(pk=booking_json['booking_id'])
2753
    assert booking.backoffice_url == 'publik://default/'
2754
    date = booking.event.start_datetime
2755
    month_view_url = '/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month)
2756

  
2757
    app.reset()
2758
    login(app, username='admin', password='admin')
2759
    resp = app.get(month_view_url)
2760
    resp = resp.click('Cancel')
2761
    assert 'http://example.org/' in resp.text
2762

  
2763

  
2676 2764
def test_agenda_notifications(app, admin_user, managers_group):
2677 2765
    agenda = Agenda.objects.create(label='Events', kind='events')
2678 2766
    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
tests/manager/test_event.py
413 413
    assert 'overbooking' in resp.text
414 414

  
415 415

  
416
def test_event_detail_backoffice_url_translation(app, admin_user):
417
    agenda = Agenda(label='Foo bar')
418
    agenda.save()
419
    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
420
    event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda)
421
    event.save()
422
    Booking.objects.create(event=event, backoffice_url='publik://default/foo/')
423
    app = login(app)
424
    resp = app.get('/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk))
425
    assert 'http://example.org/foo/' in resp.text
426

  
427

  
416 428
def test_delete_event(app, admin_user):
417 429
    agenda = Agenda(label='Foo bar')
418 430
    agenda.save()
......
988 1000
    assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 5
989 1001

  
990 1002

  
1003
def test_event_cancellation_error_report_backofice_url_translation(app, admin_user):
1004
    agenda = Agenda.objects.create(label='Events', kind='events')
1005
    event = Event.objects.create(
1006
        label='xyz', start_datetime=now() + datetime.timedelta(days=1), places=10, agenda=agenda
1007
    )
1008
    day = event.start_datetime
1009

  
1010
    def mocked_requests_connection_error(*args, **kwargs):
1011
        raise requests.exceptions.ConnectionError('unreachable')
1012

  
1013
    for _ in range(5):
1014
        Booking.objects.create(
1015
            event=event,
1016
            cancel_callback_url='http://example.org/jump/trigger/',
1017
            backoffice_url='publik://default/',
1018
        )
1019

  
1020
    login(app)
1021
    resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month))
1022
    resp = resp.click('Cancellation error reports')
1023
    assert 'No error report' in resp.text
1024

  
1025
    resp = app.get('/manage/agendas/%d/events/%d/cancel' % (agenda.pk, event.pk))
1026
    resp = resp.form.submit().follow()
1027
    assert 'Cancellation in progress' in resp.text
1028

  
1029
    with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
1030
        mock_response = mock.Mock(status_code=200)
1031
        mock_send.return_value = mock_response
1032
        mock_send.side_effect = mocked_requests_connection_error
1033
        call_command('cancel_events')
1034

  
1035
    event.refresh_from_db()
1036
    assert not event.cancelled and not event.cancellation_scheduled
1037
    assert not Booking.objects.filter(cancellation_datetime__isnull=False).exists()
1038

  
1039
    resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, day.year, day.month))
1040
    assert 'Errors occured during cancellation of event "xyz".' in resp.text
1041

  
1042
    resp = resp.click('Cancellation error reports')
1043
    assert '(5 failures)' in resp.text
1044

  
1045
    resp = resp.click(str(event))
1046
    assert 'http://example.org/' in resp.text
1047

  
1048

  
991 1049
def test_event_cancellation_forbidden(app, admin_user):
992 1050
    agenda = Agenda.objects.create(label='Events', kind='events')
993 1051
    event = Event.objects.create(
tests/manager/test_resource.py
560 560
    resp = resp.follow()
561 561
    assert '/manage/resource/%s/' % resource.pk not in resp.text
562 562
    assert '/manage/agendas/%s/resource/%s/delete/' % (agenda.pk, resource.pk) not in resp.text
563

  
564

  
565
@pytest.mark.parametrize(
566
    'view',
567
    (
568
        '/manage/resource/%(resource)s/%(year)d/%(month)d/%(day)d/',
569
        '/manage/resource/%(resource)s/%(year)d/%(month)d/',
570
    ),
571
)
572
def test_agenda_day_month_view_backoffice_url_translation(
573
    app, admin_user, manager_user, api_user, settings, view
574
):
575
    today = datetime.date.today()
576
    resource = Resource.objects.create(label='Foo bar')
577
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
578
    desk = Desk.objects.create(agenda=agenda, label='Desk')
579
    meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
580
    TimePeriod.objects.create(
581
        desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 30)
582
    )
583

  
584
    login(app)
585
    url = view % {'resource': resource.id, 'year': today.year, 'month': today.month, 'day': today.day}
586

  
587
    # book some slots
588
    for hour, minute in [(10, 30), (14, 0)]:
589
        event = Event.objects.create(
590
            agenda=agenda,
591
            places=1,
592
            desk=desk,
593
            meeting_type=meetingtype,
594
            start_datetime=now().replace(hour=hour, minute=minute),
595
        )
596
        event.resources.add(resource)
597
        Booking.objects.create(event=event, backoffice_url='publik://default/foo/')
598

  
599
    resp = app.get(url)
600
    assert 'http://example.org/foo/' in resp.text
tests/test_agendas.py
1784 1784
    assert 'Edit or cancel booking' in mail.alternatives[0][0]
1785 1785
    assert 'href="https://example.org/"' in mail.alternatives[0][0]
1786 1786

  
1787
    # check url translation
1788
    Booking.objects.all().delete()
1789
    mailoutbox.clear()
1790
    freezer.move_to('2020-01-01 14:00')
1791
    Booking.objects.create(event=event, user_email='t@test.org', form_url='publik://default/someform/1/')
1792
    freezer.move_to('2020-01-02 15:00')
1793
    call_command('send_booking_reminders')
1794

  
1795
    mail = mailoutbox[0]
1796
    assert 'If in need to cancel it, you can do so here: http://example.org/someform/1/' in mail.body
1797
    assert 'Edit or cancel booking' in mail.alternatives[0][0]
1798
    assert 'href="http://example.org/someform/1/"' in mail.alternatives[0][0]
1799

  
1787 1800

  
1788 1801
@override_settings(SMS_URL='https://passerelle.test.org/sms/send/', SMS_SENDER='EO', TIME_ZONE='UTC')
1789 1802
def test_agenda_reminders_sms_content(freezer):
1790
-