0001-manager-display-exceptions-in-day-month-views-39906.patch
chrono/manager/static/css/style.scss | ||
---|---|---|
172 | 172 |
padding: 1ex; |
173 | 173 |
position: absolute; |
174 | 174 |
overflow: hidden; |
175 |
&.opening-hours { |
|
175 |
&.opening-hours, &.exception-hours {
|
|
176 | 176 |
z-index: 1; |
177 | 177 |
background: #b1ea4d linear-gradient(135deg, #b1ea4d 0%, #459522 100%); |
178 | 178 |
opacity: 0.6; |
179 | 179 |
left: 0.5ex; |
180 | 180 |
width: calc(100% - 1ex); |
181 | 181 |
} |
182 |
&.exception-hours { |
|
183 |
background: #fee linear-gradient(135deg, #fee 0%, #fdd 100%); |
|
184 |
text-align: center; |
|
185 |
} |
|
182 | 186 |
&.booking { |
183 | 187 |
background: #eef linear-gradient(135deg, #eef 0%, #ddf 100%); |
184 | 188 |
box-shadow: 0 0 1px 0 #2d2dad; |
chrono/manager/templates/chrono/manager_agenda_day_view.html | ||
---|---|---|
60 | 60 |
style="height: {{ slot.css_height }}%; top: {{ slot.css_top }}%;" |
61 | 61 |
>{{slot.begin}} {{slot.end}}</div> |
62 | 62 |
{% endfor %} |
63 |
{% for slot in desk_info.exceptions %} |
|
64 |
<div class="exception-hours" |
|
65 |
style="height: {{ slot.css_height }}%; top: {{ slot.css_top }}%;" |
|
66 |
>{% if slot.label %}<span class="exception-label">{{slot.label}}</span>{% endif %}</div> |
|
67 |
{% endfor %} |
|
63 | 68 |
{% endif %} |
64 | 69 | |
65 | 70 |
{% for booking in desk_info.bookings %} |
chrono/manager/templates/chrono/manager_meetings_agenda_month_view.html | ||
---|---|---|
26 | 26 |
{% for slot in day.infos.opening_hours %} |
27 | 27 |
<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> |
28 | 28 |
{% endfor %} |
29 | ||
30 |
{% for slot in day.infos.exceptions %} |
|
31 |
<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> |
|
32 |
{% endfor %} |
|
33 | ||
29 | 34 |
{% for slot in day.infos.booked_slots %} |
30 | 35 |
<div class="booking" style="left:{{ slot.css_left|stringformat:".1f" }}%;height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%;width:{{ slot.css_width|stringformat:".1f" }}%"> |
31 | 36 |
<span class="start-time">{{slot.booking.event.start_datetime|date:"TIME_FORMAT"}}</span> |
chrono/manager/views.py | ||
---|---|---|
380 | 380 |
# until the end of the last hour. |
381 | 381 |
max_date += datetime.timedelta(hours=1) |
382 | 382 | |
383 |
desks = self.agenda.desk_set.all() |
|
383 |
desks = self.agenda.desk_set.all().prefetch_related('timeperiodexception_set')
|
|
384 | 384 | |
385 | 385 |
first = True |
386 | 386 |
while current_date < max_date: |
... | ... | |
399 | 399 |
'css_height': 100 * (opening_hour.end - opening_hour.begin).seconds // 3600, |
400 | 400 |
} |
401 | 401 |
) |
402 | ||
403 |
# and exceptions for this desk |
|
404 |
info['exceptions'] = [] |
|
405 |
for exception in desk.timeperiodexception_set.all(): |
|
406 |
if exception.end_datetime < start_date: |
|
407 |
continue |
|
408 |
if exception.start_datetime > max_date: |
|
409 |
continue |
|
410 |
start_max = max(start_date, exception.start_datetime) |
|
411 |
end_min = min(max_date, exception.end_datetime) |
|
412 |
exception.css_top = int(100 * (start_max - start_date).seconds // 3600) |
|
413 |
exception.css_height = int(100 * (end_min - start_max).seconds // 3600) |
|
414 |
info['exceptions'].append(exception) |
|
415 | ||
402 | 416 |
infos.append(info) |
403 | 417 |
info['bookings'] = bookings = [] # bookings for this desk |
404 | 418 |
finish_datetime = current_date + interval |
... | ... | |
511 | 525 |
'date': current_date, |
512 | 526 |
'today': day.date() == datetime.date.today(), |
513 | 527 |
'other_month': day.month != self.date.month, |
514 |
'infos': {'opening_hours': [], 'booked_slots': []}, |
|
528 |
'infos': {'opening_hours': [], 'exceptions': [], 'booked_slots': []},
|
|
515 | 529 |
} |
516 | 530 | |
517 |
desks = self.agenda.desk_set.all() |
|
531 |
desks = self.agenda.desk_set.all().prefetch_related('timeperiodexception_set')
|
|
518 | 532 |
desks_len = len(desks) |
519 | 533 |
max_date = day.replace(hour=self.max_timeperiod.hour, minute=0) |
520 | 534 | |
... | ... | |
557 | 571 |
'css_left': left, |
558 | 572 |
} |
559 | 573 |
) |
574 |
for exception in desk.timeperiodexception_set.all(): |
|
575 |
if exception.end_datetime < current_date: |
|
576 |
continue |
|
577 |
if exception.start_datetime > max_date: |
|
578 |
continue |
|
579 |
start_max = max(current_date, exception.start_datetime) |
|
580 |
end_min = min(max_date, exception.end_datetime) |
|
581 |
exception.css_top = int(100 * (start_max - current_date).seconds // 3600) |
|
582 |
exception.css_height = int(100 * (end_min - start_max).seconds // 3600) |
|
583 |
exception.css_width = width |
|
584 |
exception.css_left = left |
|
585 |
timetable['infos']['exceptions'].append(exception) |
|
586 | ||
560 | 587 |
left += width + 1 |
561 | 588 |
period += interval |
562 | 589 |
tests/test_manager.py | ||
---|---|---|
4 | 4 |
import copy |
5 | 5 |
import json |
6 | 6 |
import os |
7 |
import re |
|
7 | 8 | |
8 | 9 |
from django.contrib.auth.models import User, Group |
9 | 10 |
from django.utils.encoding import force_text |
... | ... | |
1724 | 1725 |
agenda.save() |
1725 | 1726 |
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200) |
1726 | 1727 | |
1728 |
# display exception |
|
1729 |
TimePeriodException.objects.create( |
|
1730 |
label='Exception for the afternoon', |
|
1731 |
desk=desk, |
|
1732 |
start_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 13, 0)), |
|
1733 |
end_datetime=make_aware(datetime.datetime(date.year, date.month, date.day, 23, 0)), |
|
1734 |
) |
|
1735 |
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200) |
|
1736 |
# day is displaying rows from 10am to 6pm, |
|
1737 |
# opening hours, 10am to 1pm gives top: 300% |
|
1738 |
# rest of the day, 1pm to 6(+1)pm gives 600% |
|
1739 |
assert resp.pyquery.find('.exception-hours')[0].attrib == { |
|
1740 |
'class': 'exception-hours', |
|
1741 |
'style': 'height: 600%; top: 300%;', |
|
1742 |
} |
|
1743 |
assert resp.pyquery.find('.exception-hours span')[0].text == 'Exception for the afternoon' |
|
1744 | ||
1727 | 1745 | |
1728 | 1746 |
def test_agenda_day_view_late_meeting(app, admin_user, manager_user, api_user): |
1729 | 1747 |
agenda = Agenda.objects.create(label='New Example', kind='meetings') |
... | ... | |
1888 | 1906 |
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) |
1889 | 1907 |
assert not 'No opening hours this month.' in resp.text |
1890 | 1908 | |
1909 |
# display exception |
|
1910 |
TimePeriodException.objects.create( |
|
1911 |
label='Exception for a December day', |
|
1912 |
desk=desk, |
|
1913 |
start_datetime=make_aware(datetime.datetime(2018, 12, 15, 5, 0)), |
|
1914 |
end_datetime=make_aware(datetime.datetime(2018, 12, 15, 23, 0)), |
|
1915 |
) |
|
1916 |
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month)) |
|
1917 |
assert resp.pyquery.find('.exception-hours')[0].attrib == { |
|
1918 |
'class': 'exception-hours', |
|
1919 |
'style': 'height:800.0%;top:0.0%;width:48.0%;left:50.0%;', |
|
1920 |
'title': 'Exception for a December day', |
|
1921 |
} |
|
1922 | ||
1891 | 1923 | |
1892 | 1924 |
def test_agenda_month_view_weekend(app, admin_user, manager_user, api_user): |
1893 | 1925 |
agenda = Agenda.objects.create(label='Passeports', kind='meetings') |
1894 |
- |