0002-general-add-a-daily-view-for-meeting-agendas-11114.patch
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 |
- |