agendas: month/day agenda for resource (#38942)
This commit is contained in:
parent
4cc127c09e
commit
c19796751c
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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'),
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
Loading…
Reference in New Issue