agendas: month/day agenda for resource (#38942)

This commit is contained in:
Lauréline Guérin 2020-05-29 09:41:09 +02:00
parent 4cc127c09e
commit c19796751c
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
6 changed files with 642 additions and 17 deletions

View File

@ -0,0 +1,52 @@
{% extends "chrono/manager_resource_detail.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a>{{ day|date:"SHORT_DATE_FORMAT" }}</a>
{% endblock %}
{% block appbar-title %}
<h2>
<a href="{{ view.get_previous_day_url }}"></a>
<span class="date-title">{{ view.date|date:"l j F Y" }}</span>
{% with selected_day=view.date|date:"j" selected_month=view.date|date:"n" selected_year=view.date|date:"Y" %}
<div class="date-picker" style="display: none">
<select name="day">{% for day in view.get_days %}<option value="{{ day }}" {% if selected_day == day %}selected{% endif %}>{{day}}</option>{% endfor %}</select>
<select name="month">{% for month, month_label in view.get_months %}<option value="{{ month }}" {% if selected_month == month %}selected{% endif %}>{{ month_label }}</option>{% endfor %}</select>
<select name="year">{% for year in view.get_years %}<option value="{{ year }}" {% if selected_year == year %}selected{% endif %}>{{year}}</option>{% endfor %}</select>
<button>{% trans 'Set Date' %}</button>
</div>
{% endwith %}
<a href="{{ view.get_next_day_url }}"></a>
</h2>
{% endblock %}
{% block appbar-extras %}
<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>
{% endblock %}
{% block content %}
<table class="agenda-table day-view hourspan-{{ hour_span }}">
<tbody>
{% for period, resource_info in view.get_timetable_infos %}
<tr class="{% cycle 'odd' 'even' %}">
<th class="hour">{{ period|date:"TIME_FORMAT" }}</th>
<td>
{% for booking in resource_info.bookings %}
<div class="booking"
style="height: {{ booking.css_height }}%; min-height: {{ booking.css_height }}%; top: {{ booking.css_top }}%;"
><span class="start-time">{{ booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
<a {% if booking.backoffice_url %}href="{{ booking.backoffice_url }}"{% endif %}
>{% if booking.label or booking.user_name %}
{{ booking.label }}{% if booking.label and booking.user_name %} - {% endif %} {{ booking.user_name }}
{% else %}{% trans "booked" %}{% endif %}</a>
</div>
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -2,19 +2,28 @@
{% load i18n %}
{% block page-title-extra-label %}
- {{ object.label }}
- {{ resource.label }}
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'chrono-manager-resource-view' pk=object.pk %}">{{ object }}</a>
<a href="{% url 'chrono-manager-resource-view' pk=resource.pk %}">{{ resource.label }}</a>
{% endblock %}
{% block appbar %}
<h2>{{ object }}</h2>
{% block appbar-title %}
<h2>{{ resource }}</h2>
{% endblock %}
<span class="actions">
<a rel="popup" href="{% url 'chrono-manager-resource-edit' pk=object.pk %}">{% trans 'Edit' %}</a>
<a rel="popup" href="{% url 'chrono-manager-resource-delete' pk=object.pk %}">{% trans 'Delete' %}</a>
{% block appbar-extras %}
<a rel="popup" href="{% url 'chrono-manager-resource-edit' pk=resource.pk %}">{% trans 'Edit' %}</a>
<a rel="popup" href="{% url 'chrono-manager-resource-delete' pk=resource.pk %}">{% trans 'Delete' %}</a>
{% now "Y" as today_year %}
{% now "n" as today_month %}
{% now "j" as today_day %}
<a href="{% url 'chrono-manager-resource-month-view' pk=resource.pk year=today_year month=today_month %}">{% trans 'Month view' %}</a>
<a href="{% url 'chrono-manager-resource-day-view' pk=resource.pk year=today_year month=today_month day=today_day %}">{% trans 'Day view' %}</a>
{% endblock %}
</span>
{% endblock %}
@ -23,7 +32,7 @@
<div class="section">
<h3>{% trans 'Used in meetings agendas' %}</h3>
<div>
{% with object.agenda_set.all as agendas %}
{% with resource.agenda_set.all as agendas %}
{% if agendas %}
<ul class="objects-list single-links">
{% for agenda in agendas %}

View File

@ -0,0 +1,64 @@
{% extends "chrono/manager_resource_detail.html" %}
{% load i18n %}
{% block bodyargs %}class="monthview"{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a>{{ view.date|date:"F Y" }}</a>
{% endblock %}
{% block appbar-title %}
<h2>
<a href="{{ view.get_previous_month_url }}"></a>
<span class="date-title">{{ view.date|date:"F Y" }}</span>
{% with selected_month=view.date|date:"n" selected_year=view.date|date:"Y" %}
<div class="date-picker" style="display: none">
<select name="month">{% for month, month_label in view.get_months %}<option value="{{ month }}" {% if selected_month == month %}selected{% endif %}>{{ month_label }}</option>{% endfor %}</select>
<select name="year">{% for year in view.get_years %}<option value="{{ year }}" {% if selected_year == year %}selected{% endif %}>{{year}}</option>{% endfor %}</select>
<button>{% trans 'Set Date' %}</button>
</div>
{% endwith %}
<a href="{{ view.get_next_month_url }}"></a>
</h2>
{% endblock %}
{% block appbar-extras %}
<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>
{% endblock %}
{% block content %}
<table class="agenda-table month-view single-desk">
<tbody>
{% for week_days in view.get_timetable_infos %}
<tr>
<th></th>
{% for day in week_days.days %}
<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>
{% endfor %}
</tr>
{% for hour in week_days.periods %}
<tr class="{% cycle 'odd' 'even' %}">
<th class="hour">{{ hour|date:"TIME_FORMAT" }}</th>
{% for day in week_days.days %}
<td class="{% if day.other_month %}other-month{% endif %} {% if day.today %}today{% endif %}">
{% if forloop.parentloop.first %}
{% for slot in day.infos.booked_slots %}
<div class="booking" style="height:{{ slot.css_height|stringformat:".1f" }}%;min-height:{{ slot.css_height|stringformat:".1f" }}%;top:{{ slot.css_top|stringformat:".1f" }}%">
<span class="start-time">{{ slot.booking.event.start_datetime|date:"TIME_FORMAT" }}</span>
<a {% if slot.booking.backoffice_url %}href="{{ slot.booking.backoffice_url }}"{% endif %}
>{% if slot.booking.label or slot.booking.user_name %}
{{ slot.booking.label }}{% if slot.booking.label and slot.booking.user_name %} - {% endif %} {{ slot.booking.user_name }}
{% else %}{% trans "booked" %}{% endif %}</a>
</div>
{% endfor %}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -23,6 +23,16 @@ urlpatterns = [
url(r'^resources/$', views.resource_list, name='chrono-manager-resource-list'),
url(r'^resource/add/$', views.resource_add, name='chrono-manager-resource-add'),
url(r'^resource/(?P<pk>\d+)/$', views.resource_view, name='chrono-manager-resource-view'),
url(
r'^resource/(?P<pk>\d+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$',
views.resource_monthly_view,
name='chrono-manager-resource-month-view',
),
url(
r'^resource/(?P<pk>\d+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/(?P<day>[0-9]+)/$',
views.resource_day_view,
name='chrono-manager-resource-day-view',
),
url(r'^resource/(?P<pk>\d+)/edit/$', views.resource_edit, name='chrono-manager-resource-edit'),
url(r'^resource/(?P<pk>\d+)/delete/$', views.resource_delete, name='chrono-manager-resource-delete'),
url(r'^agendas/add/$', views.agenda_add, name='chrono-manager-agenda-add'),

View File

@ -17,11 +17,13 @@
import datetime
import itertools
import json
import math
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db.models import Prefetch
from django.db.models import Q
from django.db.models import Min, Max
from django.forms import ValidationError
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
@ -113,10 +115,285 @@ class ResourceDetailView(DetailView):
template_name = 'chrono/manager_resource_detail.html'
model = Resource
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['resource'] = self.object
return context
resource_view = ResourceDetailView.as_view()
class DateMixin(object):
def get_days(self):
return [str(x) for x in range(1, 32)]
def get_months(self):
return [(str(x), MONTHS[x]) for x in range(1, 13)]
def get_years(self):
year = now().year
return [str(x) for x in range(year - 1, year + 5)]
class ResourceDayView(DateMixin, DayArchiveView):
template_name = 'chrono/manager_resource_day_view.html'
model = Event
month_format = '%m'
date_field = 'start_datetime'
allow_empty = True
allow_future = True
def dispatch(self, request, *args, **kwargs):
self.resource = get_object_or_404(Resource, pk=kwargs['pk'])
# specify 6am time to get the expected timezone on daylight saving time
# days.
try:
self.date = make_aware(
datetime.datetime.strptime(
'%s-%s-%s 06:00' % (self.get_year(), self.get_month(), self.get_day()), '%Y-%m-%d %H:%M'
)
)
except ValueError: # day is out of range for month
# redirect to last day of month
date = datetime.date(int(self.get_year()), int(self.get_month()), 1)
date += datetime.timedelta(days=40)
date = date.replace(day=1)
date -= datetime.timedelta(days=1)
return HttpResponseRedirect(
reverse(
'chrono-manager-resource-day-view',
kwargs={'pk': self.resource.pk, 'year': date.year, 'month': date.month, 'day': date.day},
)
)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
queryset = (
self.resource.event_set.all().select_related('meeting_type').prefetch_related('booking_set')
)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['resource'] = self.resource
context['hour_span'] = 1
durations = MeetingType.objects.filter(agenda__resources=self.resource).values_list(
'duration', flat=True
)
if durations:
gcd = durations[0]
for duration in durations[1:]:
gcd = math.gcd(duration, gcd)
context['hour_span'] = max(60 // gcd, 1)
return context
def get_previous_day_url(self):
previous_day = self.date.date() - datetime.timedelta(days=1)
return reverse(
'chrono-manager-resource-day-view',
kwargs={
'pk': self.resource.pk,
'year': previous_day.year,
'month': previous_day.month,
'day': previous_day.day,
},
)
def get_next_day_url(self):
next_day = self.date.date() + datetime.timedelta(days=1)
return reverse(
'chrono-manager-resource-day-view',
kwargs={
'pk': self.resource.pk,
'year': next_day.year,
'month': next_day.month,
'day': next_day.day,
},
)
def get_timetable_infos(self):
timeperiods = TimePeriod.objects.filter(desk__agenda__resources=self.resource).aggregate(
Min('start_time'), Max('end_time')
)
min_timeperiod = timeperiods['start_time__min']
max_timeperiod = timeperiods['end_time__max']
interval = datetime.timedelta(minutes=60)
current_date = self.date.replace(hour=min_timeperiod and min_timeperiod.hour or 0, minute=0)
max_date = self.date.replace(hour=max_timeperiod and max_timeperiod.hour or 23, minute=0)
if not max_timeperiod or max_timeperiod.minute != 0:
# until the end of the last hour.
max_date += datetime.timedelta(hours=1)
while current_date < max_date:
info = {}
info['bookings'] = bookings = [] # bookings for this resource
finish_datetime = current_date + interval
for event in [
x
for x in self.object_list
if x.start_datetime >= current_date and x.start_datetime < finish_datetime
]:
# don't consider cancelled bookings
for booking in [x for x in event.booking_set.all() if not x.cancellation_datetime]:
booking.css_top = int(100 * event.start_datetime.minute / 60)
booking.css_height = int(100 * event.meeting_type.duration / 60)
bookings.append(booking)
yield current_date, info
current_date += interval
resource_day_view = ResourceDayView.as_view()
class ResourceMonthView(DateMixin, MonthArchiveView):
template_name = 'chrono/manager_resource_month_view.html'
model = Event
month_format = '%m'
date_field = 'start_datetime'
allow_empty = True
allow_future = True
def dispatch(self, request, *args, **kwargs):
self.resource = get_object_or_404(Resource, pk=kwargs['pk'])
self.date = make_aware(
datetime.datetime.strptime(
'%s-%s-%s 06:00' % (self.get_year(), self.get_month(), 1), '%Y-%m-%d %H:%M'
)
)
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
queryset = (
self.resource.event_set.all().select_related('meeting_type').prefetch_related('booking_set')
)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['resource'] = self.resource
return context
def get_previous_month_url(self):
previous_month = self.get_previous_month(self.date.date())
return reverse(
'chrono-manager-resource-month-view',
kwargs={'pk': self.resource.pk, 'year': previous_month.year, 'month': previous_month.month},
)
def get_next_month_url(self):
next_month = self.get_next_month(self.date.date())
return reverse(
'chrono-manager-resource-month-view',
kwargs={'pk': self.resource.pk, 'year': next_month.year, 'month': next_month.month},
)
def get_timetable_infos(self):
timeperiods = TimePeriod.objects.filter(desk__agenda__resources=self.resource).aggregate(
Min('start_time'), Max('end_time')
)
self.min_timeperiod = timeperiods['start_time__min']
self.max_timeperiod = timeperiods['end_time__max']
weekdays = TimePeriod.objects.filter(desk__agenda__resources=self.resource).values_list(
'weekday', flat=True
)
hide_sunday = 6 not in weekdays
hide_weekend = hide_sunday and 5 not in weekdays
# avoid displaying empty first week
first_week_offset = int(
(hide_sunday and self.date.weekday() == 6) or (hide_weekend and self.date.weekday() == 5)
)
first_week_number = self.date.isocalendar()[1]
if first_week_number >= 52:
first_week_number = 0
last_month_day = self.get_next_month(self.date.date()) - datetime.timedelta(days=1)
last_week_number = last_month_day.isocalendar()[1]
if last_week_number < first_week_number: # new year
last_week_number = 53
for week_number in range(first_week_number + first_week_offset, last_week_number + 1):
yield self.get_week_timetable_infos(
week_number - first_week_number, week_end_offset=int(hide_sunday) + int(hide_weekend),
)
def get_week_timetable_infos(self, week_index, week_end_offset=0):
date = self.date + datetime.timedelta(week_index * 7)
year, week_number, dow = date.isocalendar()
start_date = date - datetime.timedelta(dow)
interval = datetime.timedelta(minutes=60)
period = self.date.replace(hour=self.min_timeperiod and self.min_timeperiod.hour or 0, minute=0)
max_date = self.date.replace(hour=self.max_timeperiod and self.max_timeperiod.hour or 23, minute=0)
if not self.max_timeperiod or self.max_timeperiod.minute != 0:
# until the end of the last hour.
max_date += datetime.timedelta(hours=1)
periods = []
while period < max_date:
periods.append(period)
period = period + interval
return {
'days': [
self.get_day_timetable_infos(start_date + datetime.timedelta(i), interval)
for i in range(1, 8 - week_end_offset)
],
'periods': periods,
}
def get_day_timetable_infos(self, day, interval):
day = make_aware(make_naive(day)) # give day correct timezone
period = current_date = day.replace(
hour=self.min_timeperiod and self.min_timeperiod.hour or 0, minute=0
)
max_date = day.replace(hour=self.max_timeperiod and self.max_timeperiod.hour or 23, minute=0)
if not self.max_timeperiod or self.max_timeperiod.minute != 0:
# until the end of the last hour.
max_date += datetime.timedelta(hours=1)
timetable = {
'date': current_date,
'today': day.date() == datetime.date.today(),
'other_month': day.month != self.date.month,
'infos': {'booked_slots': []},
}
# compute booking and opening hours only for current month
if self.date.month != day.month:
return timetable
while period <= max_date:
period_end = period + interval
for event in [
x for x in self.object_list if x.start_datetime >= period and x.start_datetime < period_end
]:
# don't consider cancelled bookings
bookings = [x for x in event.booking_set.all() if not x.cancellation_datetime]
if not bookings:
continue
booking = {
'css_top': 100 * (event.start_datetime - current_date).seconds // 3600,
'css_height': 100 * event.meeting_type.duration // 60,
'booking': bookings[0],
}
timetable['infos']['booked_slots'].append(booking)
period += interval
return timetable
resource_monthly_view = ResourceMonthView.as_view()
class ResourceAddView(CreateView):
template_name = 'chrono/manager_resource_form.html'
model = Resource
@ -355,7 +632,7 @@ class AgendaView(ViewableAgendaMixin, View):
agenda_view = AgendaView.as_view()
class AgendaDateView(ViewableAgendaMixin):
class AgendaDateView(DateMixin, ViewableAgendaMixin):
model = Event
month_format = '%m'
date_field = 'start_datetime'
@ -408,16 +685,6 @@ class AgendaDateView(ViewableAgendaMixin):
)
return queryset
def get_days(self):
return [str(x) for x in range(1, 32)]
def get_months(self):
return [(str(x), MONTHS[x]) for x in range(1, 13)]
def get_years(self):
year = now().year
return [str(x) for x in range(year - 1, year + 5)]
class AgendaDayView(AgendaDateView, DayArchiveView):
template_name = 'chrono/manager_agenda_day_view.html'

View File

@ -206,6 +206,229 @@ def test_view_resource(app, admin_user):
assert '/manage/agendas/%s/settings' % agenda.pk in resp.text
def test_resource_day_view(app, admin_user):
today = datetime.date.today()
resource = Resource.objects.create(label='Foo bar')
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
timeperiod = TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
login(app)
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
assert 'div class="booking' not in resp.text
assert resp.text.count('<tr') == 24 # resource in no agenda
agenda.resources.add(resource)
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
assert 'div class="booking' not in resp.text
assert resp.text.count('<tr') == 8 # 10->18 (not included)
timeperiod.end_time = datetime.time(18, 30) # end during an hour
timeperiod.save()
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 9 # 10->18 (included)
# book some slots
for hour, minute in [(10, 30), (14, 0)]:
event = Event.objects.create(
agenda=agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=now().replace(hour=hour, minute=minute),
)
event.resources.add(resource)
Booking.objects.create(event=event)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
assert len(ctx.captured_queries) == 7
assert resp.text.count('div class="booking') == 2
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
assert 'height: 50%; top: 50%;' in resp.text # booking cells
# create a shorter meeting type, this will change the table CSS class
# (and visually this will give more room for events)
meetingtype = MeetingType.objects.create(agenda=agenda, label='Baz', duration=15)
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
assert resp.text.count('div class="booking') == 2
assert 'hourspan-4' in resp.text # table CSS class
# cancel a booking
booking = Booking.objects.first()
booking.cancel()
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
assert resp.text.count('div class="booking') == 1
def test_resource_day_view_late_meeting(app, admin_user):
today = datetime.date.today()
resource = Resource.objects.create(label='Foo bar')
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
agenda.resources.add(resource)
desk = Desk.objects.create(agenda=agenda, label='Desk')
MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
TimePeriod.objects.create(
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(23, 30)
)
login(app)
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, today.year, today.month, today.day))
assert resp.text.count('<tr') == 14
assert '<th class="hour">midnight</th>' not in resp.text
assert '<th class="hour">11 p.m.</th>' in resp.text
def test_resource_invalid_day_view(app, admin_user):
resource = Resource.objects.create(label='Foo bar')
login(app)
resp = app.get('/manage/resource/%s/%d/%d/%d/' % (resource.pk, 2018, 11, 31), status=302)
assert resp.location.endswith('2018/11/30/')
def test_resource_month_view(app, admin_user):
resource = Resource.objects.create(label='Foo bar')
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
agenda.resources.add(resource)
desk = Desk.objects.create(agenda=agenda, label='Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=20)
TimePeriod.objects.create(
desk=desk, weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
login(app)
today = datetime.date(2018, 11, 10) # fixed day
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, today.month))
assert '<div class="booking' not in resp.text
first_month_day = today.replace(day=1)
last_month_day = today.replace(day=1, month=today.month + 1) - datetime.timedelta(days=1)
start_week_number = first_month_day.isocalendar()[1]
end_week_number = last_month_day.isocalendar()[1]
weeks_number = end_week_number - start_week_number + 1
assert resp.text.count('<tr') == 9 * weeks_number
# book some slots
for hour, minute in [(10, 30), (14, 0)]:
event = Event.objects.create(
agenda=agenda,
places=1,
desk=desk,
meeting_type=meetingtype,
start_datetime=now().replace(hour=hour, minute=minute),
)
event.resources.add(resource)
Booking.objects.create(event=event)
today = datetime.date.today()
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, today.month))
assert len(ctx.captured_queries) == 8
assert resp.text.count('<div class="booking" style="height:33.0%;') == 2 # booking cells
# cancel booking
booking = Booking.objects.first()
booking.cancel()
# make sure the are not
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, today.year, today.month))
assert resp.text.count('<div class="booking"') == 1
def test_resource_month_view_weekend(app, admin_user):
resource = Resource.objects.create(label='Foo bar')
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
agenda.resources.add(resource)
desk = Desk.objects.create(agenda=agenda, label='Desk')
MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
monday = 0
TimePeriod.objects.create(
desk=desk, weekday=monday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
month, year = 1, 2019
login(app)
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, year, month))
assert 'Sunday' not in resp.text
assert 'Saturday' not in resp.text
# No Monday on first row since month starts a Tuesday
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 1
# When weekend is hidden, do not display an empty first week
month, year = 12, 2019 # month starts a Sunday
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, year, month))
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
month, year = 6, 2019 # month starts a Saturday
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, year, month))
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 0
saturday = 5
timeperiod_sat = TimePeriod.objects.create(
desk=desk, weekday=saturday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, year, month))
assert 'Sunday' not in resp.text
assert 'Saturday' in resp.text
assert len(resp.pyquery.find('tbody tr:first th.weekday:empty')) == 5
sunday = 6
TimePeriod.objects.create(
desk=desk, weekday=sunday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
)
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, year, month))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
timeperiod_sat.delete()
resp = app.get('/manage/resource/%s/%s/%s/' % (resource.pk, year, month))
assert 'Sunday' in resp.text
assert 'Saturday' in resp.text
def test_resource_month_view_dst_change(app, admin_user):
resource = Resource.objects.create(label='Foo bar')
agenda = Agenda.objects.create(label='Agenda', kind='meetings')
agenda.resources.add(resource)
desk = Desk.objects.create(agenda=agenda, label='Desk')
meetingtype = MeetingType.objects.create(agenda=agenda, label='Bar', duration=30)
TimePeriod.objects.create(
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
)
login(app)
# book some slots
with freezegun.freeze_time('2019-10-01'):
for start_datetime in [
make_aware(datetime.datetime(2019, 10, 2, 10, 0)),
make_aware(datetime.datetime(2019, 10, 29, 10, 0)),
]:
event = Event.objects.create(
agenda=agenda, places=1, desk=desk, meeting_type=meetingtype, start_datetime=start_datetime,
)
event.resources.add(resource)
Booking.objects.create(event=event)
# check booked slots are similarly aligned
resp = app.get('/manage/resource/%s/2019/10/' % resource.pk)
assert resp.text.count('height:50.0%;top:100.0%') == 2
def test_resource_month_view_januaries(app, admin_user):
resource = Resource.objects.create(label='Foo bar')
for year in range(2020, 2030):
date = datetime.date(year, 1, 1)
with freezegun.freeze_time(date):
login(app)
resp = app.get('/manage/resource/%s/%s/1/' % (resource.pk, date.year))
assert resp.text.count('<th></th>') in (4, 5)
def test_edit_resource(app, admin_user):
resource = Resource.objects.create(label='Foo bar')