manager: add partial bookings day view (#78056)
This commit is contained in:
parent
21c0d0da1f
commit
1b9bd2f428
|
@ -93,6 +93,7 @@ class AgendaAddForm(forms.ModelForm):
|
|||
if self.cleaned_data.get('kind') == 'partial-bookings':
|
||||
self.cleaned_data['kind'] = 'events'
|
||||
self.instance.partial_bookings = True
|
||||
self.instance.default_view = 'day'
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
create = self.instance.pk is None
|
||||
|
@ -129,6 +130,8 @@ class AgendaEditForm(forms.ModelForm):
|
|||
else:
|
||||
if not EventsType.objects.exists():
|
||||
del self.fields['events_type']
|
||||
if kwargs['instance'].partial_bookings:
|
||||
del self.fields['default_view']
|
||||
|
||||
|
||||
class AgendaBookingDelaysForm(forms.ModelForm):
|
||||
|
|
|
@ -597,3 +597,10 @@ div#appbar a.active {
|
|||
background: #386ede;
|
||||
color: white;
|
||||
}
|
||||
|
||||
table.partial-bookings {
|
||||
border-spacing: 0;
|
||||
td.hour-cell {
|
||||
outline: solid 1px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
{% now "m" as today_month %}
|
||||
{% now "j" as today_day %}
|
||||
{% now "Ymj" as today %}
|
||||
{% if not no_opened and agenda.kind == 'events' %}
|
||||
<a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
|
||||
{% if not agenda.partial_bookings %}
|
||||
{% if not no_opened and agenda.kind == 'events' %}
|
||||
<a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
|
||||
{% endif %}
|
||||
<span class="buttons-group">
|
||||
<a {% if active == 'day' %}class="active"{% endif%} href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=view.date|date:"Y"|default:today_year|default:today_year month=view.date|date:"m"|default:today_month day=view.date|date:"d"|default:today_day %}">{% trans 'Day' %}</a>
|
||||
<a {% if active == 'week' %}class="active"{% endif%} href="{% url 'chrono-manager-agenda-week-view' pk=agenda.pk year=view.date|date:"Y"|default:today_year|default:today_year month=view.date|date:"m"|default:today_month day=view.date|date:"d"|default:today_day %}">{% trans 'Week' %}</a>
|
||||
<a {% if active == 'month' %}class="active"{% endif%} href="{% url 'chrono-manager-agenda-month-view' pk=agenda.pk year=view.date|date:"Y"|default:today_year|default:today_year month=view.date|date:"m"|default:today_month day=view.date|date:"d"|default:today_day %}">{% trans 'Month' %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="buttons-group">
|
||||
<a {% if active == 'day' %}class="active"{% endif%} href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=view.date|date:"Y"|default:today_year|default:today_year month=view.date|date:"m"|default:today_month day=view.date|date:"d"|default:today_day %}">{% trans 'Day' %}</a>
|
||||
<a {% if active == 'week' %}class="active"{% endif%} href="{% url 'chrono-manager-agenda-week-view' pk=agenda.pk year=view.date|date:"Y"|default:today_year|default:today_year month=view.date|date:"m"|default:today_month day=view.date|date:"d"|default:today_day %}">{% trans 'Week' %}</a>
|
||||
<a {% if active == 'month' %}class="active"{% endif%} href="{% url 'chrono-manager-agenda-month-view' pk=agenda.pk year=view.date|date:"Y"|default:today_year|default:today_year month=view.date|date:"m"|default:today_month day=view.date|date:"d"|default:today_day %}">{% trans 'Month' %}</a>
|
||||
</span>
|
||||
{% if not no_today %}
|
||||
<a {% if active == 'day' and view.date|date:"Ymj" == today %}class="active"{% endif%} href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=today_year month=today_month day=today_day %}">{% trans 'Today' %}</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
{% extends "chrono/manager_agenda_day_view.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if not hours %}
|
||||
<div>
|
||||
<p>{% trans "No opening hours this day." %}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table class="agenda-table partial-bookings">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
{% for hour in hours %}
|
||||
<th>{{ hour|date:"TIME_FORMAT" }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for user, bookings in bookings_by_user.items %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<th>{{ bookings.0.get_user_block }}</th>
|
||||
{% for _ in hours %}
|
||||
<td class="hour-cell">
|
||||
{% if forloop.first %}
|
||||
{% for booking in bookings %}
|
||||
<div class="booking"
|
||||
style="left: {{ booking.css_left }}%; width: {{ booking.css_width }}%;"
|
||||
>{{ booking.start_time }} - {{ booking.end_time }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1370,6 +1370,8 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
def get_template_names(self):
|
||||
if self.agenda.kind == 'virtual':
|
||||
return ['chrono/manager_meetings_agenda_day_view.html']
|
||||
if self.agenda.partial_bookings:
|
||||
return ['chrono/manager_partial_bookings_day_view.html']
|
||||
return ['chrono/manager_%s_agenda_day_view.html' % self.agenda.kind]
|
||||
|
||||
def get_previous_day_url(self):
|
||||
|
@ -1487,6 +1489,37 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
current_date += interval
|
||||
first = False
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.agenda.partial_bookings:
|
||||
self.fill_partial_bookings_context(context)
|
||||
return context
|
||||
|
||||
def fill_partial_bookings_context(self, context):
|
||||
events = self.agenda.event_set.filter(start_datetime__date=self.date.date())
|
||||
if not events.exists():
|
||||
return
|
||||
|
||||
event_times = events.aggregate(Min('start_datetime'), Max('end_time'))
|
||||
min_time = localtime(event_times['start_datetime__min']).time()
|
||||
max_time = event_times['end_time__max']
|
||||
|
||||
start_time = datetime.time(min_time.hour - 1, 0)
|
||||
end_time = datetime.time(max_time.hour + 2, 0)
|
||||
context['hours'] = [datetime.time(hour=i) for i in range(start_time.hour, end_time.hour)]
|
||||
|
||||
def get_time_ratio(t1, t2):
|
||||
return 100 * ((t1.hour - t2.hour) * 60 + t1.minute - t2.minute) // 60
|
||||
|
||||
bookings = Booking.objects.filter(event__in=events)
|
||||
bookings_by_user = collections.defaultdict(list)
|
||||
for booking in bookings:
|
||||
booking.css_left = get_time_ratio(booking.start_time, start_time)
|
||||
booking.css_width = get_time_ratio(booking.end_time, booking.start_time)
|
||||
bookings_by_user[booking.user_external_id].append(booking)
|
||||
|
||||
context['bookings_by_user'] = dict(bookings_by_user)
|
||||
|
||||
|
||||
agenda_day_view = AgendaDayView.as_view()
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import datetime
|
|||
import pytest
|
||||
|
||||
from chrono.agendas.models import Agenda, Booking, Event
|
||||
from chrono.utils.timezone import make_aware
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
@ -23,6 +24,10 @@ def test_manager_partial_bookings_add_agenda(app, admin_user, settings):
|
|||
agenda = Agenda.objects.get(label='Foo bar')
|
||||
assert agenda.kind == 'events'
|
||||
assert agenda.partial_bookings is True
|
||||
assert agenda.default_view == 'day'
|
||||
|
||||
resp = resp.click('Options')
|
||||
assert 'default_view' not in resp.form.fields
|
||||
|
||||
|
||||
def test_manager_partial_bookings_add_event(app, admin_user):
|
||||
|
@ -47,10 +52,60 @@ def test_manager_partial_bookings_add_event(app, admin_user):
|
|||
assert 'duration' not in resp.form.fields
|
||||
assert resp.form['end_time'].value == '18:00'
|
||||
|
||||
resp.form['end_time'] = '08:01'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.pk, event.pk))
|
||||
resp.form['end_time'] = '07:59'
|
||||
resp = resp.form.submit()
|
||||
assert 'End time must be greater than start time.' in resp.text
|
||||
def test_manager_partial_bookings_day_view(app, admin_user, freezer):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
||||
start_datetime = make_aware(datetime.datetime(2023, 5, 2, 8, 0))
|
||||
event = Event.objects.create(
|
||||
label='Event', start_datetime=start_datetime, end_time=datetime.time(18, 00), places=10, agenda=agenda
|
||||
)
|
||||
Booking.objects.create(
|
||||
user_external_id='xxx',
|
||||
user_first_name='Jane',
|
||||
user_last_name='Doe',
|
||||
start_time=datetime.time(11, 00),
|
||||
end_time=datetime.time(13, 30),
|
||||
event=event,
|
||||
)
|
||||
Booking.objects.create(
|
||||
user_external_id='yyy',
|
||||
user_first_name='Jon',
|
||||
user_last_name='Doe',
|
||||
start_time=datetime.time(8, 00),
|
||||
end_time=datetime.time(10, 00),
|
||||
event=event,
|
||||
)
|
||||
Booking.objects.create(
|
||||
user_external_id='yyy',
|
||||
user_first_name='Jon',
|
||||
user_last_name='Doe',
|
||||
start_time=datetime.time(12, 00),
|
||||
end_time=datetime.time(14, 00),
|
||||
event=event,
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
today = start_datetime.date()
|
||||
resp = app.get('/manage/agendas/%s/day/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
||||
assert 'Week' not in resp.text
|
||||
assert 'Month' not in resp.text
|
||||
|
||||
# time range from one hour before event start to one hour after end
|
||||
assert (
|
||||
resp.pyquery('thead th').text()
|
||||
== '7 a.m. 8 a.m. 9 a.m. 10 a.m. 11 a.m. noon 1 p.m. 2 p.m. 3 p.m. 4 p.m. 5 p.m. 6 p.m. 7 p.m.'
|
||||
)
|
||||
|
||||
assert len(resp.pyquery('tbody tr')) == 2
|
||||
assert resp.pyquery('tbody tr th')[0].text == 'Jane Doe'
|
||||
assert resp.pyquery('tbody tr th')[1].text == 'Jon Doe'
|
||||
|
||||
assert resp.pyquery('tbody tr div.booking')[0].text == '11 a.m. - 1:30 p.m.'
|
||||
assert resp.pyquery('tbody tr div.booking')[0].attrib['style'] == 'left: 400%; width: 250%;'
|
||||
assert resp.pyquery('tbody tr div.booking')[1].text == '8 a.m. - 10 a.m.'
|
||||
assert resp.pyquery('tbody tr div.booking')[1].attrib['style'] == 'left: 100%; width: 200%;'
|
||||
assert resp.pyquery('tbody tr div.booking')[2].text == 'noon - 2 p.m.'
|
||||
assert resp.pyquery('tbody tr div.booking')[2].attrib['style'] == 'left: 500%; width: 200%;'
|
||||
|
||||
resp = resp.click('Next day')
|
||||
assert 'No opening hours this day.' in resp.text
|
||||
|
|
Loading…
Reference in New Issue