0001-manager-differentiate-bookings-with-colors-39794.patch
chrono/agendas/migrations/0070_auto_20201202_1834.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.29 on 2020-12-02 17:34 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 |
import django.db.models.deletion |
|
7 | ||
8 | ||
9 |
class Migration(migrations.Migration): |
|
10 | ||
11 |
dependencies = [ |
|
12 |
('agendas', '0069_translate_holidays'), |
|
13 |
] |
|
14 | ||
15 |
operations = [ |
|
16 |
migrations.CreateModel( |
|
17 |
name='BookingColor', |
|
18 |
fields=[ |
|
19 |
( |
|
20 |
'id', |
|
21 |
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), |
|
22 |
), |
|
23 |
('label', models.CharField(max_length=250, verbose_name='Label')), |
|
24 |
('index', models.PositiveSmallIntegerField()), |
|
25 |
( |
|
26 |
'agenda', |
|
27 |
models.ForeignKey( |
|
28 |
on_delete=django.db.models.deletion.CASCADE, |
|
29 |
related_name='booking_colors', |
|
30 |
to='agendas.Agenda', |
|
31 |
), |
|
32 |
), |
|
33 |
], |
|
34 |
options={'ordering': ('pk',),}, |
|
35 |
), |
|
36 |
migrations.AddField( |
|
37 |
model_name='booking', |
|
38 |
name='color', |
|
39 |
field=models.ForeignKey( |
|
40 |
null=True, |
|
41 |
on_delete=django.db.models.deletion.SET_NULL, |
|
42 |
related_name='bookings', |
|
43 |
to='agendas.BookingColor', |
|
44 |
), |
|
45 |
), |
|
46 |
migrations.AlterUniqueTogether(name='bookingcolor', unique_together=set([('agenda', 'label')]),), |
|
47 |
] |
chrono/agendas/models.py | ||
---|---|---|
1021 | 1021 |
self.save() |
1022 | 1022 | |
1023 | 1023 | |
1024 |
class BookingColor(models.Model): |
|
1025 |
COLOR_COUNT = 5 |
|
1026 | ||
1027 |
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, related_name='booking_colors') |
|
1028 |
label = models.CharField(_('Label'), max_length=250) |
|
1029 |
index = models.PositiveSmallIntegerField() |
|
1030 | ||
1031 |
class Meta: |
|
1032 |
unique_together = ('agenda', 'label') |
|
1033 |
ordering = ('pk',) |
|
1034 | ||
1035 |
def save(self, *args, **kwargs): |
|
1036 |
if self.index is None: |
|
1037 |
last_color = BookingColor.objects.filter(agenda=self.agenda).last() |
|
1038 |
last_color_index = last_color.index if last_color else -1 |
|
1039 |
self.index = (last_color_index + 1) % self.COLOR_COUNT |
|
1040 |
super().save(*args, **kwargs) |
|
1041 | ||
1042 |
def __str__(self): |
|
1043 |
return '%s' % self.label |
|
1044 | ||
1045 | ||
1024 | 1046 |
class Booking(models.Model): |
1025 | 1047 |
event = models.ForeignKey(Event, on_delete=models.CASCADE) |
1026 | 1048 |
extra_data = JSONField(null=True) |
... | ... | |
1045 | 1067 |
form_url = models.URLField(blank=True) |
1046 | 1068 |
backoffice_url = models.URLField(blank=True) |
1047 | 1069 |
cancel_callback_url = models.URLField(blank=True) |
1070 |
color = models.ForeignKey(BookingColor, null=True, on_delete=models.SET_NULL, related_name='bookings') |
|
1048 | 1071 | |
1049 | 1072 |
def save(self, *args, **kwargs): |
1050 | 1073 |
with transaction.atomic(): |
chrono/api/views.py | ||
---|---|---|
37 | 37 |
from rest_framework.views import APIView |
38 | 38 | |
39 | 39 |
from chrono.api.utils import Response |
40 |
from ..agendas.models import Agenda, Event, Booking, MeetingType, TimePeriodException, Desk |
|
40 |
from ..agendas.models import Agenda, Event, Booking, MeetingType, TimePeriodException, Desk, BookingColor
|
|
41 | 41 |
from ..interval import IntervalSet |
42 | 42 | |
43 | 43 | |
... | ... | |
739 | 739 |
count = serializers.IntegerField(min_value=1) |
740 | 740 |
cancel_booking_id = serializers.CharField(max_length=250, allow_blank=True, allow_null=True) |
741 | 741 |
force_waiting_list = serializers.BooleanField(default=False) |
742 |
use_color_for = serializers.CharField(max_length=250, allow_blank=True) |
|
742 | 743 | |
743 | 744 | |
744 | 745 |
class StringOrListField(serializers.ListField): |
... | ... | |
856 | 857 |
extra_data[k] = v |
857 | 858 | |
858 | 859 |
available_desk = None |
860 |
color = None |
|
859 | 861 | |
860 | 862 |
if agenda.accept_meetings(): |
861 | 863 |
# slots are actually timeslot ids (meeting_type:start_datetime), not events ids. |
... | ... | |
914 | 916 |
for slot in all_free_slots: |
915 | 917 |
datetimes_by_desk[slot.desk.id].add(slot.start_datetime) |
916 | 918 | |
919 |
color_label = payload.get('use_color_for') |
|
920 |
if color_label: |
|
921 |
color = BookingColor.objects.get_or_create(agenda=agenda, label=color_label)[0] |
|
922 | ||
917 | 923 |
available_desk = None |
918 | 924 | |
919 | 925 |
if agenda.kind == 'virtual': |
... | ... | |
1058 | 1064 |
cancel_callback_url=payload.get('cancel_callback_url', ''), |
1059 | 1065 |
user_display_label=payload.get('user_display_label', ''), |
1060 | 1066 |
extra_data=extra_data, |
1067 |
color=color, |
|
1061 | 1068 |
) |
1062 | 1069 |
if primary_booking is not None: |
1063 | 1070 |
new_booking.primary_booking = primary_booking |
chrono/manager/static/css/style.scss | ||
---|---|---|
107 | 107 |
margin-top: 0; |
108 | 108 |
} |
109 | 109 | |
110 | ||
111 |
table.agenda-table { |
|
112 |
border-spacing: 1vw 0; |
|
113 |
table-layout: fixed; |
|
114 |
background-color: hsla(0, 0%, 0%, 0.06); |
|
115 |
padding: 0.5em 0; |
|
116 |
padding-bottom: 2em; |
|
117 |
} |
|
118 | ||
110 | 119 |
.agenda-table thead th { |
111 |
width: 14vw; |
|
112 | 120 |
padding-bottom: 1ex; |
113 | 121 |
font-weight: normal; |
114 | 122 |
} |
... | ... | |
126 | 134 |
box-sizing: border-box; |
127 | 135 |
padding: 1.2ex 2ex; |
128 | 136 |
vertical-align: top; |
129 |
width: 8ex; |
|
130 | 137 |
font-weight: normal; |
131 | 138 |
&.hour { |
132 |
width: 5%; |
|
133 | 139 |
text-align: left; |
134 | 140 |
} |
135 | 141 |
a { |
... | ... | |
137 | 143 |
border: 0; |
138 | 144 |
} |
139 | 145 |
&.weekday { |
140 |
width: 12.5%; |
|
141 | 146 |
padding-top: 3rem; |
142 | 147 |
&.today { |
143 | 148 |
font-weight: bold; |
... | ... | |
149 | 154 |
// don't add extra padding on top row |
150 | 155 |
padding-top: 1ex; |
151 | 156 |
} |
157 |
// hour cells width |
|
158 |
.agenda-table thead tr:first-child td:first-child, |
|
159 |
.agenda-table tbody tr:first-child th:not(.weekday) { |
|
160 |
width: 5em; |
|
161 |
} |
|
152 | 162 | |
153 | 163 |
.agenda-table tbody tr.odd th.hour, |
154 | 164 |
.agenda-table tbody tr.odd td { |
155 |
background: #f0f0f0;
|
|
165 |
background: hsla(0, 0%, 0%, 0.06);
|
|
156 | 166 |
@media print { |
157 | 167 |
border-top: 1px solid #aaa; |
158 | 168 |
} |
159 | 169 |
} |
160 | 170 | |
161 | 171 |
.agenda-table tbody tr.odd td.other-month { |
162 |
background: #f8f8f8;
|
|
172 |
background: transparent;
|
|
163 | 173 |
} |
164 | 174 | |
165 | 175 | |
... | ... | |
170 | 180 |
border: 0; |
171 | 181 |
} |
172 | 182 | |
173 |
.agenda-table.month-view { |
|
174 |
border-spacing: 0; |
|
175 |
} |
|
176 | ||
177 |
.agenda-table.month-view tbody td { |
|
178 |
border: 5px solid white; |
|
179 |
border-width: 0 5px; |
|
180 |
} |
|
181 | ||
182 | 183 |
@for $i from 1 through 60 { |
183 | 184 |
table.hourspan-#{$i} tbody td { |
184 | 185 |
height: calc(#{$i} * 2.5em); |
... | ... | |
192 | 193 |
overflow: hidden; |
193 | 194 |
&.opening-hours, &.exception-hours { |
194 | 195 |
z-index: 1; |
195 |
background: #b1ea4d linear-gradient(135deg, #b1ea4d 0%, #459522 100%); |
|
196 |
opacity: 0.6; |
|
196 |
background: |
|
197 |
linear-gradient( |
|
198 |
135deg, |
|
199 |
hsl(100, 20%, 97%) 20%, |
|
200 |
hsla(100, 30%, 99%, 0.7) 40%, |
|
201 |
hsla(100, 40%, 94%, 0.7) 60%, |
|
202 |
hsl(100, 55%, 93%) 100%) |
|
203 |
fixed; |
|
197 | 204 |
left: 0.5ex; |
198 | 205 |
width: calc(100% - 1ex); |
199 | 206 |
} |
207 |
&.opening-hours { |
|
208 |
border-left: 0.5em solid white; |
|
209 |
} |
|
200 | 210 |
&.exception-hours { |
201 |
background: #fee linear-gradient(135deg, #fee 0%, #fdd 100%); |
|
211 |
background: |
|
212 |
repeating-linear-gradient( |
|
213 |
135deg, |
|
214 |
hsla(10, 10%, 75%, 0.7) 0, |
|
215 |
hsla(10, 10%, 80%, 0.55) 10px, |
|
216 |
transparent 11px, |
|
217 |
transparent 20px); |
|
202 | 218 |
text-align: center; |
203 | 219 |
} |
204 | 220 |
&.booking { |
205 |
background: #eef linear-gradient(135deg, #eef 0%, #ddf 100%); |
|
206 |
box-shadow: 0 0 1px 0 #2d2dad; |
|
207 |
width: calc(100% - 2ex); |
|
208 |
border: 1px solid #aaa; |
|
221 |
left: 0.5ex; |
|
222 |
color: #5382CF; |
|
223 |
background: |
|
224 |
linear-gradient( |
|
225 |
110deg, |
|
226 |
hsla(0, 0%, 100%, 0.85) 0%, |
|
227 |
hsla(0, 0%, 100%, 0.65) 100%) |
|
228 |
currentColor |
|
229 |
fixed; |
|
230 |
width: calc(100% - 1ex); |
|
231 |
border-color: currentColor; |
|
232 |
border-left: .5em solid currentcolor; |
|
233 | ||
209 | 234 |
z-index: 2; |
210 | 235 |
&:hover { |
211 | 236 |
z-index: 3; |
212 | 237 |
height: auto !important; |
213 | 238 |
} |
239 |
> * { |
|
240 |
color: hsla(0, 0%, 0%, 0.6); |
|
241 |
} |
|
242 |
a { |
|
243 |
// color: currentColor; |
|
244 |
color: hsla(0, 0%, 0%, 0.7); |
|
245 |
border-bottom-color: inherit; |
|
246 | ||
247 |
&:hover { |
|
248 |
color: black; |
|
249 |
} |
|
250 |
} |
|
251 | ||
214 | 252 |
} |
215 | 253 |
} |
216 | 254 | |
217 | 255 |
.monthview tbody td div.booking { |
218 |
padding: 0; |
|
219 |
transition: width 100ms ease-in, left 100ms ease-in, color 200ms ease-in; |
|
256 |
box-shadow: 0 0 0 0 #888; |
|
257 |
transition: |
|
258 |
width 100ms ease-in, |
|
259 |
left 100ms ease-in, |
|
260 |
color 200ms ease-in, |
|
261 |
box-shadow 200ms ease-in; |
|
220 | 262 |
text-indent: -9999px; |
221 | 263 |
&:hover { |
222 | 264 |
text-indent: 0; |
223 |
color: inherit; |
|
224 | 265 |
left: 0% !important; |
225 |
width: 100% !important |
|
266 |
width: 100% !important; |
|
267 |
box-shadow: 0 0 1em 0 #888; |
|
226 | 268 |
} |
227 | 269 |
span.desk { |
228 | 270 |
display: block; |
... | ... | |
311 | 353 |
p.email-subject { |
312 | 354 |
text-align: center; |
313 | 355 |
} |
356 | ||
357 |
.booking-colors { |
|
358 |
margin-top: 1.5rem; |
|
359 |
} |
|
360 |
.booking-color-label { |
|
361 |
padding: .33em .66em; |
|
362 |
border-radius: 0.33em; |
|
363 |
color: hsla(0, 0%, 100%, 0.9) !important; |
|
364 |
font-weight: bold; |
|
365 |
font-size: .65em; |
|
366 |
} |
|
367 | ||
368 |
.agenda-table tbody td div.booking-color-0 { |
|
369 |
color: hsl(28, 100%, 45%); |
|
370 |
} |
|
371 | ||
372 |
.agenda-table tbody td div.booking-color-1 { |
|
373 |
color: hsl(120, 56.9%, 40%); |
|
374 |
} |
|
375 | ||
376 |
.agenda-table tbody td div.booking-color-2 { |
|
377 |
color: hsl(359.7, 69.2%, 49.6%); |
|
378 |
} |
|
379 | ||
380 |
.agenda-table tbody td div.booking-color-3 { |
|
381 |
color: hsl(271.4, 39.4%, 57.3%); |
|
382 |
} |
|
383 | ||
384 |
.agenda-table tbody td div.booking-color-4 { |
|
385 |
color: hsl(10.2, 30.2%, 42.2%); |
|
386 |
} |
|
387 | ||
388 |
.booking-bg-color-0 { |
|
389 |
background-color: hsl(28, 100%, 45%); |
|
390 |
} |
|
391 | ||
392 |
.booking-bg-color-1 { |
|
393 |
background-color: hsl(120, 56.9%, 40%); |
|
394 |
} |
|
395 | ||
396 |
.booking-bg-color-2 { |
|
397 |
background-color: hsl(359.7, 69.2%, 49.6%); |
|
398 |
} |
|
399 | ||
400 |
.booking-bg-color-3 { |
|
401 |
background-color: hsl(271.4, 39.4%, 57.3%); |
|
402 |
} |
|
403 | ||
404 |
.booking-bg-color-4 { |
|
405 |
background-color: hsl(10.2, 30.2%, 42.2%); |
|
406 |
} |
chrono/manager/templates/chrono/booking_color_legend.html | ||
---|---|---|
1 |
{% load i18n %} |
|
2 | ||
3 |
<div class="booking-colors"> |
|
4 |
<strong>{% trans "Booking colors:" %}</strong> |
|
5 |
{% for color in colors %} |
|
6 |
<span class="booking-color-label booking-bg-color-{{ color.index }}">{{ color }}</span> |
|
7 |
{% endfor %} |
|
8 |
</div> |
chrono/manager/templates/chrono/manager_agenda_day_view.html | ||
---|---|---|
68 | 68 |
{% endif %} |
69 | 69 | |
70 | 70 |
{% for booking in desk_info.bookings %} |
71 |
<div class="booking" |
|
71 |
<div class="booking {% if booking.color %}booking-color-{{ booking.color.index }}{% endif %}"
|
|
72 | 72 |
style="height: {{ booking.css_height }}%; min-height: {{ booking.css_height }}%; top: {{ booking.css_top }}%;" |
73 | 73 |
><span class="start-time">{{booking.event.start_datetime|date:"TIME_FORMAT"}}</span> |
74 | 74 |
<a {% if booking.backoffice_url %}href="{{booking.backoffice_url}}"{% endif %}>{{ booking.meetings_display }}</a> |
75 | 75 |
<a rel="popup" class="cancel" href="{% url 'chrono-manager-booking-cancel' pk=booking.event.agenda_id booking_pk=booking.pk %}?next={{ request.path }}">{% trans "Cancel" %}</a> |
76 |
{% if booking.color %}<span class="booking-color-label booking-bg-color-{{ booking.color.index }}">{{ booking.color }}</span>{% endif %} |
|
76 | 77 |
</div> |
77 | 78 |
{% endfor %} |
78 | 79 |
</td> |
... | ... | |
90 | 91 |
</div> |
91 | 92 |
{% endfor %} |
92 | 93 | |
94 |
{% if booking_colors %} |
|
95 |
{% include "chrono/booking_color_legend.html" with colors=booking_colors %} |
|
96 |
{% endif %} |
|
97 | ||
93 | 98 |
{% endblock %} |
chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html | ||
---|---|---|
34 | 34 |
{% endfor %} |
35 | 35 | |
36 | 36 |
{% for slot in day.infos.booked_slots %} |
37 |
<div class="booking" 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" }}%">
|
|
37 |
<div class="booking {% if slot.booking.color %}booking-color-{{ slot.booking.color.index }}{% endif %}" style="left:{{ slot.css_left|stringformat:".1f" }}%;height:{{ slot.css_height|stringformat:".1f" }}%;min-height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%;">
|
|
38 | 38 |
<span class="start-time">{{slot.booking.event.start_datetime|date:"TIME_FORMAT"}}</span> |
39 | 39 |
<a {% if slot.booking.backoffice_url %}href="{{slot.booking.backoffice_url}}"{% endif %}>{{ slot.booking.meetings_display }}</a> |
40 | 40 |
<a rel="popup" class="cancel" href="{% url 'chrono-manager-booking-cancel' pk=slot.booking.event.agenda_id booking_pk=slot.booking.id %}?next={{ request.path }}">{% trans "Cancel" %}</a> |
41 | 41 |
{% if not single_desk %}<span class="desk">{{ slot.desk }}</span>{% endif %} |
42 |
{% if slot.booking.color %}<span class="booking-color-label booking-bg-color-{{ slot.booking.color.index }}">{{ slot.booking.color }}</span>{% endif %} |
|
42 | 43 |
</div> |
43 | 44 |
{% endfor %} |
44 | 45 |
{% endif %} |
... | ... | |
58 | 59 |
</div> |
59 | 60 |
{% endfor %} |
60 | 61 | |
62 |
{% if booking_colors %} |
|
63 |
{% include "chrono/booking_color_legend.html" with colors=booking_colors %} |
|
64 |
{% endif %} |
|
65 | ||
61 | 66 |
{% endblock %} |
chrono/manager/views.py | ||
---|---|---|
67 | 67 |
AgendaNotificationsSettings, |
68 | 68 |
AgendaReminderSettings, |
69 | 69 |
UnavailabilityCalendar, |
70 |
BookingColor, |
|
70 | 71 |
) |
71 | 72 | |
72 | 73 |
from .forms import ( |
... | ... | |
871 | 872 |
context['hour_span'] = max(60 // self.agenda.get_base_meeting_duration(), 1) |
872 | 873 |
except ValueError: # no meeting types defined |
873 | 874 |
context['hour_span'] = 1 |
875 |
context['booking_colors'] = BookingColor.objects.filter( |
|
876 |
agenda=self.agenda, bookings__event__in=self.object_list |
|
877 |
).distinct() |
|
874 | 878 |
context['user_can_manage'] = self.agenda.can_be_managed(self.request.user) |
875 | 879 |
return context |
876 | 880 |
tests/test_api.py | ||
---|---|---|
23 | 23 |
TimePeriodException, |
24 | 24 |
UnavailabilityCalendar, |
25 | 25 |
VirtualMember, |
26 |
BookingColor, |
|
26 | 27 |
) |
27 | 28 |
import chrono.api.views |
28 | 29 | |
... | ... | |
1261 | 1262 |
assert Booking.objects.count() == 2 |
1262 | 1263 | |
1263 | 1264 | |
1265 |
def test_booking_api_meeting_colors(app, user): |
|
1266 |
agenda = Agenda.objects.create( |
|
1267 |
label='foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56 |
|
1268 |
) |
|
1269 |
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30) |
|
1270 |
default_desk = Desk.objects.create(agenda=agenda, label='Desk 1') |
|
1271 |
time_period = TimePeriod.objects.create( |
|
1272 |
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=default_desk |
|
1273 |
) |
|
1274 |
datetimes_resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) |
|
1275 |
event_id = datetimes_resp.json['data'][2]['id'] |
|
1276 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
1277 | ||
1278 |
resp = app.post( |
|
1279 |
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id), params={'use_color_for': 'Cooking',}, |
|
1280 |
) |
|
1281 |
booking = Booking.objects.get(id=resp.json['booking_id']) |
|
1282 |
assert booking.color.label == 'Cooking' |
|
1283 |
assert booking.color.index == 0 |
|
1284 | ||
1285 |
event_id = datetimes_resp.json['data'][3]['id'] |
|
1286 |
resp = app.post_json( |
|
1287 |
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id), params={'use_color_for': 'Cooking',}, |
|
1288 |
) |
|
1289 |
new_booking = Booking.objects.get(id=resp.json['booking_id']) |
|
1290 |
assert new_booking.color.index == 0 |
|
1291 | ||
1292 |
event_id = datetimes_resp.json['data'][4]['id'] |
|
1293 |
resp = app.post_json( |
|
1294 |
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id), params={'use_color_for': 'Swimming',}, |
|
1295 |
) |
|
1296 |
new_booking = Booking.objects.get(id=resp.json['booking_id']) |
|
1297 |
assert new_booking.color.label == 'Swimming' |
|
1298 |
assert new_booking.color.index == 1 |
|
1299 | ||
1300 |
for i in range((BookingColor.COLOR_COUNT * 2) - 2): |
|
1301 |
event_id = datetimes_resp.json['data'][i]['id'] |
|
1302 |
resp = app.post_json( |
|
1303 |
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id), params={'use_color_for': str(i),}, |
|
1304 |
) |
|
1305 |
assert BookingColor.objects.count() == BookingColor.COLOR_COUNT * 2 |
|
1306 |
assert BookingColor.objects.distinct('index').order_by().count() == BookingColor.COLOR_COUNT |
|
1307 | ||
1308 | ||
1264 | 1309 |
def test_booking_api_meeting_with_resources(app, user): |
1265 | 1310 |
tomorrow = datetime.date.today() + datetime.timedelta(days=1) |
1266 | 1311 |
tomorrow_str = tomorrow.isoformat() |
tests/test_manager.py | ||
---|---|---|
2994 | 2994 |
resp = app.get( |
2995 | 2995 |
'/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200 |
2996 | 2996 |
) |
2997 |
assert len(ctx.captured_queries) == 14
|
|
2997 |
assert len(ctx.captured_queries) == 15
|
|
2998 | 2998 |
# day is displaying rows from 10am to 6pm, |
2999 | 2999 |
# opening hours, 10am to 1pm gives top: 300% |
3000 | 3000 |
# rest of the day, 1pm to 6(+1)pm gives 600% |
... | ... | |
3303 | 3303 |
) |
3304 | 3304 |
with CaptureQueriesContext(connection) as ctx: |
3305 | 3305 |
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) |
3306 |
assert len(ctx.captured_queries) == 9
|
|
3306 |
assert len(ctx.captured_queries) == 10
|
|
3307 | 3307 |
assert resp.pyquery.find('.exception-hours')[0].attrib == { |
3308 | 3308 |
'class': 'exception-hours', |
3309 | 3309 |
'style': 'height:800.0%;top:0.0%;width:48.0%;left:50.0%;', |
... | ... | |
3934 | 3934 |
resp = app.get( |
3935 | 3935 |
'/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200 |
3936 | 3936 |
) |
3937 |
assert len(ctx.captured_queries) == 15
|
|
3937 |
assert len(ctx.captured_queries) == 16
|
|
3938 | 3938 |
# day is displaying rows from 10am to 6pm, |
3939 | 3939 |
# opening hours, 10am to 1pm gives top: 300% |
3940 | 3940 |
# rest of the day, 1pm to 6(+1)pm gives 600% |
... | ... | |
4032 | 4032 |
) |
4033 | 4033 |
with CaptureQueriesContext(connection) as ctx: |
4034 | 4034 |
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) |
4035 |
assert len(ctx.captured_queries) == 10
|
|
4035 |
assert len(ctx.captured_queries) == 11
|
|
4036 | 4036 |
assert resp.pyquery.find('.exception-hours')[0].attrib == { |
4037 | 4037 |
'class': 'exception-hours', |
4038 | 4038 |
'style': 'height:800.0%;top:0.0%;width:48.0%;left:1.0%;', |
... | ... | |
5252 | 5252 |
assert '42 days' in resp.text |
5253 | 5253 |
agenda.refresh_from_db() |
5254 | 5254 |
assert agenda.maximal_booking_delay == 42 |
5255 | ||
5256 | ||
5257 |
@pytest.mark.parametrize( |
|
5258 |
'view', |
|
5259 |
( |
|
5260 |
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/%(day)d/', |
|
5261 |
'/manage/agendas/%(agenda)s/%(year)d/%(month)d/', |
|
5262 |
), |
|
5263 |
) |
|
5264 |
def test_agenda_booking_colors(app, admin_user, api_user, view): |
|
5265 |
agenda = Agenda.objects.create(label='New Example', kind='meetings') |
|
5266 |
desk = Desk.objects.create(agenda=agenda, label='New Desk') |
|
5267 |
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30) |
|
5268 |
today = datetime.date.today() |
|
5269 |
timeperiod = TimePeriod.objects.create( |
|
5270 |
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0) |
|
5271 |
) |
|
5272 | ||
5273 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
5274 |
datetimes_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meetingtype.slug)) |
|
5275 |
booking_url = datetimes_resp.json['data'][0]['api']['fillslot_url'] |
|
5276 | ||
5277 |
# book first slot without colors |
|
5278 |
resp = app.post(booking_url) |
|
5279 |
date = Booking.objects.all()[0].event.start_datetime |
|
5280 | ||
5281 |
app.reset() |
|
5282 |
login(app) |
|
5283 | ||
5284 |
url = view % {'agenda': agenda.id, 'year': date.year, 'month': date.month, 'day': date.day} |
|
5285 |
resp = app.get(url) |
|
5286 |
assert len(resp.pyquery.find('div.booking')) == 1 |
|
5287 |
assert 'booking-color-' not in resp.text |
|
5288 |
assert 'Booking colors:' not in resp.text |
|
5289 | ||
5290 |
app.reset() |
|
5291 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
5292 |
booking_url2 = datetimes_resp.json['data'][1]['api']['fillslot_url'] |
|
5293 |
booking_url3 = datetimes_resp.json['data'][2]['api']['fillslot_url'] |
|
5294 |
resp = app.post_json(booking_url2, params={'use_color_for': 'Cooking'}) |
|
5295 |
resp = app.post_json(booking_url3, params={'use_color_for': 'Cooking'}) |
|
5296 |
booking = Booking.objects.get(pk=resp.json['booking_id']) |
|
5297 | ||
5298 |
app.reset() |
|
5299 |
login(app) |
|
5300 | ||
5301 |
resp = app.get(url) |
|
5302 |
assert len(resp.pyquery.find('div.booking')) == 3 |
|
5303 |
assert len(resp.pyquery.find('div.booking.booking-color-%s' % booking.color.index)) == 2 |
|
5304 |
assert 'Booking colors:' in resp.text |
|
5305 |
assert len(resp.pyquery.find('div.booking-colors span.booking-color-label')) == 1 |
|
5306 |
assert resp.text.count('Cooking') == 3 # 2 bookings + legend |
|
5307 | ||
5308 |
app.reset() |
|
5309 |
app.authorization = ('Basic', ('john.doe', 'password')) |
|
5310 |
booking_url4 = datetimes_resp.json['data'][3]['api']['fillslot_url'] |
|
5311 |
resp = app.post_json(booking_url4, params={'use_color_for': 'Swimming'}) |
|
5312 |
new_booking = Booking.objects.get(pk=resp.json['booking_id']) |
|
5313 | ||
5314 |
app.reset() |
|
5315 |
login(app) |
|
5316 | ||
5317 |
resp = app.get(url) |
|
5318 |
assert len(resp.pyquery.find('div.booking')) == 4 |
|
5319 |
assert len(resp.pyquery.find('div.booking.booking-color-%s' % booking.color.index)) == 2 |
|
5320 |
assert len(resp.pyquery.find('div.booking.booking-color-%s' % new_booking.color.index)) == 1 |
|
5321 |
assert resp.text.count('Swimming') == 2 # 1 booking + legend |
|
5322 |
assert 'Booking colors:' in resp.text |
|
5323 |
assert len(resp.pyquery.find('div.booking-colors span.booking-color-label')) == 2 |
|
5255 |
- |