Projet

Général

Profil

0002-general-add-a-daily-view-for-meeting-agendas-11114.patch

Frédéric Péters, 26 novembre 2017 19:25

Télécharger (26,2 ko)

Voir les différences:

Subject: [PATCH 2/2] general: add a daily view for meeting agendas (#11114)

 chrono/manager/static/css/style.scss               |  79 +++++++++++++
 .../templates/chrono/manager_agenda_day_view.html  |  66 +++++++++++
 chrono/manager/urls.py                             |   2 +
 chrono/manager/views.py                            | 104 ++++++++++++++++-
 tests/test_manager.py                              | 130 ++++++++++++++++++++-
 5 files changed, 377 insertions(+), 4 deletions(-)
 create mode 100644 chrono/manager/templates/chrono/manager_agenda_day_view.html
chrono/manager/static/css/style.scss
73 73
.link-action-icon.upload::before {
74 74
	content: "\f093"; /* upload-sign */
75 75
}
76

  
77
.dayview h2 a {
78
	padding: 0 1ex;
79
}
80

  
81
.dayview table {
82
	border-collapse: collapse;
83
}
84

  
85
$dayview-column-width: 14vw;
86
$dayview-row-height: 4.5ex;
87

  
88
.dayview thead th {
89
	width: $dayview-column-width;
90
	padding-bottom: 1ex;
91
}
92

  
93
.dayview tbody th {
94
	box-sizing: border-box;
95
	text-align: left;
96
	padding: 0 2ex;
97
	line-height: $dayview-row-height;
98
	height: $dayview-row-height;
99
}
100

  
101
.dayview tbody tr:nth-child(2n+1) th,
102
.dayview tbody tr:nth-child(2n+1) td {
103
	background: #f0f0f0;
104
	@media print {
105
		border-top: 1px solid #aaa;
106
	}
107
}
108

  
109
.dayview td {
110
	padding: 0.5ex 1ex;
111
}
112

  
113
/* attr(data-rowspan) is not supported by browsers; emulate it by getting
114
 * the attribute value into a CSS variable. */
115
@for $i from 2 through 100 {
116
	[data-rowspan="#{$i}"] { --rowspan: #{$i}; }
117
}
118

  
119
.dayview div[data-rowspan] {
120
	margin-top: -1ex;
121
	box-sizing: border-box;
122
	padding: 1ex;
123
	background: #eef;
124
	position: absolute;
125
	width: calc(#{$dayview-column-width} - 2ex);
126
	height: calc(#{$dayview-row-height} * var(--rowspan) - 2ex);
127
	min-height: calc(#{$dayview-row-height} * var(--rowspan) - 2ex);
128
	border: 1px solid #666;
129
	overflow: hidden;
130
	&:hover {
131
		height: auto;
132
	}
133
}
134

  
135
div.closed-for-the-day {
136
	display: inline-block;
137
	font-size: 30px;
138
	border: 1px solid #777;
139
	padding: 2em 4em;
140
	box-shadow: 0 2px 2px 2px #666;
141
	background: #fafafd;
142
	animation-name: closed_sign_animation;
143
	animation-duration: 6s;
144
	animation-timing-function: ease-out;
145
}
146

  
147
@keyframes closed_sign_animation {
148
	0% { transform: rotate(2deg); }
149
	20% { transform: rotate(-2deg); }
150
	40% { transform: rotate(1deg); }
151
	60% { transform: rotate(-1deg); }
152
	80% { transform: rotate(0.5deg); }
153
	100% { transform: rotate(0deg); }
154
}
chrono/manager/templates/chrono/manager_agenda_day_view.html
1
{% extends "chrono/manager_agenda_view.html" %}
2
{% load i18n %}
3

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

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

  
11
{% block appbar %}
12
<h2>
13
  <a href="{{ view.get_previous_day_url }}">←</a>
14
  {{ view.date|date:"DATE_FORMAT" }}
15
  <a href="{{ view.get_next_day_url }}">→</a>
16
</h2>
17
{% if user_can_manage %}
18
  <a href="{% url 'chrono-manager-agenda-settings' pk=agenda.id %}">{% trans 'Settings' %}</a>
19
{% endif %}
20
<a href="" onclick="window.print()">{% trans 'Print' %}</a>
21
{% endblock %}
22

  
23
{% block content %}
24

  
25
{% for period, desk_bookings in view.get_timeperiods %}
26

  
27
{% if forloop.first %}
28
<table>
29
  <thead>
30
    <tr>
31
      <td></td>
32
      {% for desk in view.agenda.desk_set.all %}
33
      <th>{{ desk.label }}</th>
34
      {% endfor %}
35
    </tr>
36
  </thead>
37
  <tbody>
38
{% endif %}
39

  
40
    <tr>
41
      <th>{{ period|date:"TIME_FORMAT" }}</th>
42
      {% for booking in desk_bookings %}
43
      <td>
44
         {% if booking %}
45
         <div class="booked" {% if booking.rowspan %}data-rowspan="{{ booking.rowspan }}"{% endif %}>
46
           <a {% if booking.backoffice_url %}href="{{booking.backoffice_url}}"{% endif %}
47
             >{% if booking.label or booking.user_name %}
48
                {{booking.label}}{% if booking.label and booking.user_name %} - {% endif %} {{booking.user_name}}
49
              {% else %}{% trans "booked" %}{% endif %}</a>
50
         </div>
51
         {% endif %}
52
      </td>{% endfor %}
53
    </tr>
54

  
55
{% if forloop.last %}
56
  </tbody>
57
</table>
58
{% endif %}
59

  
60
{% empty %}
61
<div class="closed-for-the-day">
62
        {% trans "Closed" %}
63
</div>
64
{% endfor %}
65

  
66
{% endblock %}
chrono/manager/urls.py
24 24
            name='chrono-manager-agenda-add'),
25 25
        url(r'^agendas/(?P<pk>\w+)/$', views.agenda_view,
26 26
            name='chrono-manager-agenda-view'),
27
        url(r'^agendas/(?P<pk>\w+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/(?P<day>[0-9]+)/$', views.agenda_day_view,
28
            name='chrono-manager-agenda-day-view'),
27 29
        url(r'^agendas/(?P<pk>\w+)/settings$', views.agenda_settings,
28 30
            name='chrono-manager-agenda-settings'),
29 31
        url(r'^agendas/(?P<pk>\w+)/edit$', views.agenda_edit,
chrono/manager/views.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import datetime
17 18
import json
18 19

  
19 20
from django.contrib import messages
......
21 22
from django.core.urlresolvers import reverse, reverse_lazy
22 23
from django.db.models import Q
23 24
from django.http import HttpResponse, Http404, HttpResponseRedirect
24
from django.utils.timezone import now
25
from django.shortcuts import get_object_or_404
26
from django.utils.timezone import now, make_aware
25 27
from django.utils.translation import ugettext_lazy as _
26 28
from django.utils.translation import ungettext
27 29
from django.utils.encoding import force_text
28 30
from django.views.generic import (DetailView, CreateView, UpdateView,
29
        ListView, DeleteView, FormView, TemplateView)
31
        ListView, DeleteView, FormView, TemplateView, DayArchiveView)
30 32

  
31 33
from chrono.agendas.models import (Agenda, Event, MeetingType, TimePeriod,
32 34
                                   Booking, Desk, TimePeriodException, ICSError)
......
129 131
        if not agenda.can_be_viewed(self.request.user):
130 132
            raise PermissionDenied()
131 133

  
134
        if agenda.kind == 'meetings':
135
            # redirect to today view
136
            today = datetime.date.today()
137
            return HttpResponseRedirect(reverse('chrono-manager-agenda-day-view',
138
                    kwargs={'pk': agenda.id,
139
                            'year': today.year,
140
                            'month': today.month,
141
                            'day': today.day}))
142

  
143
        # redirect to settings
132 144
        return HttpResponseRedirect(
133 145
                reverse('chrono-manager-agenda-settings', kwargs={'pk': agenda.id}))
134 146

  
135 147
agenda_view = AgendaView.as_view()
136 148

  
137 149

  
150
class AgendaDayView(DayArchiveView):
151
    template_name = 'chrono/manager_agenda_day_view.html'
152
    model = Event
153
    month_format = '%m'
154
    date_field = 'start_datetime'
155
    allow_empty = True
156
    allow_future = True
157

  
158
    def dispatch(self, request, *args, **kwargs):
159
        self.agenda = get_object_or_404(Agenda, id=kwargs.get('pk'))
160
        if self.agenda.kind != 'meetings':
161
            raise Http404()
162
        if not self.agenda.can_be_viewed(request.user):
163
            raise PermissionDenied()
164

  
165
        self.date = make_aware(datetime.datetime.strptime(
166
                '%s-%s-%s' % (self.get_year(), self.get_month(), self.get_day()),
167
                '%Y-%m-%d'))
168
        return super(AgendaDayView, self).dispatch(request, *args, **kwargs)
169

  
170
    def get_queryset(self):
171
        queryset = super(AgendaDayView, self).get_queryset()
172
        queryset = queryset.filter(agenda=self.agenda).prefetch_related('booking_set')
173
        return queryset
174

  
175
    def get_context_data(self, **kwargs):
176
        context = super(AgendaDayView, self).get_context_data(**kwargs)
177
        context['agenda'] = self.agenda
178
        context['user_can_manage'] = self.agenda.can_be_managed(self.request.user)
179
        return context
180

  
181
    def get_previous_day_url(self):
182
        previous_day = self.date.date() - datetime.timedelta(days=1)
183
        return reverse('chrono-manager-agenda-day-view',
184
                kwargs={'pk': self.agenda.id,
185
                        'year': previous_day.year,
186
                        'month': previous_day.month,
187
                        'day': previous_day.day})
188

  
189
    def get_next_day_url(self):
190
        next_day = self.date.date() + datetime.timedelta(days=1)
191
        return reverse('chrono-manager-agenda-day-view',
192
                kwargs={'pk': self.agenda.id,
193
                        'year': next_day.year,
194
                        'month': next_day.month,
195
                        'day': next_day.day})
196

  
197
    def get_timeperiods(self):
198
        timeperiods = TimePeriod.objects.filter(
199
                desk__agenda=self.agenda,
200
                weekday=self.date.weekday(),
201
                )
202
        if not timeperiods:
203
            return
204

  
205
        min_timeperiod = min([x.start_time for x in timeperiods])
206
        max_timeperiod = max([x.end_time for x in timeperiods])
207

  
208
        interval = datetime.timedelta(minutes=self.agenda.get_base_meeting_duration())
209
        current_date = self.date.replace(hour=min_timeperiod.hour, minute=min_timeperiod.minute)
210
        max_date = self.date.replace(hour=max_timeperiod.hour, minute=max_timeperiod.minute)
211

  
212
        desks = self.agenda.desk_set.all()
213

  
214
        while current_date < max_date:
215
            # for each timeslot return the timeslot date and a list of per-desk
216
            # bookings
217
            bookings = []
218
            for desk in desks:
219
                booking = None
220
                event = [x for x in self.object_list if x.desk_id == desk.id and x.start_datetime == current_date]
221
                if event:
222
                    # if an event exist, check it has a non cancelled booking
223
                    event = event[0]
224
                    event_bookings = [x for x in event.booking_set.all() if not x.cancellation_datetime]
225
                    if event_bookings:
226
                        booking = event_bookings[0]
227
                        if event.meeting_type.duration > (interval.total_seconds() / 60):
228
                            booking.rowspan = int(event.meeting_type.duration / (interval.total_seconds() / 60))
229

  
230
                bookings.append(booking)
231

  
232
            yield current_date, bookings
233
            current_date += interval
234

  
235
agenda_day_view = AgendaDayView.as_view()
236

  
237

  
138 238
class ManagedAgendaMixin(object):
139 239
    agenda = None
140 240

  
tests/test_manager.py
43 43
        user = User.objects.create_superuser('admin', email=None, password='admin')
44 44
    return user
45 45

  
46
@pytest.fixture
47
def api_user():
48
    try:
49
        user = User.objects.get(username='api-user')
50
    except User.DoesNotExist:
51
        user = User.objects.create(username='john.doe',
52
                first_name=u'John', last_name=u'Doe', email='john.doe@example.net')
53
        user.set_password('password')
54
        user.save()
55
    return user
56

  
46 57
def login(app, username='admin', password='admin'):
47 58
    login_page = app.get('/login/')
48 59
    login_form = login_page.forms[0]
......
536 547
    agenda.save()
537 548
    app = login(app)
538 549
    resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
550
    resp = resp.click('Settings')
539 551
    assert "This agenda doesn't have any meeting type yet." in resp.body
540 552
    resp = resp.click('New Meeting Type')
541 553
    resp.form['label'] = 'Blah'
......
561 573

  
562 574
    app = login(app)
563 575
    resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
576
    resp = resp.click('Settings')
564 577
    resp = resp.click('Blah')
565 578
    resp = resp.click('Delete')
566 579
    resp = resp.form.submit()
......
572 585
    agenda.save()
573 586
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
574 587
    app = login(app)
575
    resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
588
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow()
589
    resp = resp.click('Settings')
576 590
    assert not 'Add a time period' in resp.body
577 591
    MeetingType(agenda=agenda, label='Blah').save()
578
    resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
592
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow()
593
    resp = resp.click('Settings')
579 594
    resp = resp.click('Add a time period')
580 595
    resp.form['weekday'].select(text='Wednesday')
581 596
    resp.form['start_time'] = '10:00'
......
658 673
    agenda.save()
659 674

  
660 675
    resp = app.get('/manage/agendas/%d/' % agenda.id).follow()
676
    resp = resp.click('Settings')
661 677
    assert 'Add a time period' in resp.content
662 678
    assert '/manage/timeperiods/%s/edit' % time_period.id in resp.body
663 679
    assert '/manage/timeperiods/%s/delete' % time_period.id in resp.body
......
679 695
    MeetingType(agenda=agenda, label='Blah').save()
680 696

  
681 697
    resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
698
    resp = resp.click('Settings')
682 699
    resp = resp.click('New Desk')
683 700
    resp.form['label'] = 'Desk A'
684 701
    resp = resp.form.submit().follow()
......
709 726
    MeetingType(agenda=agenda, label='Blah').save()
710 727

  
711 728
    resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
729
    resp = resp.click('Settings')
712 730
    resp = resp.click('New Desk')
713 731
    resp.form['label'] = 'Desk A'
714 732
    resp = resp.form.submit().follow()
......
776 794
    Booking.objects.create(event=event)
777 795
    login(app)
778 796
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
797
    resp = resp.click('Settings')
779 798
    resp = resp.click('Add a time period exception')
780 799
    resp.form['start_datetime'] = '2017-05-22 08:00'
781 800
    resp.form['end_datetime'] = '2017-05-26 17:30'
......
795 814
            cancellation_datetime=datetime.datetime(2017, 5, 20, 10, 30))
796 815
    login(app)
797 816
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
817
    resp = resp.click('Settings')
798 818
    resp = resp.click('Add a time period exception')
799 819
    resp.form['start_datetime'] = '2017-05-22 08:00'
800 820
    resp.form['end_datetime'] = '2017-05-26 17:30'
......
810 830
                              start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
811 831
    login(app)
812 832
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
833
    resp = resp.click('Settings')
813 834
    resp = resp.click('Add a time period exception')
814 835
    resp.form['start_datetime'] = '2017-05-26 17:30'
815 836
    resp.form['end_datetime'] = '2017-05-22 08:00'
......
825 846
                              start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
826 847
    login(app)
827 848
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
849
    resp = resp.click('Settings')
828 850
    resp = resp.click('Add a time period exception')
829 851
    today = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
830 852
    tomorrow = make_aware(today + datetime.timedelta(days=15))
......
848 870
    MeetingType(agenda=agenda, label='Foo').save()
849 871
    login(app)
850 872
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
873
    resp = resp.click('Settings')
851 874
    assert 'Import exceptions from .ics' not in resp.content
852 875

  
853 876
    TimePeriod.objects.create(weekday=1, desk=desk,
854 877
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
855 878

  
856 879
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
880
    resp = resp.click('Settings')
857 881
    assert 'Import exceptions from .ics' in resp.content
858 882
    resp = resp.click('upload')
859 883
    assert "You can upload a file or specify an address to a remote calendar." in resp
860 884
    resp = resp.form.submit(status=302)
861 885
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
886
    resp = resp.click('Settings')
862 887
    resp = resp.click('upload')
863 888
    resp.form['ics_file'] = Upload('exceptions.ics', 'invalid content', 'text/calendar')
864 889
    resp = resp.form.submit(status=200)
......
905 930
END:VEVENT
906 931
END:VCALENDAR"""
907 932
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
933
    resp = resp.click('Settings')
908 934
    resp = resp.click('upload')
909 935
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_exceptions, 'text/calendar')
910 936
    resp = resp.form.submit(status=302)
......
919 945
    MeetingType(agenda=agenda, label='Bar').save()
920 946
    login(app)
921 947
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
948
    resp = resp.click('Settings')
922 949
    assert 'Import exceptions from .ics' not in resp.content
923 950

  
924 951
    TimePeriod.objects.create(weekday=1, desk=desk,
925 952
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
926 953

  
927 954
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
955
    resp = resp.click('Settings')
928 956
    resp = resp.click('upload')
929 957

  
930 958
    assert 'ics_file' in resp.form.fields
......
947 975
    exception = TimePeriodException.objects.get(desk=desk)
948 976
    assert exception.external_id == 'random-event-id'
949 977
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
978
    resp = resp.click('Settings')
950 979
    resp = resp.click('upload')
951 980
    resp.form['ics_url'] = ''
952 981
    resp = resp.form.submit(status=302)
......
960 989
    MeetingType(agenda=agenda, label='Bar').save()
961 990
    login(app)
962 991
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
992
    resp = resp.click('Settings')
963 993
    assert 'Import exceptions from .ics' not in resp.content
964 994

  
965 995
    TimePeriod.objects.create(weekday=1, desk=desk,
966 996
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
967 997

  
968 998
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
999
    resp = resp.click('Settings')
969 1000
    resp = resp.click('upload')
970 1001
    resp.form['ics_url'] = 'http://example.com/foo.ics'
971 1002
    mocked_response = mock.Mock()
......
989 1020
PRODID:-//foo.bar//EN
990 1021
END:VCALENDAR"""
991 1022
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1023
    resp = resp.click('Settings')
992 1024
    resp = resp.click('upload')
993 1025
    resp = resp.form.submit(status=302)
994 1026
    assert not TimePeriodException.objects.filter(desk=desk,
......
1002 1034
    MeetingType(agenda=agenda, label='Bar').save()
1003 1035
    login(app)
1004 1036
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1037
    resp = resp.click('Settings')
1005 1038
    assert 'Import exceptions from .ics' not in resp.content
1006 1039

  
1007 1040
    TimePeriod.objects.create(weekday=1, desk=desk,
1008 1041
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
1009 1042

  
1010 1043
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1044
    resp = resp.click('Settings')
1011 1045
    resp = resp.click('upload')
1012 1046
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1013 1047
    mocked_response = mock.Mock()
......
1031 1065
    resp = resp.form.submit(status=302)
1032 1066
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
1033 1067
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1068
    resp = resp.click('Settings')
1034 1069
    resp = resp.click('upload')
1035 1070
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1036 1071
    mocked_response.text = """BEGIN:VCALENDAR
......
1054 1089
    MeetingType(agenda=agenda, label='Bar').save()
1055 1090
    login(app)
1056 1091
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1092
    resp = resp.click('Settings')
1057 1093
    assert 'Import exceptions from .ics' not in resp.content
1058 1094

  
1059 1095
    TimePeriod.objects.create(weekday=1, desk=desk,
1060 1096
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
1061 1097

  
1062 1098
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1099
    resp = resp.click('Settings')
1063 1100
    resp = resp.click('upload')
1064 1101

  
1065 1102
    assert 'ics_file' in resp.form.fields
......
1080 1117
    MeetingType(agenda=agenda, label='Bar').save()
1081 1118
    login(app)
1082 1119
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1120
    resp = resp.click('Settings')
1083 1121
    assert 'Import exceptions from .ics' not in resp.content
1084 1122

  
1085 1123
    TimePeriod.objects.create(weekday=1, desk=desk,
1086 1124
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
1087 1125

  
1088 1126
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1127
    resp = resp.click('Settings')
1089 1128
    resp = resp.click('upload')
1090 1129
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1091 1130
    mocked_response = mock.Mock()
......
1104 1143
    MeetingType(agenda=agenda, label='Bar').save()
1105 1144
    login(app)
1106 1145
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1146
    resp = resp.click('Settings')
1107 1147
    assert 'Import exceptions from .ics' not in resp.content
1108 1148
    TimePeriod.objects.create(weekday=1, desk=desk,
1109 1149
                start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
1110 1150

  
1111 1151
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
1152
    resp = resp.click('Settings')
1112 1153
    resp = resp.click('upload')
1113 1154
    resp.form['ics_url'] = 'https://example.com/foo.ics'
1114 1155
    mocked_response = mock.Mock()
......
1118 1159
    mocked_get.side_effect = mocked_requests_http_ssl_error
1119 1160
    resp = resp.form.submit(status=200)
1120 1161
    assert 'Failed to retrieve remote calendar (SSL error).' in resp.content
1162

  
1163
def test_agenda_day_view(app, admin_user, manager_user, api_user):
1164
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1165
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
1166
    desk.save()
1167

  
1168
    meetingtype = MeetingType(agenda=agenda, label='Bar', duration=30)
1169
    meetingtype.save()
1170

  
1171
    login(app)
1172
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=302)
1173
    today = datetime.date.today()
1174
    assert resp.location.endswith('%s/%s/%s/' % (today.year, today.month, today.day))
1175
    resp = resp.follow()
1176
    assert 'Closed' in resp.body # no time pediod
1177

  
1178
    TimePeriod(desk=desk, weekday=today.weekday(),
1179
            start_time=datetime.time(10, 0),
1180
            end_time=datetime.time(18, 0)).save()
1181
    resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow()
1182
    assert not 'Closed' in resp.body
1183
    assert not 'div class="booked' in resp.body
1184
    assert resp.body.count('<tr') == 17
1185

  
1186
    # book some slots
1187
    app.reset()
1188
    app.authorization = ('Basic', ('john.doe', 'password'))
1189
    resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug))
1190
    booking_url = resp.json['data'][0]['api']['fillslot_url']
1191
    booking_url2 = resp.json['data'][2]['api']['fillslot_url']
1192
    resp = app.post(booking_url)
1193
    resp = app.post_json(booking_url2,
1194
            params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
1195

  
1196
    app.reset()
1197
    login(app)
1198
    date = Booking.objects.all()[0].event.start_datetime
1199
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
1200
        agenda.id, date.year, date.month, date.day))
1201
    assert resp.body.count('div class="booked') == 2
1202
    assert resp.body.count('data-rowspan') == 0
1203

  
1204
    # create a shorted meeting type, this will double the number of rows and
1205
    # the bookings will have to span multiple rows.
1206
    meetingtype = MeetingType(agenda=agenda, label='Baz', duration=15)
1207
    meetingtype.save()
1208
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
1209
        agenda.id, date.year, date.month, date.day))
1210
    assert resp.body.count('<tr') == 33
1211
    assert resp.body.count('div class="booked') == 2
1212
    assert resp.body.count('data-rowspan') == 2
1213

  
1214
    # cancel a booking
1215
    app.reset()
1216
    app.authorization = ('Basic', ('john.doe', 'password'))
1217
    booking = Booking.objects.all()[0]
1218
    resp = app.post('/api/booking/%s/cancel/' % booking.id)
1219
    assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 1
1220

  
1221
    app.reset()
1222
    login(app)
1223
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
1224
        agenda.id, date.year, date.month, date.day))
1225
    assert resp.body.count('div class="booked') == 1
1226
    assert resp.body.count('data-rowspan') == 1
1227

  
1228
    # wrong type
1229
    agenda2 = Agenda(label=u'Foo bar')
1230
    agenda2.save()
1231
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
1232
        agenda2.id, date.year, date.month, date.day), status=404)
1233

  
1234
    # not enough permissions
1235
    agenda2.view_role = manager_user.groups.all()[0]
1236
    agenda2.save()
1237
    app.reset()
1238
    app = login(app, username='manager', password='manager')
1239
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
1240
        agenda.id, date.year, date.month, date.day), status=403)
1241

  
1242
    # just enough permissions
1243
    agenda.view_role = manager_user.groups.all()[0]
1244
    agenda.save()
1245
    resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
1246
        agenda.id, date.year, date.month, date.day), status=200)
1121
-