Projet

Général

Profil

0001-manager-weekly-wiew-for-agendas-resources-33404.patch

Lauréline Guérin, 25 octobre 2022 19:27

Télécharger (102 ko)

Voir les différences:

Subject: [PATCH] manager: weekly wiew for agendas & resources (#33404)

 .../agendas/migrations/0090_default_view.py   |   7 +-
 chrono/agendas/models.py                      |   1 +
 chrono/manager/static/js/chrono.manager.js    |   4 +-
 .../chrono/manager_agenda_week_view.html      |  32 +
 .../manager_events_agenda_day_view.html       |   1 +
 .../manager_events_agenda_month_view.html     |   1 +
 .../manager_events_agenda_week_view.html      |  44 +
 .../manager_meetings_agenda_day_view.html     |   1 +
 .../manager_meetings_agenda_month_view.html   |  58 +-
 ...etings_agenda_week_timetable_fragment.html |  59 ++
 .../manager_meetings_agenda_week_view.html    |  12 +
 .../chrono/manager_resource_day_view.html     |   1 +
 .../chrono/manager_resource_detail.html       |   2 +
 .../chrono/manager_resource_month_view.html   |  44 +-
 ...ager_resource_week_timetable_fragment.html |  43 +
 .../chrono/manager_resource_week_view.html    |  32 +
 chrono/manager/urls.py                        |  15 +
 chrono/manager/views.py                       | 301 ++++--
 tests/manager/test_all.py                     | 860 ++++++++++++++++--
 tests/manager/test_resource.py                | 292 +++++-
 20 files changed, 1530 insertions(+), 280 deletions(-)
 create mode 100644 chrono/manager/templates/chrono/manager_agenda_week_view.html
 create mode 100644 chrono/manager/templates/chrono/manager_events_agenda_week_view.html
 create mode 100644 chrono/manager/templates/chrono/manager_meetings_agenda_week_timetable_fragment.html
 create mode 100644 chrono/manager/templates/chrono/manager_meetings_agenda_week_view.html
 create mode 100644 chrono/manager/templates/chrono/manager_resource_week_timetable_fragment.html
 create mode 100644 chrono/manager/templates/chrono/manager_resource_week_view.html
chrono/agendas/migrations/0090_default_view.py
12 12
            model_name='agenda',
13 13
            name='default_view',
14 14
            field=models.CharField(
15
                choices=[('day', 'Day view'), ('month', 'Month view'), ('open_events', 'Open events')],
15
                choices=[
16
                    ('day', 'Day view'),
17
                    ('week', 'Week view'),
18
                    ('month', 'Month view'),
19
                    ('open_events', 'Open events'),
20
                ],
16 21
                max_length=20,
17 22
                verbose_name='Default view',
18 23
            ),
chrono/agendas/models.py
70 70

  
71 71
AGENDA_VIEWS = (
72 72
    ('day', _('Day view')),
73
    ('week', _('Week view')),
73 74
    ('month', _('Month view')),
74 75
    ('open_events', _('Open events')),
75 76
)
chrono/manager/static/js/chrono.manager.js
10 10
    $('.date-picker button').on('click', function() {
11 11
    if ($('[name=day]').val()) {
12 12
       window.location = '../../../' + $('[name=year]').val() + '/' + $('[name=month]').val() + '/' + $('[name=day]').val() + '/';
13
    } else {
13
    } else if ($('[name=month]').val()) {
14 14
        window.location = '../../' + $('[name=year]').val() + '/' + $('[name=month]').val() + '/';
15
    } else {
16
        window.location = '../../../' + $('[name=year]').val() + '/week/' + $('[name=week]').val() + '/';
15 17
    }
16 18
    return false;
17 19
  });
chrono/manager/templates/chrono/manager_agenda_week_view.html
1
{% extends "chrono/manager_agenda_view.html" %}
2
{% load i18n %}
3

  
4
{% block bodyargs %}class="weekview"{% endblock %}
5

  
6
{% block breadcrumb %}
7
  {{ block.super }}
8
  <a>{{ view.date|date:"F Y" }}</a>
9
{% endblock %}
10

  
11
{% block appbar %}
12
  <h2>
13
    <a href="{{ view.get_previous_week_url }}">←</a>
14
    <span class="date-title">{{ view.date|date:_("Y \w\e\e\k W") }}</span>
15
    {% with selected_week=view.date|date:"W" selected_year=view.date|date:"Y" %}
16
      <div class="date-picker" style="display: none">
17
        <select name="week">{% for week, week_label in view.get_weeks %}<option value="{{ week }}" {% if selected_week == week %}selected{% endif %}>{{ week_label }}</option>{% endfor %}</select>
18
        <select name="year">{% for year in view.get_years %}<option value="{{ year }}" {% if selected_year == year %}selected{% endif %}>{{ year }}</option>{% endfor %}</select>
19
        <button>{% trans 'Set Date' %}</button>
20
      </div>
21
    {% endwith %}
22
    <a href="{{ view.get_next_week_url }}">→</a>
23
  </h2>
24
  <span class="actions">
25
    {% block actions %}
26
      {% if user_can_manage %}
27
        <a href="{{ agenda.get_settings_url }}">{% trans 'Settings' %}</a>
28
      {% endif %}
29
      <a href="" onclick="window.print()">{% trans 'Print' %}</a>
30
    {% endblock %}
31
  </span>
32
{% endblock %}
chrono/manager/templates/chrono/manager_events_agenda_day_view.html
11 11
  {{ block.super }}
12 12
  <a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
13 13
  <a href="{% url 'chrono-manager-agenda-month-view' pk=agenda.pk year=view.date|date:"Y" month=view.date|date:"n" %}">{% trans 'Month view' %}</a>
14
  <a href="{% url 'chrono-manager-agenda-week-view' pk=agenda.pk year=view.date|date:"Y" week=view.date|date:"W" %}">{% trans 'Week view' %}</a>
14 15
{% endblock %}
15 16

  
16 17
{% block content %}
chrono/manager/templates/chrono/manager_events_agenda_month_view.html
11 11
  </ul>
12 12
  {{ block.super }}
13 13
  <a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
14
  <a href="{% url 'chrono-manager-agenda-week-view' pk=agenda.pk year=view.date|date:"Y" week=view.date|date:"W" %}">{% trans 'Week view' %}</a>
14 15
  <a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=view.date|date:"Y" month=view.date|date:"m" day=view.date|date:"d" %}">{% trans 'Day view' %}</a>
15 16
{% endblock %}
16 17

  
chrono/manager/templates/chrono/manager_events_agenda_week_view.html
1
{% extends "chrono/manager_agenda_week_view.html" %}
2
{% load i18n %}
3

  
4
{% block actions %}
5
  <a class="extra-actions-menu-opener"></a>
6
  <ul class="extra-actions-menu">
7
    <li><a href="{% url 'chrono-manager-event-cancellation-report-list' pk=agenda.pk %}">{% trans 'Cancellation error reports' %}</a></li>
8
    {% if agenda.subscriptions.exists %}
9
      <li><a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans 'Timesheet' %}</a></li>
10
    {% endif %}
11
  </ul>
12
  {{ block.super }}
13
  <a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
14
  <a href="{% url 'chrono-manager-agenda-month-view' pk=agenda.pk year=view.date|date:"Y" month=view.date|date:"m" %}">{% trans 'Month view' %}</a>
15
  <a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=view.date|date:"Y" month=view.date|date:"m" day=view.date|date:"d" %}">{% trans 'Day view' %}</a>
16
{% endblock %}
17

  
18
{% block content %}
19
  <div class="section">
20
    <h3>{% trans "Events" %}</h3>
21
    {% include 'chrono/manager_event_cancellation_report_notice.html' %}
22
    <div>
23
      {% if object_list %}
24
        <ul class="objects-list single-links">
25
          {% for object in object_list %}
26
            {% if object.is_exception %}
27
              <li><a class="disabled">{% trans "Exception:"%} {{ object }}</a></li>
28
            {% else %}
29
              {% include 'chrono/manager_agenda_event_fragment.html' with event=object %}
30
            {% endif %}
31
          {% endfor %}
32
        </ul>
33
        {% include "gadjo/pagination.html" %}
34
      {% else %}
35
        <div class="big-msg-info">
36
          {% blocktrans trimmed %}
37
            This week doesn't have any event configured.
38
          {% endblocktrans %}
39
        </div>
40
      {% endif %}
41
    </div>
42
  </div>
43

  
44
{% endblock %}
chrono/manager/templates/chrono/manager_meetings_agenda_day_view.html
4 4
{% block actions %}
5 5
  {{ block.super }}
6 6
  <a href="{% url 'chrono-manager-agenda-month-view' pk=agenda.id year=view.date|date:"Y" month=view.date|date:"n" %}">{% trans 'Month view' %}</a>
7
  <a href="{% url 'chrono-manager-agenda-week-view' pk=agenda.id year=view.date|date:"Y" week=view.date|date:"W" %}">{% trans 'Week view' %}</a>
7 8
{% endblock %}
8 9

  
9 10
{% block content %}
chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html
3 3

  
4 4
{% block actions %}
5 5
  {{ block.super }}
6
  <a href="{% url 'chrono-manager-agenda-week-view' pk=agenda.id year=view.date|date:"Y" week=view.date|date:"W" %}">{% trans 'Week view' %}</a>
6 7
  <a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.id year=view.date|date:"Y" month=view.date|date:"m" day=view.date|date:"d" %}">{% trans 'Day view' %}</a>
7 8
{% endblock %}
8 9

  
9 10
{% block content %}
10

  
11
  {% for week_days in view.get_timetable_infos %}
12
    {% if forloop.first %}
13
      <table class="agenda-table month-view {% if single_desk %}single-desk{% endif %}">
14
        <tbody>
15
    {% endif %}
16
    <tr>
17
      <th></th>
18
      {% for day in week_days.days %}
19
        <th class="weekday {% if day.today %}today{% endif %}">{% if not day.other_month %}<a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.id year=day.date|date:"Y" month=day.date|date:"m" day=day.date|date:"d" %}">{{ day.date|date:"l j" }}</a>{% endif %}</th>
20
      {% endfor %}
21
    </tr>
22
    {% for hour in week_days.periods %}
23
      <tr class="{% cycle 'odd' 'even' %}">
24
        <th class="hour">{{ hour|date:"TIME_FORMAT" }}</th>
25
        {% for day in week_days.days %}
26
          <td class="{% if day.other_month %}other-month{% endif %} {% if day.today %}today{% endif %}">
27
            {% if forloop.parentloop.first %}
28
              {% for slot in day.infos.opening_hours %}
29
                <div class="opening-hours" style="height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%;left:{{ slot.css_left|stringformat:".1f" }}%;"></div>
30
              {% endfor %}
31

  
32
              {% for slot in day.infos.exceptions %}
33
                <div class="exception-hours" style="height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%;left:{{ slot.css_left|stringformat:".1f" }}%;" {% if slot.label %}title="{{ slot.label }}"{% endif %}></div>
34
              {% endfor %}
35

  
36
              {% for slot in day.infos.booked_slots %}
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
                  <span class="start-time">{{slot.booking.event.start_datetime|date:"TIME_FORMAT"}}</span>
39
                  <a {% if slot.booking.get_backoffice_url %}href="{{slot.booking.get_backoffice_url}}"{% endif %}>{{ slot.booking.get_user_block }}</a>
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
                  {% if not single_desk %}<span class="desk">{{ slot.desk }}</span>{% endif %}
42
                  {% if slot.booking.color %}<span class="booking-color-label booking-bg-color-{{ slot.booking.color.index }}">{{ slot.booking.color }}</span>{% endif %}
43
                </div>
44
              {% endfor %}
45
            {% endif %}
46
          </td>
47
        {% endfor %}
48
      </tr>
49
    {% endfor %}
50
    {% resetcycle %}
51
    {% if forloop.last %}
52
      </tbody>
53
      </table>
54
    {% endif %}
55

  
56
  {% empty %}
57
    <div class="closed-for-the-day">
58
      <p>{% trans "No opening hours this month." %}</p>
59
    </div>
60
  {% endfor %}
61

  
62
  {% if booking_colors %}
63
    {% include "chrono/booking_color_legend.html" with colors=booking_colors %}
64
  {% endif %}
65

  
11
  {% include "chrono/manager_meetings_agenda_week_timetable_fragment.html" %}
66 12
{% endblock %}
chrono/manager/templates/chrono/manager_meetings_agenda_week_timetable_fragment.html
1
{% load i18n %}
2
{% for week_days in view.get_timetable_infos %}
3
  {% if forloop.first %}
4
    <table class="agenda-table {{ kind }}-view {% if kind == 'week' %}hourspan-{{ hour_span }}{% endif %} {% if single_desk %}single-desk{% endif %}">
5
      <tbody>
6
  {% endif %}
7
  <tr>
8
    <th></th>
9
    {% for day in week_days.days %}
10
      <th class="weekday {% if day.today %}today{% endif %}">{% if kind == 'month' and not day.other_month or kind == 'week' %}<a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.id year=day.date|date:"Y" month=day.date|date:"m" day=day.date|date:"d" %}">{{ day.date|date:"l j" }}{% if kind == 'week' %}<br />{{ day.date|date:"F" }}{% endif %}</a>{% endif %}</th>
11
    {% endfor %}
12
  </tr>
13
  {% for hour in week_days.periods %}
14
    <tr class="{% cycle 'odd' 'even' %}">
15
      <th class="hour">{{ hour|date:"TIME_FORMAT" }}</th>
16
      {% for day in week_days.days %}
17
        <td class="{% if kind == 'month' and day.other_month %}other-month{% endif %} {% if day.today %}today{% endif %}">
18
          {% if forloop.parentloop.first %}
19
            {% for slot in day.infos.opening_hours %}
20
              <div class="opening-hours" style="height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%;left:{{ slot.css_left|stringformat:".1f" }}%;"></div>
21
            {% endfor %}
22

  
23
            {% for slot in day.infos.exceptions %}
24
              <div class="exception-hours" style="height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%;left:{{ slot.css_left|stringformat:".1f" }}%;" {% if slot.label %}title="{{ slot.label }}"{% endif %}></div>
25
            {% endfor %}
26

  
27
            {% for slot in day.infos.booked_slots %}
28
              <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" }}%;">
29
                <span class="start-time">{{slot.booking.event.start_datetime|date:"TIME_FORMAT"}}</span>
30
                <a {% if slot.booking.get_backoffice_url %}href="{{slot.booking.get_backoffice_url}}"{% endif %}>{{ slot.booking.get_user_block }}</a>
31
                <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>
32
                {% if not single_desk %}<span class="desk">{{ slot.desk }}</span>{% endif %}
33
                {% if slot.booking.color %}<span class="booking-color-label booking-bg-color-{{ slot.booking.color.index }}">{{ slot.booking.color }}</span>{% endif %}
34
              </div>
35
            {% endfor %}
36
          {% endif %}
37
        </td>
38
      {% endfor %}
39
    </tr>
40
  {% endfor %}
41
  {% resetcycle %}
42
  {% if forloop.last %}
43
    </tbody>
44
    </table>
45
  {% endif %}
46

  
47
{% empty %}
48
  <div class="closed-for-the-day">
49
    {% if kind == 'month' %}
50
      <p>{% trans "No opening hours this month." %}</p>
51
    {% else %}
52
      <p>{% trans "No opening hours this week." %}</p>
53
    {% endif %}
54
  </div>
55
{% endfor %}
56

  
57
{% if booking_colors %}
58
  {% include "chrono/booking_color_legend.html" with colors=booking_colors %}
59
{% endif %}
chrono/manager/templates/chrono/manager_meetings_agenda_week_view.html
1
{% extends "chrono/manager_agenda_week_view.html" %}
2
{% load i18n %}
3

  
4
{% block actions %}
5
  {{ block.super }}
6
  <a href="{% url 'chrono-manager-agenda-month-view' pk=agenda.id year=view.date|date:"Y" month=view.date|date:"m" %}">{% trans 'Month view' %}</a>
7
  <a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.id year=view.date|date:"Y" month=view.date|date:"m" day=view.date|date:"d" %}">{% trans 'Day view' %}</a>
8
{% endblock %}
9

  
10
{% block content %}
11
  {% include "chrono/manager_meetings_agenda_week_timetable_fragment.html" %}
12
{% endblock %}
chrono/manager/templates/chrono/manager_resource_day_view.html
23 23
{% endblock %}
24 24
{% block appbar-extras %}
25 25
  <a href="{% url 'chrono-manager-resource-month-view' pk=resource.pk year=view.date|date:"Y" month=view.date|date:"n" %}">{% trans 'Month view' %}</a>
26
  <a href="{% url 'chrono-manager-resource-week-view' pk=resource.pk year=view.date|date:"Y" week=view.date|date:"W" %}">{% trans 'Week view' %}</a>
26 27
{% endblock %}
27 28

  
28 29
{% block content %}
chrono/manager/templates/chrono/manager_resource_detail.html
22 22
      {% endif %}
23 23
      {% now "Y" as today_year %}
24 24
      {% now "n" as today_month %}
25
      {% now "W" as today_week %}
25 26
      {% now "j" as today_day %}
26 27
      <a href="{% url 'chrono-manager-resource-month-view' pk=resource.pk year=today_year month=today_month %}">{% trans 'Month view' %}</a>
28
      <a href="{% url 'chrono-manager-resource-week-view' pk=resource.pk year=today_year week=today_week %}">{% trans 'Week view' %}</a>
27 29
      <a href="{% url 'chrono-manager-resource-day-view' pk=resource.pk year=today_year month=today_month day=today_day %}">{% trans 'Day view' %}</a>
28 30
    {% endblock %}
29 31
  </span>
chrono/manager/templates/chrono/manager_resource_month_view.html
23 23
  </h2>
24 24
{% endblock %}
25 25
{% block appbar-extras %}
26
  <a href="{% url 'chrono-manager-resource-day-view' pk=resource.pk year=view.date|date:"Y" month=view.date|date:"n" day=1 %}">{% trans 'Day view' %}</a>
26
  <a href="{% url 'chrono-manager-resource-week-view' pk=resource.pk year=view.date|date:"Y" week=view.date|date:"W" %}">{% trans 'Week view' %}</a>
27
  <a href="{% url 'chrono-manager-resource-day-view' pk=resource.pk year=view.date|date:"Y" month=view.date|date:"n" day=view.date|date:"d" %}">{% trans 'Day view' %}</a>
27 28
{% endblock %}
28 29

  
29 30
{% block content %}
30

  
31
  {% for week_days in view.get_timetable_infos %}
32

  
33
    {% if forloop.first %}
34
      <table class="agenda-table month-view single-desk">
35
        <tbody>
36
    {% endif %}
37
    <tr>
38
      <th></th>
39
      {% for day in week_days.days %}
40
        <th class="weekday {% if day.today %}today{% endif %}">{% if not day.other_month %}<a href="{% url 'chrono-manager-resource-day-view' pk=resource.pk year=day.date|date:"Y" month=day.date|date:"m" day=day.date|date:"j" %}">{{ day.date|date:"l j" }}</a>{% endif %}</th>
41
      {% endfor %}
42
    </tr>
43
    {% for hour in week_days.periods %}
44
      <tr class="{% cycle 'odd' 'even' %}">
45
        <th class="hour">{{ hour|date:"TIME_FORMAT" }}</th>
46
        {% for day in week_days.days %}
47
          <td class="{% if day.other_month %}other-month{% endif %} {% if day.today %}today{% endif %}">
48
            {% if forloop.parentloop.first %}
49
              {% for slot in day.infos.booked_slots %}
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
                  <span class="start-time">{{ slot.booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
52
                  <a {% if slot.booking.get_backoffice_url %}href="{{ slot.booking.get_backoffice_url }}"{% endif %}>{{ slot.booking.get_user_block }}</a>
53
                </div>
54
              {% endfor %}
55
            {% endif %}
56
          </td>
57
        {% endfor %}
58
      </tr>
59
    {% endfor %}
60
    {% if forloop.last %}
61
      </tbody>
62
      </table>
63
    {% endif %}
64
  {% empty %}
65
    <div class="closed-for-the-day">
66
      <p>{% trans "No bookings this month." %}</p>
67
    </div>
68
  {% endfor %}
69

  
31
  {% include "chrono/manager_resource_week_timetable_fragment.html" %}
70 32
{% endblock %}
chrono/manager/templates/chrono/manager_resource_week_timetable_fragment.html
1
{% load i18n %}
2
{% for week_days in view.get_timetable_infos %}
3

  
4
  {% if forloop.first %}
5
    <table class="agenda-table {{ kind }}-view {% if kind == 'week' %}hourspan-{{ hour_span }}{% endif %} single-desk">
6
      <tbody>
7
  {% endif %}
8
  <tr>
9
    <th></th>
10
    {% for day in week_days.days %}
11
      <th class="weekday {% if day.today %}today{% endif %}">{% if kind == 'month' and not day.other_month or kind == 'week' %}<a href="{% url 'chrono-manager-resource-day-view' pk=resource.pk year=day.date|date:"Y" month=day.date|date:"m" day=day.date|date:"d" %}">{{ day.date|date:"l j" }}{% if kind == 'week' %}<br />{{ day.date|date:"F" }}{% endif %}</a>{% endif %}</th>
12
    {% endfor %}
13
  </tr>
14
  {% for hour in week_days.periods %}
15
    <tr class="{% cycle 'odd' 'even' %}">
16
      <th class="hour">{{ hour|date:"TIME_FORMAT" }}</th>
17
      {% for day in week_days.days %}
18
        <td class="{% if kind == 'month' and day.other_month %}other-month{% endif %} {% if day.today %}today{% endif %}">
19
          {% if forloop.parentloop.first %}
20
            {% for slot in day.infos.booked_slots %}
21
              <div class="booking" style="height:{{ slot.css_height|stringformat:".1f" }}%;min-height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%">
22
                <span class="start-time">{{ slot.booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
23
                <a {% if slot.booking.get_backoffice_url %}href="{{ slot.booking.get_backoffice_url }}"{% endif %}>{{ slot.booking.get_user_block }}</a>
24
              </div>
25
            {% endfor %}
26
          {% endif %}
27
        </td>
28
      {% endfor %}
29
    </tr>
30
  {% endfor %}
31
  {% if forloop.last %}
32
    </tbody>
33
    </table>
34
  {% endif %}
35
{% empty %}
36
  <div class="closed-for-the-day">
37
    {% if kind == 'month' %}
38
      <p>{% trans "No bookings this month." %}</p>
39
    {% else %}
40
      <p>{% trans "No bookings this week." %}</p>
41
    {% endif %}
42
  </div>
43
{% endfor %}
chrono/manager/templates/chrono/manager_resource_week_view.html
1
{% extends "chrono/manager_resource_detail.html" %}
2
{% load i18n %}
3

  
4
{% block bodyargs %}class="weekview"{% endblock %}
5

  
6
{% block breadcrumb %}
7
  {{ block.super }}
8
  <a>{{ view.date|date:"F Y" }}</a>
9
{% endblock %}
10

  
11
{% block appbar-title %}
12
  <h2>
13
    <a href="{{ view.get_previous_week_url }}">←</a>
14
    <span class="date-title">{{ view.date|date:_("Y \w\e\e\k W") }}</span>
15
    {% with selected_week=view.date|date:"W" selected_year=view.date|date:"Y" %}
16
      <div class="date-picker" style="display: none">
17
        <select name="week">{% for week, week_label in view.get_weeks %}<option value="{{ week }}" {% if selected_week == week %}selected{% endif %}>{{ week_label }}</option>{% endfor %}</select>
18
        <select name="year">{% for year in view.get_years %}<option value="{{ year }}" {% if selected_year == year %}selected{% endif %}>{{ year }}</option>{% endfor %}</select>
19
        <button>{% trans 'Set Date' %}</button>
20
      </div>
21
    {% endwith %}
22
    <a href="{{ view.get_next_week_url }}">→</a>
23
  </h2>
24
{% endblock %}
25
{% block appbar-extras %}
26
  <a href="{% url 'chrono-manager-resource-month-view' pk=resource.pk year=view.date|date:"Y" month=view.date|date:"n" %}">{% trans 'Month view' %}</a>
27
  <a href="{% url 'chrono-manager-resource-day-view' pk=resource.pk year=view.date|date:"Y" month=view.date|date:"n" day=view.date|date:"d" %}">{% trans 'Day view' %}</a>
28
{% endblock %}
29

  
30
{% block content %}
31
  {% include "chrono/manager_resource_week_timetable_fragment.html" %}
32
{% endblock %}
chrono/manager/urls.py
73 73
        views.resource_monthly_view,
74 74
        name='chrono-manager-resource-month-view',
75 75
    ),
76
    re_path(
77
        r'^resource/(?P<pk>\d+)/(?P<year>[0-9]{4})/week/(?P<week>[0-9]+)/$',
78
        views.resource_weekly_view,
79
        name='chrono-manager-resource-week-view',
80
    ),
76 81
    re_path(
77 82
        r'^resource/(?P<pk>\d+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/(?P<day>[0-9]+)/$',
78 83
        views.resource_day_view,
......
107 112
        views.agenda_monthly_view,
108 113
        name='chrono-manager-agenda-month-view',
109 114
    ),
115
    path(
116
        'agendas/<int:pk>/week/',
117
        views.agenda_week_redirect_view,
118
        name='chrono-manager-agenda-week-redirect-view',
119
    ),
120
    re_path(
121
        r'^agendas/(?P<pk>\d+)/(?P<year>[0-9]{4})/week/(?P<week>[0-9]+)/$',
122
        views.agenda_weekly_view,
123
        name='chrono-manager-agenda-week-view',
124
    ),
110 125
    path(
111 126
        'agendas/<int:pk>/day/',
112 127
        views.agenda_day_redirect_view,
chrono/manager/views.py
57 57
    TemplateView,
58 58
    UpdateView,
59 59
    View,
60
    WeekArchiveView,
60 61
)
61 62
from django.views.generic.dates import MonthMixin, YearMixin
62 63
from weasyprint import HTML
......
250 251
    def get_months(self):
251 252
        return [(str(x), MONTHS[x]) for x in range(1, 13)]
252 253

  
254
    def get_weeks(self):
255
        return [(str(x), _('Week %s') % x) for x in range(1, 53)]
256

  
253 257
    def get_years(self):
254 258
        year = now().year
255 259
        return [str(x) for x in range(year - 1, year + 5)]
......
384 388
resource_day_view = ResourceDayView.as_view()
385 389

  
386 390

  
387
class ResourceMonthView(DateMixin, MonthArchiveView):
388
    template_name = 'chrono/manager_resource_month_view.html'
389
    model = Event
390
    month_format = '%m'
391
    date_field = 'start_datetime'
392
    allow_empty = True
393
    allow_future = True
394

  
395
    def dispatch(self, request, *args, **kwargs):
396
        self.resource = get_object_or_404(Resource, pk=kwargs['pk'])
397
        if not self.resource.can_be_viewed(request.user):
398
            raise PermissionDenied()
399
        try:
400
            self.date = make_aware(
401
                datetime.datetime.strptime(
402
                    '%s-%s-%s 06:00' % (self.get_year(), self.get_month(), 1), '%Y-%m-%d %H:%M'
403
                )
404
            )
405
        except ValueError:
406
            raise Http404
407
        return super().dispatch(request, *args, **kwargs)
408

  
391
class ResourceWeekMonthMixin:
409 392
    def get_queryset(self):
410 393
        queryset = (
411 394
            self.resource.event_set.all()
......
418 401
        context = super().get_context_data(**kwargs)
419 402

  
420 403
        context['resource'] = self.resource
421

  
422
        return context
423

  
424
    def get_previous_month_url(self):
425
        previous_month = self.get_previous_month(self.date.date())
426
        return reverse(
427
            'chrono-manager-resource-month-view',
428
            kwargs={'pk': self.resource.pk, 'year': previous_month.year, 'month': previous_month.month},
404
        context['kind'] = self.kind
405
        context['hour_span'] = 1
406
        durations = MeetingType.objects.filter(agenda__resources=self.resource).values_list(
407
            'duration', flat=True
429 408
        )
409
        if durations:
410
            gcd = durations[0]
411
            for duration in durations[1:]:
412
                gcd = math.gcd(duration, gcd)
413
            context['hour_span'] = max(60 // gcd, 1)
430 414

  
431
    def get_next_month_url(self):
432
        next_month = self.get_next_month(self.date.date())
433
        return reverse(
434
            'chrono-manager-resource-month-view',
435
            kwargs={'pk': self.resource.pk, 'year': next_month.year, 'month': next_month.month},
436
        )
415
        return context
437 416

  
438 417
    def get_timetable_infos(self):
439 418
        interval = datetime.timedelta(minutes=60)
......
469 448
        hide_weekend = hide_weekend_timeperiod and hide_weekend_event
470 449

  
471 450
        # avoid displaying empty first week
472
        first_week_offset = int(
473
            (hide_sunday and self.date.weekday() == 6) or (hide_weekend and self.date.weekday() == 5)
474
        )
475

  
451
        first_week_offset = 0
476 452
        first_week_number = self.date.isocalendar()[1]
477
        if first_week_number >= 52:
478
            first_week_number = 0
479
        last_month_day = self.get_next_month(self.date.date()) - datetime.timedelta(days=1)
480
        last_week_number = last_month_day.isocalendar()[1]
453
        last_week_number = first_week_number
454
        if self.kind == 'month':
455
            first_week_offset = int(
456
                (hide_sunday and self.date.weekday() == 6) or (hide_weekend and self.date.weekday() == 5)
457
            )
458
            if first_week_number >= 52:
459
                first_week_number = 0
460
            last_day = self.get_next(self.date.date()) - datetime.timedelta(days=1)
461
            last_week_number = last_day.isocalendar()[1]
481 462

  
482
        if last_week_number < first_week_number:  # new year
483
            last_week_number = 53
463
            if last_week_number < first_week_number:  # new year
464
                last_week_number = 53
484 465

  
485 466
        for week_number in range(first_week_number + first_week_offset, last_week_number + 1):
486 467
            yield self.get_week_timetable_infos(
......
528 509
            # until the end of the last hour.
529 510
            max_date += datetime.timedelta(hours=1)
530 511

  
531
        # compute booking and opening hours only for current month
532
        if self.date.month != day.month:
512
        # compute booking and opening hours only for current month/week
513
        if self.kind == 'month' and timetable['other_month']:
533 514
            return timetable
534 515

  
535 516
        while period <= max_date:
......
552 533
        return timetable
553 534

  
554 535

  
536
class ResourceWeekView(ResourceWeekMonthMixin, DateMixin, WeekArchiveView):
537
    template_name = 'chrono/manager_resource_week_view.html'
538
    model = Event
539
    week_format = '%W'
540
    date_field = 'start_datetime'
541
    allow_empty = True
542
    allow_future = True
543
    kind = 'week'
544

  
545
    def dispatch(self, request, *args, **kwargs):
546
        self.resource = get_object_or_404(Resource, pk=kwargs['pk'])
547
        if not self.resource.can_be_viewed(request.user):
548
            raise PermissionDenied()
549
        try:
550
            date = datetime.datetime.strptime('%s-W%s-1' % (self.get_year(), self.get_week()), "%Y-W%W-%w")
551
            self.date = make_aware(
552
                datetime.datetime.strptime(
553
                    '%s-%s-%s 06:00' % (self.get_year(), date.month, date.day), '%Y-%m-%d %H:%M'
554
                )
555
            )
556
        except ValueError:
557
            raise Http404
558
        return super().dispatch(request, *args, **kwargs)
559

  
560
    def get_previous_week_url(self):
561
        previous_week = self.get_previous_week(self.date.date())
562
        return reverse(
563
            'chrono-manager-resource-week-view',
564
            kwargs={'pk': self.resource.pk, 'year': previous_week.year, 'week': previous_week.strftime('%W')},
565
        )
566

  
567
    def get_next_week_url(self):
568
        next_week = self.get_next_week(self.date.date())
569
        return reverse(
570
            'chrono-manager-resource-week-view',
571
            kwargs={'pk': self.resource.pk, 'year': next_week.year, 'week': next_week.strftime('%W')},
572
        )
573

  
574
    def get_next(self, date):
575
        return self.get_next_week(date)
576

  
577

  
578
resource_weekly_view = ResourceWeekView.as_view()
579

  
580

  
581
class ResourceMonthView(ResourceWeekMonthMixin, DateMixin, MonthArchiveView):
582
    template_name = 'chrono/manager_resource_month_view.html'
583
    model = Event
584
    month_format = '%m'
585
    date_field = 'start_datetime'
586
    allow_empty = True
587
    allow_future = True
588
    kind = 'month'
589

  
590
    def dispatch(self, request, *args, **kwargs):
591
        self.resource = get_object_or_404(Resource, pk=kwargs['pk'])
592
        if not self.resource.can_be_viewed(request.user):
593
            raise PermissionDenied()
594
        try:
595
            self.date = make_aware(
596
                datetime.datetime.strptime(
597
                    '%s-%s-%s 06:00' % (self.get_year(), self.get_month(), 1), '%Y-%m-%d %H:%M'
598
                )
599
            )
600
        except ValueError:
601
            raise Http404
602
        return super().dispatch(request, *args, **kwargs)
603

  
604
    def get_previous_month_url(self):
605
        previous_month = self.get_previous_month(self.date.date())
606
        return reverse(
607
            'chrono-manager-resource-month-view',
608
            kwargs={'pk': self.resource.pk, 'year': previous_month.year, 'month': previous_month.month},
609
        )
610

  
611
    def get_next_month_url(self):
612
        next_month = self.get_next_month(self.date.date())
613
        return reverse(
614
            'chrono-manager-resource-month-view',
615
            kwargs={'pk': self.resource.pk, 'year': next_month.year, 'month': next_month.month},
616
        )
617

  
618
    def get_next(self, date):
619
        return self.get_next_month(date)
620

  
621

  
555 622
resource_monthly_view = ResourceMonthView.as_view()
556 623

  
557 624

  
......
1082 1149
        if self.agenda.default_view == 'day':
1083 1150
            return redirect('chrono-manager-agenda-day-redirect-view', pk=self.agenda.pk)
1084 1151

  
1152
        if self.agenda.default_view == 'week':
1153
            return redirect('chrono-manager-agenda-week-redirect-view', pk=self.agenda.pk)
1154

  
1085 1155
        if self.agenda.default_view == 'month':
1086 1156
            return redirect('chrono-manager-agenda-month-redirect-view', pk=self.agenda.pk)
1087 1157

  
......
1114 1184
agenda_month_redirect_view = AgendaMonthRedirectView.as_view()
1115 1185

  
1116 1186

  
1187
class AgendaWeekRedirectView(AgendaMonthRedirectView):
1188
    def get(self, request, *args, **kwargs):
1189
        day = self.get_day()
1190
        return redirect(
1191
            'chrono-manager-agenda-week-view', pk=self.agenda.pk, year=day.year, week=day.strftime('%W')
1192
        )
1193

  
1194

  
1195
agenda_week_redirect_view = AgendaWeekRedirectView.as_view()
1196

  
1197

  
1117 1198
class AgendaDayRedirectView(AgendaMonthRedirectView):
1118 1199
    def get(self, request, *args, **kwargs):
1119 1200
        day = self.get_day()
......
1128 1209
class AgendaDateView(DateMixin, ViewableAgendaMixin):
1129 1210
    model = Event
1130 1211
    month_format = '%m'
1212
    week_format = '%W'
1131 1213
    date_field = 'start_datetime'
1132 1214
    allow_empty = True
1133 1215
    allow_future = True
......
1333 1415
agenda_day_view = AgendaDayView.as_view()
1334 1416

  
1335 1417

  
1336
class AgendaMonthView(AgendaDateView, MonthArchiveView):
1418
class AgendaWeekMonthMixin:
1337 1419
    def get_queryset(self):
1338 1420
        qs = super().get_queryset()
1339 1421
        if self.agenda.kind != 'events':
......
1343 1425
    def get_dated_items(self):
1344 1426
        date_list, object_list, extra_context = super().get_dated_items()
1345 1427
        if self.agenda.kind == 'events':
1346
            min_start = make_aware(datetime.datetime.combine(extra_context['month'], datetime.time(0, 0)))
1428
            min_start = make_aware(datetime.datetime.combine(extra_context[self.kind], datetime.time(0, 0)))
1347 1429
            max_start = make_aware(
1348
                datetime.datetime.combine(extra_context['next_month'], datetime.time(0, 0))
1430
                datetime.datetime.combine(extra_context['next_%s' % self.kind], datetime.time(0, 0))
1349 1431
            )
1350 1432
            exceptions = TimePeriodException.objects.filter(
1351 1433
                desk__agenda=self.agenda, start_datetime__gte=min_start, end_datetime__lt=max_start
......
1353 1435
            object_list = sorted(itertools.chain(object_list, exceptions), key=lambda x: x.start_datetime)
1354 1436
        return date_list, object_list, extra_context
1355 1437

  
1356
    def get_template_names(self):
1357
        if self.agenda.kind == 'virtual':
1358
            return ['chrono/manager_meetings_agenda_month_view.html']
1359
        return ['chrono/manager_%s_agenda_month_view.html' % self.agenda.kind]
1360

  
1361 1438
    def get_context_data(self, **kwargs):
1362 1439
        context = super().get_context_data(**kwargs)
1363 1440
        if self.agenda.kind == 'events':
......
1367 1444
            ).all()
1368 1445
        else:
1369 1446
            context['single_desk'] = bool(len(self.agenda.prefetched_desks) == 1)
1447
        context['kind'] = self.kind
1370 1448
        return context
1371 1449

  
1372
    def get_previous_month_url(self):
1373
        previous_month = self.get_previous_month(self.date.date())
1374
        return reverse(
1375
            'chrono-manager-agenda-month-view',
1376
            kwargs={'pk': self.agenda.id, 'year': previous_month.year, 'month': previous_month.month},
1377
        )
1378

  
1379
    def get_next_month_url(self):
1380
        next_month = self.get_next_month(self.date.date())
1381
        return reverse(
1382
            'chrono-manager-agenda-month-view',
1383
            kwargs={'pk': self.agenda.id, 'year': next_month.year, 'month': next_month.month},
1384
        )
1385

  
1386
    def get_day(self):
1387
        return '1'
1388

  
1389 1450
    def get_timetable_infos(self):
1390 1451
        timeperiods = itertools.chain(*(d.timeperiod_set.all() for d in self.agenda.prefetched_desks))
1391 1452
        timeperiods = sorted(timeperiods, key=lambda t: (t.weekday, t.start_time))
......
1419 1480
        hide_weekend = hide_weekend_timeperiod and hide_weekend_event
1420 1481

  
1421 1482
        # avoid displaying empty first week
1422
        first_week_offset = int(
1423
            (hide_sunday and self.date.weekday() == 6) or (hide_weekend and self.date.weekday() == 5)
1424
        )
1425

  
1483
        first_week_offset = 0
1426 1484
        first_week_number = self.date.isocalendar()[1]
1427
        if first_week_number >= 52:
1428
            first_week_number = 0
1429
        last_month_day = self.get_next_month(self.date.date()) - datetime.timedelta(days=1)
1430
        last_week_number = last_month_day.isocalendar()[1]
1485
        last_week_number = first_week_number
1486
        if self.kind == 'month':
1487
            first_week_offset = int(
1488
                (hide_sunday and self.date.weekday() == 6) or (hide_weekend and self.date.weekday() == 5)
1489
            )
1490
            first_week_number = self.date.isocalendar()[1]
1491
            if first_week_number >= 52:
1492
                first_week_number = 0
1493
            last_day = self.get_next(self.date.date()) - datetime.timedelta(days=1)
1494
            last_week_number = last_day.isocalendar()[1]
1431 1495

  
1432
        if last_week_number < first_week_number:  # new year
1433
            last_week_number = 53
1496
            if last_week_number < first_week_number:  # new year
1497
                last_week_number = 53
1434 1498

  
1435 1499
        for week_number in range(first_week_number + first_week_offset, last_week_number + 1):
1436 1500
            yield self.get_week_timetable_infos(
......
1474 1538
        desks_len = len(desks)
1475 1539
        max_date = day.replace(hour=self.max_display.hour, minute=0)
1476 1540

  
1477
        # compute booking and opening hours only for current month
1478
        if self.date.month != day.month:
1541
        # compute booking and opening hours only for current month/week
1542
        if self.kind == 'month' and timetable['other_month']:
1479 1543
            return timetable
1480 1544

  
1481 1545
        while period <= max_date:
......
1536 1600
        return timetable
1537 1601

  
1538 1602

  
1603
class AgendaWeekView(AgendaWeekMonthMixin, AgendaDateView, WeekArchiveView):
1604
    week_format = "%W"
1605
    kind = 'week'
1606

  
1607
    def get_template_names(self):
1608
        if self.agenda.kind == 'virtual':
1609
            return ['chrono/manager_meetings_agenda_week_view.html']
1610
        return ['chrono/manager_%s_agenda_week_view.html' % self.agenda.kind]
1611

  
1612
    def get_previous_week_url(self):
1613
        previous_week = self.get_previous_week(self.date.date())
1614
        return reverse(
1615
            'chrono-manager-agenda-week-view',
1616
            kwargs={'pk': self.agenda.id, 'year': previous_week.year, 'week': previous_week.strftime('%W')},
1617
        )
1618

  
1619
    def get_next_week_url(self):
1620
        next_week = self.get_next_week(self.date.date())
1621
        return reverse(
1622
            'chrono-manager-agenda-week-view',
1623
            kwargs={'pk': self.agenda.id, 'year': next_week.year, 'week': next_week.strftime('%W')},
1624
        )
1625

  
1626
    def get_next(self, date):
1627
        return self.get_next_week(date)
1628

  
1629
    def get_month(self):
1630
        date = datetime.datetime.strptime('%s-W%s-1' % (self.get_year(), self.get_week()), "%Y-W%W-%w")
1631
        return date.month
1632

  
1633
    def get_day(self):
1634
        date = datetime.datetime.strptime('%s-W%s-1' % (self.get_year(), self.get_week()), "%Y-W%W-%w")
1635
        return date.day
1636

  
1637

  
1638
agenda_weekly_view = AgendaWeekView.as_view()
1639

  
1640

  
1641
class AgendaMonthView(AgendaWeekMonthMixin, AgendaDateView, MonthArchiveView):
1642
    kind = 'month'
1643

  
1644
    def get_template_names(self):
1645
        if self.agenda.kind == 'virtual':
1646
            return ['chrono/manager_meetings_agenda_month_view.html']
1647
        return ['chrono/manager_%s_agenda_month_view.html' % self.agenda.kind]
1648

  
1649
    def get_previous_month_url(self):
1650
        previous_month = self.get_previous_month(self.date.date())
1651
        return reverse(
1652
            'chrono-manager-agenda-month-view',
1653
            kwargs={'pk': self.agenda.id, 'year': previous_month.year, 'month': previous_month.month},
1654
        )
1655

  
1656
    def get_next_month_url(self):
1657
        next_month = self.get_next_month(self.date.date())
1658
        return reverse(
1659
            'chrono-manager-agenda-month-view',
1660
            kwargs={'pk': self.agenda.id, 'year': next_month.year, 'month': next_month.month},
1661
        )
1662

  
1663
    def get_next(self, date):
1664
        return self.get_next_month(date)
1665

  
1666
    def get_day(self):
1667
        return '1'
1668

  
1669

  
1539 1670
agenda_monthly_view = AgendaMonthView.as_view()
1540 1671

  
1541 1672

  
tests/manager/test_all.py
127 127
        resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
128 128
    assert resp.location.endswith('/manage/agendas/%s/day/' % agenda.pk)
129 129

  
130
    agenda.default_view = 'week'
131
    agenda.save()
132
    for agenda_id in [agenda.pk, agenda.slug]:
133
        resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
134
    assert resp.location.endswith('/manage/agendas/%s/week/' % agenda.pk)
135

  
130 136

  
131 137
@freezegun.freeze_time('2020-07-12')
132 138
def test_events_agenda_month_redirect(app, admin_user):
......
180 186
    assert resp.location.endswith('/manage/agendas/%s/2020/7/' % agenda.pk)
181 187

  
182 188

  
189
@freezegun.freeze_time('2020-07-12')
190
def test_events_agenda_week_redirect(app, admin_user):
191
    agenda = Agenda.objects.create(label='Foo Bar', kind='events')
192

  
193
    app = login(app)
194
    # no event, redirect to current week
195
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
196
    assert resp.location.endswith('/manage/agendas/%s/2020/week/27/' % agenda.pk)
197

  
198
    # only past events, redirect to last event month
199
    Event.objects.create(
200
        agenda=agenda,
201
        places=1,
202
        start_datetime=now() - datetime.timedelta(days=60),
203
    )
204
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
205
    assert resp.location.endswith('/manage/agendas/%s/2020/week/19/' % agenda.pk)
206
    Event.objects.create(
207
        agenda=agenda,
208
        places=1,
209
        start_datetime=now() - datetime.timedelta(days=30),
210
    )
211
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
212
    assert resp.location.endswith('/manage/agendas/%s/2020/week/23/' % agenda.pk)
213

  
214
    # future events
215
    Event.objects.create(
216
        agenda=agenda,
217
        places=1,
218
        start_datetime=now() + datetime.timedelta(days=60),
219
    )
220
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
221
    assert resp.location.endswith('/manage/agendas/%s/2020/week/36/' % agenda.pk)
222
    Event.objects.create(
223
        agenda=agenda,
224
        places=1,
225
        start_datetime=now() + datetime.timedelta(days=30),
226
    )
227
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
228
    assert resp.location.endswith('/manage/agendas/%s/2020/week/32/' % agenda.pk)
229

  
230
    # don't check events for meetings
231
    agenda.kind = 'virtual'
232
    agenda.save()
233
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
234
    assert resp.location.endswith('/manage/agendas/%s/2020/week/27/' % agenda.pk)
235
    agenda.kind = 'meetings'
236
    agenda.save()
237
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk, status=302)
238
    assert resp.location.endswith('/manage/agendas/%s/2020/week/27/' % agenda.pk)
239

  
240

  
183 241
@freezegun.freeze_time('2020-07-12')
184 242
def test_events_agenda_day_redirect(app, admin_user):
185 243
    agenda = Agenda.objects.create(label='Foo Bar', kind='events')
......
246 304
        resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
247 305
    assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
248 306

  
307
    agenda.default_view = 'week'
308
    agenda.save()
309
    for agenda_id in [agenda.pk, agenda.slug]:
310
        resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
311
    assert resp.location.endswith('/manage/agendas/%s/week/' % agenda.pk)
312

  
249 313

  
250 314
def test_virtual_agenda_redirect(app, admin_user):
251 315
    agenda = Agenda.objects.create(label='Foo Bar', kind='virtual')
......
261 325
        resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
262 326
    assert resp.location.endswith('/manage/agendas/%s/month/' % agenda.pk)
263 327

  
328
    agenda.default_view = 'week'
329
    agenda.save()
330
    for agenda_id in [agenda.pk, agenda.slug]:
331
        resp = app.get('/manage/agendas/%s/' % agenda_id, status=302)
332
    assert resp.location.endswith('/manage/agendas/%s/week/' % agenda.pk)
333

  
264 334

  
265 335
def test_view_agendas_as_admin(app, admin_user):
266 336
    Agenda.objects.create(label='Bar Foo')
......
929 999
    'view',
930 1000
    (
931 1001
        '/manage/agendas/%(agenda)s/%(year)d/%(month)d/%(day)d/',
1002
        '/manage/agendas/%(agenda)s/%(year)d/week/%(week)d/',
932 1003
        '/manage/agendas/%(agenda)s/%(year)d/%(month)d/',
933 1004
    ),
934 1005
)
935
def test_agenda_day_month_view_backoffice_url_translation(
1006
def test_agenda_day_week_month_view_backoffice_url_translation(
936 1007
    app, admin_user, manager_user, api_user, settings, view
937 1008
):
938 1009
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
......
959 1030
    booking = Booking.objects.get(pk=booking_id)
960 1031
    assert booking.backoffice_url == backoffice_url
961 1032
    date = booking.event.start_datetime
962
    url = view % {'agenda': agenda.id, 'year': date.year, 'month': date.month, 'day': date.day}
1033
    url = view % {
1034
        'agenda': agenda.id,
1035
        'year': date.year,
1036
        'month': date.month,
1037
        'week': int(date.strftime('%W')),
1038
        'day': date.day,
1039
    }
963 1040
    resp = app.get(url)
964 1041
    assert resp.text.count('div class="booking') == 1
965 1042
    assert backoffice_url in resp.text
......
1139 1216
    assert len(resp.pyquery.find('.event-info')) == 1
1140 1217

  
1141 1218

  
1219
@freezegun.freeze_time('2020-10-01')
1220
def test_agenda_events_week_view(app, admin_user):
1221
    agenda = Agenda.objects.create(label='Events', kind='events')
1222
    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
1223
    today = datetime.date.today()
1224

  
1225
    login(app)
1226
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.pk, today.year, today.strftime('%W')))
1227
    assert 'Day view' in resp.text
1228
    assert "This week doesn't have any event configured." in resp.text
1229

  
1230
    # add event in a future month, a wednesday
1231
    Event.objects.create(
1232
        label='xyz', start_datetime=localtime().replace(day=11, month=11, year=2020), places=10, agenda=agenda
1233
    )
1234
    # current month still doesn't have events
1235
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
1236
    assert "This week doesn't have any event configured." in resp.text
1237

  
1238
    # add recurring event on every Wednesday
1239
    start_datetime = localtime().replace(day=4, month=11, year=2020)
1240
    event = Event.objects.create(
1241
        label='abc',
1242
        start_datetime=start_datetime,
1243
        places=10,
1244
        agenda=agenda,
1245
        recurrence_days=[start_datetime.weekday()],
1246
        recurrence_end_date=start_datetime + datetime.timedelta(days=60),
1247
    )
1248
    event.create_all_recurrences()
1249

  
1250
    with CaptureQueriesContext(connection) as ctx:
1251
        resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, 2020, 45))
1252
        assert len(ctx.captured_queries) == 7
1253
    assert len(resp.pyquery.find('.event-info')) == 2
1254
    assert 'abc' in resp.pyquery.find('.event-info')[0].text
1255
    assert 'xyz' in resp.pyquery.find('.event-info')[1].text
1256

  
1257
    TimePeriodException.objects.create(
1258
        desk=agenda.desk_set.get(),
1259
        start_datetime=start_datetime + datetime.timedelta(days=6),
1260
        end_datetime=start_datetime + datetime.timedelta(days=8),
1261
    )
1262
    agenda.update_event_recurrences()
1263
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, 2020, 45))
1264
    assert len(resp.pyquery.find('.event-info')) == 1
1265
    assert 'Exception: 11/10/2020' in resp.pyquery.find('li')[4].text_content()
1266
    assert 'xyz' in resp.pyquery.find('li')[5].text_content()
1267

  
1268
    # create another event with recurrence, the same day/time
1269
    start_datetime = localtime().replace(day=4, month=11, year=2020)
1270
    event = Event.objects.create(
1271
        label='def',
1272
        start_datetime=start_datetime,
1273
        places=10,
1274
        agenda=agenda,
1275
        recurrence_days=[start_datetime.weekday()],
1276
        recurrence_end_date=start_datetime + datetime.timedelta(days=60),
1277
    )
1278
    event.create_all_recurrences()
1279
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, 2020, 49))
1280
    assert len(resp.pyquery.find('.event-info')) == 2
1281

  
1282
    time = localtime(event.start_datetime).strftime('%H%M')
1283
    resp = resp.click('Dec. 9, 2020, 1 a.m.', index=1)
1284
    resp = resp.click('Options')
1285
    resp.form['start_datetime_1'] = '13:12'
1286
    resp = resp.form.submit(status=302).follow()
1287
    agenda.update_event_recurrences()
1288

  
1289
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, 2020, 49))
1290
    assert len(resp.pyquery.find('.event-info')) == 2
1291
    assert '1:12 p.m.' in resp.text
1292

  
1293
    Event.objects.get(slug='abc--2020-12-02-%s' % time).cancel()
1294
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, 2020, 48))
1295
    assert 'Cancelled' in resp.text
1296

  
1297

  
1298
def test_agenda_events_week_view_midnight(app, admin_user):
1299
    agenda = Agenda.objects.create(label='Events', kind='events', default_view='day')
1300
    midnight = localtime(now().replace(day=1, month=11, year=2020)).replace(hour=0, minute=0)
1301
    Event.objects.create(label='xyz', start_datetime=midnight, places=10, agenda=agenda)
1302

  
1303
    login(app)
1304
    resp = app.get('/manage/agendas/%s/week/' % agenda.pk)
1305
    assert resp.location.endswith('/2020/week/43/')
1306
    resp = resp.follow()
1307
    assert len(resp.pyquery.find('.event-info')) == 1
1308

  
1309

  
1142 1310
@freezegun.freeze_time('2020-10-01')
1143 1311
def test_agenda_events_month_view(app, admin_user):
1144 1312
    agenda = Agenda.objects.create(label='Events', kind='events')
......
1365 1533
    resp = resp.click('Month view')
1366 1534
    assert resp.request.url.endswith('%s/%s/' % (today.year, today.month))
1367 1535

  
1368
    assert 'Day view' in resp.text  # date view link should be present
1536
    assert 'Day view' in resp.text  # day view link should be present
1537
    assert 'Week view' in resp.text  # week view link should be present
1369 1538
    assert 'No opening hours this month.' in resp.text
1370 1539

  
1371 1540
    today = datetime.date(2018, 11, 10)  # fixed day
......
1744 1913
    assert 'Saturday' in resp.text
1745 1914

  
1746 1915

  
1747
def test_agenda_view_event(app, manager_user):
1748
    agenda = Agenda(label='Foo bar')
1749
    agenda.view_role = manager_user.groups.all()[0]
1750
    agenda.save()
1751
    event = Event.objects.create(
1752
        label='xyz',
1753
        start_datetime=make_aware(datetime.datetime(2019, 12, 22, 17, 0)),
1754
        places=10,
1755
        agenda=agenda,
1756
    )
1757
    for i in range(8):
1758
        booking = Booking.objects.create(event=event)
1759
        if i < 5:
1760
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 14, 0 + i))
1761
        if i == 5:
1762
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 15, 0))
1763
            booking.user_first_name = 'Foo Bar'
1764
            booking.user_last_name = 'User'
1765
        if i == 6:
1766
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 16, 0))
1767
            booking.user_first_name = 'Foo Bar'
1768
            booking.user_last_name = 'User 2'
1769
            booking.label = 'Foo Bar Label 2'
1770
        if i == 7:
1771
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 17, 0))
1772
            booking.label = 'Foo Bar Label 3'
1773
        booking.save()
1774
    Booking.objects.create(event=event, cancellation_datetime=now())
1775
    app = login(app, username='manager', password='manager')
1776
    resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
1777
    resp = resp.click('xyz')
1778
    assert 'Bookings (8/10): 2 remaining places' in resp.text
1779
    assert 'Waiting' not in resp.text
1780
    assert 'This event is overbooked.' not in resp.text
1781
    assert 'This event is full.' not in resp.text
1782
    event.waiting_list_places = 5
1783
    event.save()
1784
    resp = app.get(resp.request.url)
1785
    assert 'Waiting List (0/5): 5 remaining places' in resp.text
1786
    assert 'Anonymous, Dec. 21, 2019, 2 p.m.' in resp.text
1787
    assert 'Anonymous, Dec. 21, 2019, 2:01 p.m.' in resp.text
1788
    assert 'Anonymous, Dec. 21, 2019, 2:02 p.m.' in resp.text
1789
    assert 'Anonymous, Dec. 21, 2019, 2:03 p.m.' in resp.text
1790
    assert 'Anonymous, Dec. 21, 2019, 2:04 p.m.' in resp.text
1791
    assert 'Foo Bar User, Dec. 21, 2019, 3 p.m.' in resp.text
1792
    assert 'Foo Bar User 2, Dec. 21, 2019, 4 p.m.' in resp.text
1793
    assert 'Foo Bar Label 3, Dec. 21, 2019, 5 p.m.' in resp.text
1916
def test_agenda_week_view(app, admin_user, manager_user, api_user, get_proper_html_str):
1917
    agenda = Agenda.objects.create(label='Passeports', kind='meetings')
1918
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1919
    today = datetime.date.today()
1794 1920

  
1795
    booking = Booking.objects.order_by('pk')[0]
1796
    booking.in_waiting_list = True
1797
    booking.save()
1798
    booking = Booking.objects.order_by('pk')[1]
1799
    booking.in_waiting_list = True
1800
    booking.save()
1801
    resp = app.get(resp.request.url)
1802
    assert 'Waiting List (2/5): 3 remaining places' in resp.text
1803
    assert 'Bookings (6/10): 4 remaining places' in resp.text
1804
    assert list(resp.context['booked']) == list(Booking.objects.order_by('creation_datetime')[2:8])
1805
    assert list(resp.context['waiting']) == list(Booking.objects.order_by('creation_datetime')[0:2])
1921
    meetingtype = MeetingType(agenda=agenda, label='passeport', duration=20)
1922
    meetingtype.save()
1806 1923

  
1807
    event.places = 5
1808
    event.save()
1809
    resp = app.get(resp.request.url)
1810
    assert 'This event is overbooked.' in resp.text
1811
    assert 'This event is full.' not in resp.text
1924
    login(app)
1925
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
1926
    assert 'Week view' in resp.text
1927
    resp = resp.click('Week view')
1928
    assert resp.request.url.endswith('%s/week/%s/' % (today.year, today.strftime('%W')))
1812 1929

  
1813
    event.places = 6
1814
    event.save()
1815
    resp = app.get(resp.request.url)
1816
    assert 'This event is overbooked.' not in resp.text
1817
    assert 'This event is full.' in resp.text
1930
    assert 'Day view' in resp.text  # day view link should be present
1931
    assert 'Month view' in resp.text  # month view link should be present
1932
    assert 'No opening hours this week.' in resp.text
1818 1933

  
1934
    today = datetime.date(2018, 11, 10)  # fixed day
1935
    timeperiod_weekday = today.weekday()
1936
    timeperiod = TimePeriod(
1937
        desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
1938
    )
1939
    timeperiod.save()
1940
    app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, 72), status=404)
1941
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
1942
    assert 'No opening hours this week.' not in resp.text
1943
    assert '<div class="booking' not in resp.text
1944
    assert resp.text.count('<tr') == 9
1819 1945

  
1820
def test_agenda_view_edit_event(app, manager_user):
1821
    test_agenda_view_event(app, manager_user)
1822
    agenda = Agenda.objects.first()
1823
    resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
1824
    resp = resp.click('xyz')
1825
    assert 'Options' not in resp.text
1826
    assert 'Delete' not in resp.text
1946
    # check opening hours cells
1947
    assert '<div class="opening-hours" style="height:800.0%;top:0.0%;width:97.0%;left:1.0%' in resp.text
1827 1948

  
1828
    agenda.edit_role = manager_user.groups.all()[0]
1829
    agenda.save()
1830
    event_url = resp.request.url
1831
    resp = app.get(event_url)
1832
    assert 'Options' in resp.text
1833
    resp = resp.click('Options')
1834
    resp.form['start_datetime_0'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d')
1835
    resp.form['start_datetime_1'] = agenda.event_set.first().start_datetime.strftime('%H:%M')
1836
    resp = resp.form.submit(status=302).follow()
1837
    assert event_url == resp.request.url
1949
    # book some slots
1950
    app.reset()
1951
    app.authorization = ('Basic', ('john.doe', 'password'))
1952
    resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
1953
    booking_url = resp.json['data'][0]['api']['fillslot_url']
1954
    booking_url2 = resp.json['data'][2]['api']['fillslot_url']
1955
    booking = app.post(booking_url)
1956
    booking_2 = app.post_json(
1957
        booking_url2, params={'label': 'foo book', 'user_last_name': "bar's", 'url': 'http://baz/'}
1958
    )
1838 1959

  
1839
    resp = resp.click('Delete')
1840
    resp = resp.form.submit()
1841
    assert Event.objects.count() == 0
1960
    app.reset()
1961
    login(app)
1962
    date = Booking.objects.all()[0].event.start_datetime
1963
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, date.year, date.strftime('%W')))
1964
    assert resp.text.count('<div class="booking" style="left:1.0%;height:33.0%;') == 2  # booking cells
1965
    assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
1966
    assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo book - bar's"
1967
    assert get_proper_html_str('foo book - bar&#39;s') in resp
1968
    assert len(resp.pyquery.find('span.desk')) == 0
1842 1969

  
1970
    agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
1971
    agenda.save()
1972
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, date.year, date.strftime('%W')))
1973
    assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
1974
    assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
1975
    assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; Foo Bar') in resp
1843 1976

  
1844
def test_virtual_agenda_add(app, admin_user):
1845
    app = login(app)
1846
    resp = app.get('/manage/', status=200)
1977
    desk = Desk.objects.create(agenda=agenda, label='Desk B')
1978
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, date.year, date.strftime('%W')))
1979
    assert len(resp.pyquery.find('span.desk')) == 2
1980

  
1981
    timeperiod = TimePeriod(
1982
        desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
1983
    )
1984
    timeperiod.save()
1985

  
1986
    app.reset()
1987
    booking_3 = app.post(booking_url)
1988
    login(app)
1989
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
1990

  
1991
    # count occurences of timeperiod weekday in current month
1992
    assert resp.text.count('<div class="opening-hours"') == 2
1993
    current_month = today.strftime('%Y-%m')
1994
    if current_month in booking_url or current_month in booking_url2:
1995
        assert resp.text.count('<div class="booking"') == 3
1996

  
1997
    # cancel bookings
1998
    app.reset()
1999
    app.post(booking.json['api']['cancel_url'])
2000
    app.post(booking_2.json['api']['cancel_url'])
2001
    app.post(booking_3.json['api']['cancel_url'])
2002

  
2003
    # make sure the are not
2004
    login(app)
2005
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
2006
    assert resp.text.count('<div class="booking"') == 0
2007

  
2008
    # check December is correctly displayed
2009
    today = datetime.date(2018, 12, 10)
2010
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
2011
    assert 'No opening hours this week.' not in resp.text
2012

  
2013
    # display exception
2014
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
2015
    TimePeriodException.objects.create(
2016
        label='Calendar exception',
2017
        unavailability_calendar=unavailability_calendar,
2018
        start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)),
2019
        end_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)),
2020
    )
2021
    unavailability_calendar.desks.add(desk)
2022
    TimePeriodException.objects.create(
2023
        label='Exception for a December day',
2024
        desk=desk,
2025
        start_datetime=make_aware(datetime.datetime(2018, 12, 15, 14, 0)),
2026
        end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)),
2027
    )
2028
    TimePeriodException.objects.create(
2029
        label='Exception spanning multiple days',
2030
        desk=desk,
2031
        start_datetime=make_aware(datetime.datetime(2018, 12, 11, 14, 0)),
2032
        end_datetime=make_aware(datetime.datetime(2018, 12, 13, 16, 0)),
2033
    )
2034
    with CaptureQueriesContext(connection) as ctx:
2035
        resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
2036
        assert len(ctx.captured_queries) == 10
2037
    assert resp.pyquery.find('.exception-hours')[0].attrib == {
2038
        'class': 'exception-hours',
2039
        'style': 'height:400.0%;top:400.0%;width:48.0%;left:50.0%;',
2040
        'title': 'Exception spanning multiple days',
2041
    }
2042
    assert resp.pyquery.find('.exception-hours')[1].attrib == {
2043
        'class': 'exception-hours',
2044
        'style': 'height:800.0%;top:0.0%;width:48.0%;left:50.0%;',
2045
        'title': 'Exception spanning multiple days',
2046
    }
2047
    assert resp.pyquery.find('.exception-hours')[2].attrib == {
2048
        'class': 'exception-hours',
2049
        'style': 'height:600.0%;top:0.0%;width:48.0%;left:50.0%;',
2050
        'title': 'Exception spanning multiple days',
2051
    }
2052
    assert resp.pyquery.find('.exception-hours')[3].attrib == {
2053
        'class': 'exception-hours',
2054
        'style': 'height:400.0%;top:0.0%;width:48.0%;left:50.0%;',
2055
        'title': 'Calendar exception',
2056
    }
2057
    assert resp.pyquery.find('.exception-hours')[4].attrib == {
2058
        'class': 'exception-hours',
2059
        'style': 'height:400.0%;top:400.0%;width:48.0%;left:50.0%;',
2060
        'title': 'Exception for a December day',
2061
    }
2062

  
2063

  
2064
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
2065
def test_agenda_week_view_weekend(app, admin_user, kind):
2066
    week, year = '02', 2019
2067
    monday = 0
2068
    if kind == 'meetings':
2069
        agenda = Agenda.objects.create(label='Passeports', kind='meetings')
2070
        desk = Desk.objects.create(agenda=agenda, label='Desk A')
2071
    else:
2072
        agenda = Agenda.objects.create(label='Virtual', kind='virtual')
2073
        real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
2074
        VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
2075
        desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
2076
    TimePeriod.objects.create(
2077
        desk=desk, weekday=monday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
2078
    )
2079

  
2080
    login(app)
2081
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, year, week))
2082
    assert 'Sunday' not in resp.text
2083
    assert 'Saturday' not in resp.text
2084
    # Month starts a Tuesday, but monday is displayed
2085
    assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
2086

  
2087
    week, year = 21, 2019  # month starts a Saturday
2088
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, year, week))
2089
    assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
2090

  
2091
    saturday = 5
2092
    timeperiod_sat = TimePeriod.objects.create(
2093
        desk=desk, weekday=saturday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
2094
    )
2095
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, year, week))
2096
    assert 'Sunday' not in resp.text
2097
    assert 'Saturday' in resp.text
2098
    assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
2099

  
2100
    sunday = 6
2101
    TimePeriod.objects.create(
2102
        desk=desk, weekday=sunday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
2103
    )
2104
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, year, week))
2105
    assert 'Sunday' in resp.text
2106
    assert 'Saturday' in resp.text
2107

  
2108
    timeperiod_sat.delete()
2109
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, year, week))
2110
    assert 'Sunday' in resp.text
2111
    assert 'Saturday' in resp.text
2112

  
2113

  
2114
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
2115
def test_agenda_week_view_dst_change(app, admin_user, kind):
2116
    if kind == 'meetings':
2117
        agenda = Agenda.objects.create(label='Passeports', kind='meetings')
2118
        desk = Desk.objects.create(agenda=agenda, label='Desk A')
2119
        meetingtype = MeetingType.objects.create(agenda=agenda, label='passeport', duration=20)
2120
    else:
2121
        agenda = Agenda.objects.create(label='Virtual', kind='virtual')
2122
        real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
2123
        VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
2124
        desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
2125
        meetingtype = MeetingType.objects.create(agenda=real_agenda, label='passeport', duration=20)
2126

  
2127
    for weekday in range(0, 7):  # open all mornings
2128
        TimePeriod.objects.create(
2129
            desk=desk, weekday=weekday, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
2130
        )
2131

  
2132
    login(app)
2133
    for date in ('2019-10-28', '2019-11-03'):
2134
        # dst change was on 2019-11-03
2135
        with freezegun.freeze_time(date):
2136
            resp = app.get('/manage/agendas/%s/2019/week/43/' % agenda.id)
2137
            # check all days are correctly aligned
2138
            assert resp.text.count('height:300.0%;top:0.0%') == 7
2139

  
2140
    # book some slots
2141
    for date_tuple in [(2019, 10, 29, 10, 0), (2019, 10, 30, 10, 0)]:
2142
        event = Event.objects.create(
2143
            agenda=desk.agenda,
2144
            places=1,
2145
            desk=desk,
2146
            meeting_type=meetingtype,
2147
            start_datetime=localtime(make_aware(datetime.datetime(*date_tuple))),
2148
        )
2149
        Booking.objects.create(event=event)
2150

  
2151
    # check booked slots are similarly aligned
2152
    resp = app.get('/manage/agendas/%s/2019/week/43/' % agenda.id)
2153
    assert resp.text.count('height:33.0%;top:100.0%;') == 2
2154

  
2155

  
2156
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
2157
def test_agenda_week_view_januaries(app, admin_user, kind):
2158
    if kind == 'meetings':
2159
        agenda = Agenda.objects.create(label='Passports', kind='meetings')
2160
        desk = Desk.objects.create(agenda=agenda, label='Desk A')
2161
        MeetingType.objects.create(agenda=agenda, label='passport', duration=20)
2162
    else:
2163
        agenda = Agenda.objects.create(label='Virtual', kind='virtual')
2164
        real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
2165
        VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
2166
        desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
2167
        MeetingType.objects.create(agenda=real_agenda, label='passport', duration=20)
2168
    TimePeriod(desk=desk, weekday=2, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)).save()
2169

  
2170
    for year in range(2020, 2030):
2171
        date = datetime.date(year, 1, 1)
2172
        with freezegun.freeze_time(date):
2173
            login(app)
2174
            resp = app.get('/manage/agendas/%s/%s/week/01/' % (agenda.id, date.year))
2175
            assert resp.text.count('<th></th>') == 1
2176

  
2177

  
2178
@pytest.mark.parametrize('kind', ['meetings', 'virtual'])
2179
def test_agenda_week_view_event_outside_timeperiod(app, admin_user, kind):
2180
    middle_day = now().replace(day=15)
2181
    middle_day = middle_day + datetime.timedelta(days=4 - middle_day.weekday())
2182
    if kind == 'meetings':
2183
        agenda = Agenda.objects.create(label='New Example', kind='meetings')
2184
        desk = Desk.objects.create(agenda=agenda, label='New Desk')
2185
        meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
2186
    else:
2187
        agenda = Agenda.objects.create(label='Virtual', kind='virtual')
2188
        real_agenda = Agenda.objects.create(label='Real 1', kind='meetings')
2189
        VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda)
2190
        desk = Desk.objects.create(agenda=real_agenda, label='New Desk')
2191
        meetingtype = MeetingType.objects.create(agenda=real_agenda, label='passport', duration=20)
2192
    login(app)
2193

  
2194
    # no time period - no events
2195
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2196
    assert 'No opening hours this week.' in resp.text
2197
    assert 'div class="booking' not in resp.text
2198

  
2199
    # book some slots
2200
    middle_day = now().replace(day=15)
2201
    for hour, minute in [(9, 0), (17, 0)]:
2202
        event = Event.objects.create(
2203
            agenda=desk.agenda,
2204
            places=1,
2205
            desk=desk,
2206
            meeting_type=meetingtype,
2207
            start_datetime=localtime(now().replace(day=middle_day.day - middle_day.weekday() + 2)).replace(
2208
                hour=hour, minute=minute
2209
            ),
2210
        )
2211
        Booking.objects.create(event=event)
2212

  
2213
    # no time period - events are displayed
2214
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2215
    assert resp.text.count('div class="booking') == 2
2216

  
2217
    # bookings are cancelled
2218
    Booking.objects.update(cancellation_datetime=now())
2219
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2220
    assert 'No opening hours this week.' in resp.text
2221
    assert resp.text.count('div class="booking') == 0
2222

  
2223
    # events outside time period
2224
    Booking.objects.update(cancellation_datetime=None)  # reset
2225
    TimePeriod.objects.create(
2226
        desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0)
2227
    )
2228
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2229
    assert resp.text.count('div class="booking') == 2
2230
    assert '<div class="opening-hours" style="height:600.0%;top:100.0%;width:97.0%;left:1.0%' in resp.text
2231
    assert 'Sunday' not in resp.text
2232
    assert 'Saturday' not in resp.text
2233

  
2234
    # create an event on saturday
2235
    middle_day = now().replace(day=15)
2236
    middle_day = middle_day + datetime.timedelta(days=5 - middle_day.weekday())
2237
    event = Event.objects.create(
2238
        agenda=desk.agenda,
2239
        places=1,
2240
        desk=desk,
2241
        meeting_type=meetingtype,
2242
        start_datetime=localtime(now()).replace(day=middle_day.day, hour=10, minute=0),
2243
    )
2244
    Booking.objects.create(event=event)
2245
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2246
    assert resp.text.count('div class="booking') == 3
2247
    assert 'Sunday' not in resp.text
2248
    assert 'Saturday' in resp.text
2249
    # bookings are cancelled
2250
    Booking.objects.update(cancellation_datetime=now())
2251
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2252
    assert resp.text.count('div class="booking') == 0
2253
    # and a timeperiod
2254
    Booking.objects.update(cancellation_datetime=None)  # reset
2255
    TimePeriod.objects.create(
2256
        desk=desk, weekday=5, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
2257
    )
2258
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2259
    assert resp.text.count('div class="booking') == 3
2260
    assert 'Sunday' not in resp.text
2261
    assert 'Saturday' in resp.text
2262

  
2263
    # create an event on sunday
2264
    middle_day = now().replace(day=15)
2265
    middle_day = middle_day + datetime.timedelta(days=6 - middle_day.weekday())
2266
    event = Event.objects.create(
2267
        agenda=desk.agenda,
2268
        places=1,
2269
        desk=desk,
2270
        meeting_type=meetingtype,
2271
        start_datetime=localtime(now()).replace(day=middle_day.day, hour=10, minute=0),
2272
    )
2273
    Booking.objects.create(event=event)
2274
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2275
    assert resp.text.count('div class="booking') == 4
2276
    assert 'Sunday' in resp.text
2277
    assert 'Saturday' in resp.text
2278
    # bookings are cancelled
2279
    Booking.objects.update(cancellation_datetime=now())
2280
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2281
    assert resp.text.count('div class="booking') == 0
2282
    # and a timeperiod
2283
    Booking.objects.update(cancellation_datetime=None)  # reset
2284
    TimePeriod.objects.create(
2285
        desk=desk, weekday=6, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
2286
    )
2287
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.pk, middle_day.year, middle_day.strftime('%W')))
2288
    assert resp.text.count('div class="booking') == 4
2289
    assert 'Sunday' in resp.text
2290
    assert 'Saturday' in resp.text
2291

  
2292

  
2293
def test_agenda_view_event(app, manager_user):
2294
    agenda = Agenda(label='Foo bar')
2295
    agenda.view_role = manager_user.groups.all()[0]
2296
    agenda.save()
2297
    event = Event.objects.create(
2298
        label='xyz',
2299
        start_datetime=make_aware(datetime.datetime(2019, 12, 22, 17, 0)),
2300
        places=10,
2301
        agenda=agenda,
2302
    )
2303
    for i in range(8):
2304
        booking = Booking.objects.create(event=event)
2305
        if i < 5:
2306
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 14, 0 + i))
2307
        if i == 5:
2308
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 15, 0))
2309
            booking.user_first_name = 'Foo Bar'
2310
            booking.user_last_name = 'User'
2311
        if i == 6:
2312
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 16, 0))
2313
            booking.user_first_name = 'Foo Bar'
2314
            booking.user_last_name = 'User 2'
2315
            booking.label = 'Foo Bar Label 2'
2316
        if i == 7:
2317
            booking.creation_datetime = make_aware(datetime.datetime(2019, 12, 21, 17, 0))
2318
            booking.label = 'Foo Bar Label 3'
2319
        booking.save()
2320
    Booking.objects.create(event=event, cancellation_datetime=now())
2321
    app = login(app, username='manager', password='manager')
2322
    resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
2323
    resp = resp.click('xyz')
2324
    assert 'Bookings (8/10): 2 remaining places' in resp.text
2325
    assert 'Waiting' not in resp.text
2326
    assert 'This event is overbooked.' not in resp.text
2327
    assert 'This event is full.' not in resp.text
2328
    event.waiting_list_places = 5
2329
    event.save()
2330
    resp = app.get(resp.request.url)
2331
    assert 'Waiting List (0/5): 5 remaining places' in resp.text
2332
    assert 'Anonymous, Dec. 21, 2019, 2 p.m.' in resp.text
2333
    assert 'Anonymous, Dec. 21, 2019, 2:01 p.m.' in resp.text
2334
    assert 'Anonymous, Dec. 21, 2019, 2:02 p.m.' in resp.text
2335
    assert 'Anonymous, Dec. 21, 2019, 2:03 p.m.' in resp.text
2336
    assert 'Anonymous, Dec. 21, 2019, 2:04 p.m.' in resp.text
2337
    assert 'Foo Bar User, Dec. 21, 2019, 3 p.m.' in resp.text
2338
    assert 'Foo Bar User 2, Dec. 21, 2019, 4 p.m.' in resp.text
2339
    assert 'Foo Bar Label 3, Dec. 21, 2019, 5 p.m.' in resp.text
2340

  
2341
    booking = Booking.objects.order_by('pk')[0]
2342
    booking.in_waiting_list = True
2343
    booking.save()
2344
    booking = Booking.objects.order_by('pk')[1]
2345
    booking.in_waiting_list = True
2346
    booking.save()
2347
    resp = app.get(resp.request.url)
2348
    assert 'Waiting List (2/5): 3 remaining places' in resp.text
2349
    assert 'Bookings (6/10): 4 remaining places' in resp.text
2350
    assert list(resp.context['booked']) == list(Booking.objects.order_by('creation_datetime')[2:8])
2351
    assert list(resp.context['waiting']) == list(Booking.objects.order_by('creation_datetime')[0:2])
2352

  
2353
    event.places = 5
2354
    event.save()
2355
    resp = app.get(resp.request.url)
2356
    assert 'This event is overbooked.' in resp.text
2357
    assert 'This event is full.' not in resp.text
2358

  
2359
    event.places = 6
2360
    event.save()
2361
    resp = app.get(resp.request.url)
2362
    assert 'This event is overbooked.' not in resp.text
2363
    assert 'This event is full.' in resp.text
2364

  
2365

  
2366
def test_agenda_view_edit_event(app, manager_user):
2367
    test_agenda_view_event(app, manager_user)
2368
    agenda = Agenda.objects.first()
2369
    resp = app.get('/manage/agendas/%s/2019/12/' % agenda.id, status=200)
2370
    resp = resp.click('xyz')
2371
    assert 'Options' not in resp.text
2372
    assert 'Delete' not in resp.text
2373

  
2374
    agenda.edit_role = manager_user.groups.all()[0]
2375
    agenda.save()
2376
    event_url = resp.request.url
2377
    resp = app.get(event_url)
2378
    assert 'Options' in resp.text
2379
    resp = resp.click('Options')
2380
    resp.form['start_datetime_0'] = agenda.event_set.first().start_datetime.strftime('%Y-%m-%d')
2381
    resp.form['start_datetime_1'] = agenda.event_set.first().start_datetime.strftime('%H:%M')
2382
    resp = resp.form.submit(status=302).follow()
2383
    assert event_url == resp.request.url
2384

  
2385
    resp = resp.click('Delete')
2386
    resp = resp.form.submit()
2387
    assert Event.objects.count() == 0
2388

  
2389

  
2390
def test_virtual_agenda_add(app, admin_user):
2391
    app = login(app)
2392
    resp = app.get('/manage/', status=200)
1847 2393
    resp = resp.click('New')
1848 2394
    resp.form['label'] = 'Virtual agenda'
1849 2395
    resp.form['kind'] = 'virtual'
......
1997 2543
    assert 'exceptions-hours' not in resp.text
1998 2544

  
1999 2545

  
2546
def test_virtual_agenda_week_view(app, admin_user, get_proper_html_str):
2547
    agenda = Agenda.objects.create(label='Virtual', kind='virtual')
2548
    real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings')
2549
    real_agenda_2 = Agenda.objects.create(label='Real 2', kind='meetings')
2550
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_1)
2551
    VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=real_agenda_2)
2552

  
2553
    desk1 = Desk.objects.create(agenda=real_agenda_1, label='New Desk')
2554
    desk2 = Desk.objects.create(agenda=real_agenda_2, label='New Desk')
2555
    today = datetime.date.today()
2556

  
2557
    meetingtype1 = MeetingType.objects.create(agenda=real_agenda_1, label='Bar', duration=30)
2558
    meetingtype2 = MeetingType.objects.create(agenda=real_agenda_2, label='Bar', duration=30)
2559

  
2560
    login(app)
2561
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
2562
    assert 'Week view' in resp.text
2563
    resp = resp.click('Week view')
2564
    assert resp.request.url.endswith('%s/week/%s/' % (today.year, today.strftime('%W')))
2565

  
2566
    assert 'Day view' in resp.text  # day view link should be present
2567
    assert 'Month view' in resp.text  # month view link should be present
2568
    assert 'No opening hours this week.' in resp.text
2569

  
2570
    today = datetime.date(2018, 11, 10)  # fixed day
2571
    timeperiod_weekday = today.weekday()
2572
    TimePeriod.objects.create(
2573
        desk=desk1, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
2574
    )
2575
    resp = app.get('/manage/agendas/%s/%s/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
2576
    assert 'No opening hours this week.' not in resp.text
2577
    assert '<div class="booking' not in resp.text
2578
    assert resp.text.count('<tr') == 9
2579

  
2580
    # check opening hours cells
2581
    assert '<div class="opening-hours" style="height:800.0%;top:0.0%;width:48.0%;left:1.0%' in resp.text
2582

  
2583
    # book some slots
2584
    for hour, minute in [(10, 30), (14, 0)]:
2585
        event = Event.objects.create(
2586
            agenda=real_agenda_1,
2587
            places=1,
2588
            desk=desk1,
2589
            meeting_type=meetingtype1,
2590
            start_datetime=now().replace(hour=hour, minute=minute),
2591
        )
2592
        Booking.objects.create(event=event)
2593
        event = Event.objects.create(
2594
            agenda=real_agenda_2,
2595
            places=1,
2596
            desk=desk2,
2597
            meeting_type=meetingtype2,
2598
            start_datetime=now().replace(hour=hour, minute=minute),
2599
        )
2600
        Booking.objects.create(event=event, label='foo', user_last_name="bar's")
2601

  
2602
    date = Booking.objects.all()[0].event.start_datetime
2603
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, date.year, date.strftime('%W')))
2604
    assert resp.text.count('<div class="booking" style="left:1.0%;height:50.0%;') == 2  # booking cells
2605
    assert (
2606
        resp.text.count('<div class="booking" style="left:50.0%;height:50.0%;min-height:50.0%;') == 2
2607
    )  # booking cells
2608
    assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
2609
    assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's"
2610
    assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == 'booked'
2611
    assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "foo - bar's"
2612
    assert get_proper_html_str('foo - bar&#39;s') in resp
2613

  
2614
    real_agenda_1.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
2615
    real_agenda_1.save()
2616
    real_agenda_2.booking_user_block_template = '<b>{{ booking.user_name }}</b> Bar Foo'
2617
    real_agenda_2.save()
2618
    resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
2619
    assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
2620
    assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Bar Foo"
2621
    assert resp.pyquery.find('div.booking a').not_('.cancel')[2].text.strip() == '<b></b> Foo Bar'
2622
    assert resp.pyquery.find('div.booking a').not_('.cancel')[3].text.strip() == "<b>bar's</b> Bar Foo"
2623
    assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; Bar Foo') in resp
2624

  
2625
    # cancel a booking
2626
    booking = Booking.objects.first()
2627
    booking.cancel()
2628
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, date.year, date.strftime('%W')))
2629
    assert resp.text.count('<div class="booking"') == 3
2630

  
2631
    # check December is correctly displayed
2632
    today = datetime.date(2018, 12, 10)
2633
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
2634
    assert 'No opening hours this month.' not in resp.text
2635

  
2636
    # display exception
2637
    TimePeriodException.objects.create(
2638
        label='Exception for a December day',
2639
        desk=desk1,
2640
        start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)),
2641
        end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)),
2642
    )
2643
    with CaptureQueriesContext(connection) as ctx:
2644
        resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
2645
        assert len(ctx.captured_queries) == 12
2646
    assert len(resp.pyquery.find('.exception-hours')) == 1
2647
    assert resp.pyquery.find('.exception-hours')[0].attrib == {
2648
        'class': 'exception-hours',
2649
        'style': 'height:800.0%;top:0.0%;width:48.0%;left:1.0%;',
2650
        'title': 'Exception for a December day',
2651
    }
2652

  
2653
    # display excluded period
2654
    TimePeriod.objects.create(
2655
        agenda=agenda, weekday=today.weekday(), start_time=datetime.time(13, 0), end_time=datetime.time(23, 0)
2656
    )
2657
    resp = app.get('/manage/agendas/%s/%d/week/%s/' % (agenda.id, today.year, today.strftime('%W')))
2658
    assert len(resp.pyquery.find('.exception-hours')) == 3
2659
    assert resp.pyquery.find('.exception-hours')[0].attrib == {
2660
        'class': 'exception-hours',
2661
        'style': 'height:500.0%;top:300.0%;width:48.0%;left:1.0%;',
2662
    }
2663
    assert resp.pyquery.find('.exception-hours')[1].attrib == {
2664
        'class': 'exception-hours',
2665
        'style': 'height:500.0%;top:300.0%;width:48.0%;left:50.0%;',
2666
    }
2667

  
2668

  
2000 2669
def test_virtual_agenda_month_view(app, admin_user, get_proper_html_str):
2001 2670
    agenda = Agenda.objects.create(label='Virtual', kind='virtual')
2002 2671
    real_agenda_1 = Agenda.objects.create(label='Real 1', kind='meetings')
......
2017 2686
    resp = resp.click('Month view')
2018 2687
    assert resp.request.url.endswith('%s/%s/' % (today.year, today.month))
2019 2688

  
2020
    assert 'Day view' in resp.text  # date view link should be present
2689
    assert 'Day view' in resp.text  # day view link should be present
2690
    assert 'Week view' in resp.text  # week view link should be present
2021 2691
    assert 'No opening hours this month.' in resp.text
2022 2692

  
2023 2693
    today = datetime.date(2018, 11, 10)  # fixed day
tests/manager/test_resource.py
220 220
    app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day), status=403)
221 221

  
222 222

  
223
@freezegun.freeze_time('2020-06-15')
224
def test_resource_week_view(app, admin_user, get_proper_html_str):
225
    resource = Resource.objects.create(label='Foo bar')
226
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
227
    agenda.resources.add(resource)
228
    desk = Desk.objects.create(agenda=agenda, label='Desk')
229
    meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=20)
230
    TimePeriod.objects.create(
231
        desk=desk, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
232
    )
233

  
234
    login(app)
235
    today = datetime.date(2018, 11, 10)  # fixed day
236
    app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, 72), status=404)
237
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, today.year, today.strftime('%W')))
238
    assert '<div class="booking' not in resp.text
239
    assert resp.text.count('<tr') == 9
240

  
241
    # book some slots
242
    for hour, minute in [(10, 30), (14, 0)]:
243
        event = Event.objects.create(
244
            agenda=agenda,
245
            places=1,
246
            desk=desk,
247
            meeting_type=meetingtype,
248
            start_datetime=now().replace(hour=hour, minute=minute),
249
        )
250
        event.resources.add(resource)
251
        if hour == 10:
252
            Booking.objects.create(event=event)
253
        else:
254
            Booking.objects.create(event=event, label='foo', user_last_name="bar's")
255

  
256
    today = datetime.date.today()
257
    with CaptureQueriesContext(connection) as ctx:
258
        resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, today.year, today.strftime('%W')))
259
        assert len(ctx.captured_queries) == 8
260
    assert resp.text.count('<div class="booking" style="height:33.0%;') == 2  # booking cells
261
    assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
262
    assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's"
263
    assert get_proper_html_str('foo - bar&#39;s') in resp
264

  
265
    agenda.booking_user_block_template = '<b>{{ booking.user_name }}</b> Foo Bar'
266
    agenda.save()
267
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, today.year, today.strftime('%W')))
268
    assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == '<b></b> Foo Bar'
269
    assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "<b>bar's</b> Foo Bar"
270
    assert get_proper_html_str('&lt;b&gt;bar&#39;s&lt;/b&gt; Foo Bar') in resp
271

  
272
    # cancel booking
273
    booking = Booking.objects.first()
274
    booking.cancel()
275

  
276
    # make sure the are not
277
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, today.year, today.strftime('%W')))
278
    assert resp.text.count('<div class="booking"') == 1
279

  
280

  
281
def test_resource_week_view_weekend(app, admin_user):
282
    resource = Resource.objects.create(label='Foo bar')
283
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
284
    agenda.resources.add(resource)
285
    desk = Desk.objects.create(agenda=agenda, label='Desk')
286
    MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
287
    monday = 0
288
    TimePeriod.objects.create(
289
        desk=desk, weekday=monday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
290
    )
291
    week, year = '01', 2019
292

  
293
    login(app)
294
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, year, week))
295
    assert 'Sunday' not in resp.text
296
    assert 'Saturday' not in resp.text
297
    # Month starts a Tuesday, but monday is displayed
298
    assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
299

  
300
    week, year = 21, 2019  # month starts a Saturday
301
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, year, week))
302
    assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
303

  
304
    saturday = 5
305
    timeperiod_sat = TimePeriod.objects.create(
306
        desk=desk, weekday=saturday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
307
    )
308
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, year, week))
309
    assert 'Sunday' not in resp.text
310
    assert 'Saturday' in resp.text
311
    assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
312

  
313
    sunday = 6
314
    TimePeriod.objects.create(
315
        desk=desk, weekday=sunday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
316
    )
317
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, year, week))
318
    assert 'Sunday' in resp.text
319
    assert 'Saturday' in resp.text
320

  
321
    timeperiod_sat.delete()
322
    resp = app.get('/manage/resource/%s/%s/week/%s/' % (resource.pk, year, week))
323
    assert 'Sunday' in resp.text
324
    assert 'Saturday' in resp.text
325

  
326

  
327
def test_resource_week_view_dst_change(app, admin_user):
328
    resource = Resource.objects.create(label='Foo bar')
329
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
330
    agenda.resources.add(resource)
331
    desk = Desk.objects.create(agenda=agenda, label='Desk')
332
    meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
333
    TimePeriod.objects.create(
334
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
335
    )
336

  
337
    login(app)
338
    # book some slots
339
    with freezegun.freeze_time('2019-10-01'):
340
        for start_datetime in [
341
            make_aware(datetime.datetime(2019, 10, 2, 10, 0)),
342
            make_aware(datetime.datetime(2019, 10, 29, 10, 0)),
343
        ]:
344
            event = Event.objects.create(
345
                agenda=agenda,
346
                places=1,
347
                desk=desk,
348
                meeting_type=meetingtype,
349
                start_datetime=start_datetime,
350
            )
351
            event.resources.add(resource)
352
            Booking.objects.create(event=event)
353

  
354
    # check booked slots are similarly aligned
355
    resp = app.get('/manage/resource/%s/2019/week/43/' % resource.pk)
356
    assert resp.text.count('height:50.0%;top:100.0%') == 1
357

  
358

  
359
def test_resource_week_view_januaries(app, admin_user):
360
    resource = Resource.objects.create(label='Foo bar')
361
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
362
    agenda.resources.add(resource)
363
    desk = Desk.objects.create(agenda=agenda, label='Desk')
364
    TimePeriod(desk=desk, weekday=2, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)).save()
365

  
366
    for year in range(2020, 2030):
367
        date = datetime.date(year, 1, 1)
368
        with freezegun.freeze_time(date):
369
            login(app)
370
            resp = app.get('/manage/resource/%s/%s/week/01/' % (resource.pk, date.year))
371
            assert resp.text.count('<th></th>') == 1
372

  
373

  
374
def test_resource_week_view_event_outside_timeperiod(app, admin_user):
375
    middle_day = now().replace(day=15)
376
    middle_day = middle_day + datetime.timedelta(days=4 - middle_day.weekday())
377
    resource = Resource.objects.create(label='Foo bar')
378
    agenda = Agenda.objects.create(label='Agenda', kind='meetings')
379
    agenda.resources.add(resource)
380
    desk = Desk.objects.create(agenda=agenda, label='Desk')
381
    meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
382
    login(app)
383

  
384
    # no time period - no events
385
    resp = app.get(
386
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
387
    )
388
    assert 'No bookings this week.' in resp.text
389
    assert 'div class="booking' not in resp.text
390

  
391
    # book some slots
392
    for hour, minute in [(9, 0), (17, 0)]:
393
        event = Event.objects.create(
394
            agenda=agenda,
395
            places=1,
396
            desk=desk,
397
            meeting_type=meetingtype,
398
            start_datetime=localtime(now()).replace(
399
                day=middle_day.day - middle_day.weekday() + 2, hour=hour, minute=minute
400
            ),
401
        )
402
        event.resources.add(resource)
403
        Booking.objects.create(event=event)
404

  
405
    # no time period - events are displayed
406
    resp = app.get(
407
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
408
    )
409
    assert resp.text.count('div class="booking') == 2
410

  
411
    # events outside time period
412
    TimePeriod.objects.create(
413
        desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(16, 0)
414
    )
415
    resp = app.get(
416
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
417
    )
418
    assert resp.text.count('div class="booking') == 2
419
    assert 'Sunday' not in resp.text
420
    assert 'Saturday' not in resp.text
421

  
422
    # bookings are cancelled
423
    Booking.objects.update(cancellation_datetime=now())
424
    resp = app.get(
425
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
426
    )
427
    assert resp.text.count('div class="booking') == 0
428

  
429
    # create an event on saturday
430
    Booking.objects.update(cancellation_datetime=None)  # reset
431
    middle_day = now().replace(day=15)
432
    middle_day = middle_day + datetime.timedelta(days=5 - middle_day.weekday())
433
    event = Event.objects.create(
434
        agenda=agenda,
435
        places=1,
436
        desk=desk,
437
        meeting_type=meetingtype,
438
        start_datetime=localtime(now()).replace(day=middle_day.day, hour=10, minute=0),
439
    )
440
    event.resources.add(resource)
441
    Booking.objects.create(event=event)
442
    resp = app.get(
443
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
444
    )
445
    assert resp.text.count('div class="booking') == 3
446
    assert 'Sunday' not in resp.text
447
    assert 'Saturday' in resp.text
448
    # bookings are cancelled
449
    Booking.objects.update(cancellation_datetime=now())
450
    resp = app.get(
451
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
452
    )
453
    assert resp.text.count('div class="booking') == 0
454
    # and a timeperiod
455
    Booking.objects.update(cancellation_datetime=None)  # reset
456
    TimePeriod.objects.create(
457
        desk=desk, weekday=5, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
458
    )
459
    resp = app.get(
460
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
461
    )
462
    assert resp.text.count('div class="booking') == 3
463
    assert 'Sunday' not in resp.text
464
    assert 'Saturday' in resp.text
465

  
466
    # create an event on sunday
467
    middle_day = now().replace(day=15)
468
    middle_day = middle_day + datetime.timedelta(days=6 - middle_day.weekday())
469
    event = Event.objects.create(
470
        agenda=agenda,
471
        places=1,
472
        desk=desk,
473
        meeting_type=meetingtype,
474
        start_datetime=localtime(now()).replace(day=middle_day.day, hour=10, minute=0),
475
    )
476
    event.resources.add(resource)
477
    Booking.objects.create(event=event)
478
    resp = app.get(
479
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
480
    )
481
    assert resp.text.count('div class="booking') == 4
482
    assert 'Sunday' in resp.text
483
    assert 'Saturday' in resp.text
484
    # bookings are cancelled
485
    Booking.objects.update(cancellation_datetime=now())
486
    resp = app.get(
487
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
488
    )
489
    assert resp.text.count('div class="booking') == 0
490
    # and a timeperiod
491
    Booking.objects.update(cancellation_datetime=None)  # reset
492
    TimePeriod.objects.create(
493
        desk=desk, weekday=6, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0)
494
    )
495
    resp = app.get(
496
        '/manage/resource/%s/%d/week/%s/' % (resource.pk, middle_day.year, middle_day.strftime('%W'))
497
    )
498
    assert resp.text.count('div class="booking') == 4
499
    assert 'Sunday' in resp.text
500
    assert 'Saturday' in resp.text
501

  
502

  
503
def test_week_view_resource_as_manager(app, manager_user):
504
    agenda = Agenda.objects.create(label='Foo Bar', kind='meetings')
505
    agenda.view_role = manager_user.groups.all()[0]
506
    agenda.save()
507
    resource = Resource.objects.create(label='Resource 1')
508
    app = login(app, username='manager', password='manager')
509
    today = datetime.date.today()
510
    app.get('/manage/resource/%s/%d/week/%s/' % (resource.pk, today.year, today.strftime('%W')), status=403)
511

  
512

  
223 513
@freezegun.freeze_time('2020-06-15')
224 514
def test_resource_month_view(app, admin_user, get_proper_html_str):
225 515
    resource = Resource.objects.create(label='Foo bar')
......
261 551
    today = datetime.date.today()
262 552
    with CaptureQueriesContext(connection) as ctx:
263 553
        resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, today.month))
264
        assert len(ctx.captured_queries) == 8
554
        assert len(ctx.captured_queries) == 9
265 555
    assert resp.text.count('<div class="booking" style="height:33.0%;') == 2  # booking cells
266 556
    assert resp.pyquery.find('div.booking a').not_('.cancel')[0].text.strip() == 'booked'
267 557
    assert resp.pyquery.find('div.booking a').not_('.cancel')[1].text.strip() == "foo - bar's"
268
-