manager: timesheet & activity display configuration (#66681)

This commit is contained in:
Lauréline Guérin 2022-06-28 14:43:28 +02:00
parent c9d2a4681c
commit ff2706b4f1
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
5 changed files with 234 additions and 150 deletions

View File

@ -543,7 +543,7 @@ class EventsTimesheetForm(forms.Form):
initial='lastname,firstname',
)
date_display = forms.ChoiceField(
label=_('Display'),
label=_('Date display'),
choices=[
('all', _('All on the same page')),
('month', _('1 month per page')),
@ -556,6 +556,14 @@ class EventsTimesheetForm(forms.Form):
label=_('Number of dates per page'),
required=False,
)
activity_display = forms.ChoiceField(
label=_('Activity display'),
choices=[
('row', _('In line')),
('col', _('In column')),
],
initial='row',
)
orientation = forms.ChoiceField(
label=_('PDF orientation'),
choices=[
@ -572,6 +580,9 @@ class EventsTimesheetForm(forms.Form):
if self.event is not None:
del self.fields['date_start']
del self.fields['date_end']
del self.fields['date_display']
del self.fields['custom_nb_dates_per_page']
del self.fields['activity_display']
def get_slots(self):
extra_data = self.cleaned_data['extra_data'].split(',')
@ -604,27 +615,27 @@ class EventsTimesheetForm(forms.Form):
.select_related('primary_event')
.order_by('start_datetime', 'label')
)
dates = set()
dates = defaultdict(list)
events = []
dates_per_event_id = defaultdict(list)
for event in all_events:
date = localtime(event.start_datetime).date()
dates.add(date)
real_event = event.primary_event or event
dates[date].append(real_event)
if real_event not in events:
events.append(real_event)
dates_per_event_id[real_event.pk].append(date)
dates = sorted(dates)
dates = sorted(dates.items(), key=lambda a: a[0])
date_display = self.cleaned_data['date_display']
date_display = self.cleaned_data.get('date_display') or 'all'
if date_display in ['month', 'week']:
grouper = defaultdict(list)
for date in dates:
for date, event in dates:
if date_display == 'month':
attr = date.month
else:
attr = date.isocalendar().week
grouper[(date.year, attr)].append(date)
grouper[(date.year, attr)].append((date, event))
dates = [grouper[g] for g in sorted(grouper.keys())]
elif date_display == 'custom':
n = self.cleaned_data['custom_nb_dates_per_page']

View File

@ -494,6 +494,10 @@ table.timesheet {
width: 20px;
text-align: center;
}
&.date-event {
width: 60px;
text-align: center;
}
}
td {
padding: 0.5em 0.5ex;

View File

@ -25,6 +25,10 @@ table.timesheet {
width: 30px;
text-align: center;
}
&.date-event {
width: 60px;
text-align: center;
}
}
td {
border: 0.5px solid black;

View File

@ -11,24 +11,47 @@
<th>{% trans "First name" %}</th>
<th>{% trans "Last name" %}</th>
{% for k in slots.extra_data %}<th>{{ k }}</th>{% endfor %}
{% if events_num > 1 %}<th>{% trans "Activity" %}</th>{% endif %}
{% for date in dates %}<th class="date">{{ date|date:"D d/m" }}</th>{% endfor %}
{% if events_num > 1 and form.cleaned_data.activity_display != 'col' %}<th class="activity">{% trans "Activity" %}</th>{% endif %}
{% for date, events in dates %}
{% if form.cleaned_data.activity_display == 'col' %}
{% for event in events %}
<th class="date-event" data-id="{{ date|date:"d-m" }}:{{ event.slug }}">{% blocktrans with date=date|date:"d/m" %}{{ event }} of {{ date }}{% endblocktrans %}</th>
{% endfor %}
{% else %}
<th class="date" data-id="{{ date|date:"d-m" }}">{{ date|date:"D d/m" }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for user in grouper.users %}{% for event in user.events %}
<tr>
{% if forloop.first %}
<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_first_name }}</td>
<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_last_name }}</td>
{% for k in slots.extra_data %}<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.extra_data|get:k }}</td>{% endfor %}
{% endif %}
{% if events_num > 1 %}<td>{{ event.event }}</td>{% endif %}
{% for date in dates %}
{% with booked=event.dates|get:date %}<td class="date">{% if booked is True %}☐{% elif booked is None %}-{% endif %}</td>{% endwith %}
{% if form.cleaned_data.activity_display == 'col' %}
{% for user in grouper.users %}
<tr>
<td>{{ user.user_first_name }}</td>
<td>{{ user.user_last_name }}</td>
{% for k in slots.extra_data %}<td>{{ user.extra_data|get:k }}</td>{% endfor %}
{% for date, events in dates %}
{% for event in events %}
{% for item in user.events %}{% if item.event == event %}{% with booked=item.dates|get:date %}<td class="date" data-id="{{ date|date:"d-m" }}:{{ item.event.slug }}:{{ user.user_id }}">{% if booked is True %}☐{% elif booked is None %}-{% endif %}</td>{% endwith %}{% endif %}{% endfor %}
{% endfor %}
{% endfor %}
</tr>
{% endfor %}
</tr>
{% endfor %}{% endfor %}
{% else %}
{% for user in grouper.users %}{% for item in user.events %}
<tr>
{% if forloop.first %}
<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_first_name }}</td>
<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_last_name }}</td>
{% for k in slots.extra_data %}<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.extra_data|get:k }}</td>{% endfor %}
{% endif %}
{% if events_num > 1 %}<td class="activity">{{ item.event }}</td>{% endif %}
{% for date, events in dates %}
{% with booked=item.dates|get:date %}<td class="date" data-id="{{ date|date:"d-m" }}:{{ item.event.slug }}:{{ user.user_id }}">{% if booked is True %}☐{% elif booked is None %}-{% endif %}</td>{% endwith %}
{% endfor %}
</tr>
{% endfor %}{% endfor %}
{% endif %}
</tbody>
</table>
{% if form.cleaned_data.with_page_break %}{% if not forloop.last or not forloop.parentloop.last %}<div class="page_break"></div>{% endif %}{% endif %}

View File

@ -1,4 +1,5 @@
import datetime
import itertools
import pytest
from django.db import connection
@ -99,21 +100,20 @@ def test_events_timesheet_slots(app, admin_user):
assert len(ctx.captured_queries) == 7
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 2, 1),
datetime.date(2022, 2, 2),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 8),
datetime.date(2022, 2, 9),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 15),
datetime.date(2022, 2, 16),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 22),
datetime.date(2022, 2, 23),
datetime.date(2022, 2, 28),
]
assert len(slots['dates']) == 1
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 2, 1),
datetime.date(2022, 2, 2),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 8),
datetime.date(2022, 2, 9),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 15),
datetime.date(2022, 2, 16),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 22),
datetime.date(2022, 2, 23),
datetime.date(2022, 2, 28),
]
assert slots['events'] == [
event2,
@ -131,26 +131,32 @@ def test_events_timesheet_slots(app, admin_user):
'events': [
{
'event': event2,
'dates': {date: False for date in slots['dates'][0] if date == datetime.date(2022, 2, 1)},
'dates': {
date: False for date, events in slots['dates'][0] if date == datetime.date(2022, 2, 1)
},
},
{
'event': recurring_event1,
'dates': {date: False for date in slots['dates'][0] if date.weekday() in [0, 1]},
'dates': {date: False for date, events in slots['dates'][0] if date.weekday() in [0, 1]},
},
{
'event': recurring_event2,
'dates': {date: False for date in slots['dates'][0] if date.weekday() in [1, 2]},
'dates': {date: False for date, events in slots['dates'][0] if date.weekday() in [1, 2]},
},
{
'event': event3,
'dates': {
date: False for date in slots['dates'][0] if date == datetime.date(2022, 2, 15)
date: False
for date, events in slots['dates'][0]
if date == datetime.date(2022, 2, 15)
},
},
{
'event': event4,
'dates': {
date: False for date in slots['dates'][0] if date == datetime.date(2022, 2, 28)
date: False
for date, events in slots['dates'][0]
if date == datetime.date(2022, 2, 28)
},
},
],
@ -202,12 +208,11 @@ def test_events_timesheet_subscription_limits(app, admin_user):
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 2, 1),
datetime.date(2022, 2, 15),
datetime.date(2022, 2, 28),
]
assert len(slots['dates']) == 1
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 2, 1),
datetime.date(2022, 2, 15),
datetime.date(2022, 2, 28),
]
assert slots['events'] == [
@ -507,6 +512,45 @@ def test_events_timesheet_booked(app, admin_user):
},
]
# activity_display: row
assert len(resp.pyquery.find('th.activity')) == 1
assert resp.pyquery.find('th.activity')[0].text == 'Activity'
assert len(resp.pyquery.find('th.date')) == 1
assert len(resp.pyquery.find('th.date-event')) == 0
assert resp.pyquery.find('th.date[data-id="15-02"]')[0].text == 'Tue 15/02'
assert len(resp.pyquery.find('td.activity')) == 5
assert resp.pyquery.find('td.activity')[0].text == 'event 1'
assert resp.pyquery.find('td.activity')[1].text == 'event 2'
assert resp.pyquery.find('td.activity')[2].text == 'event 3'
assert resp.pyquery.find('td.activity')[3].text == 'recurring 1'
assert resp.pyquery.find('td.activity')[4].text == 'recurring 2'
assert len(resp.pyquery.find('td.date')) == 5
assert resp.pyquery.find('td.date[data-id="15-02:event-1:user:1"]')[0].text == ''
assert resp.pyquery.find('td.date[data-id="15-02:event-2:user:1"]')[0].text is None
assert resp.pyquery.find('td.date[data-id="15-02:event-3:user:1"]')[0].text is None
assert resp.pyquery.find('td.date[data-id="15-02:recurring-1:user:1"]')[0].text == ''
assert resp.pyquery.find('td.date[data-id="15-02:recurring-2:user:1"]')[0].text is None
# activity_display: col
resp.form['activity_display'] = 'col'
resp = resp.form.submit()
new_slots = resp.context['form'].get_slots()
assert new_slots == slots
assert len(resp.pyquery.find('th.activity')) == 0
assert len(resp.pyquery.find('th.date')) == 0
assert len(resp.pyquery.find('th.date-event')) == 5
assert resp.pyquery.find('th.date-event[data-id="15-02:event-1"]')[0].text == 'event 1 of 15/02'
assert resp.pyquery.find('th.date-event[data-id="15-02:event-2"]')[0].text == 'event 2 of 15/02'
assert resp.pyquery.find('th.date-event[data-id="15-02:event-3"]')[0].text == 'event 3 of 15/02'
assert resp.pyquery.find('th.date-event[data-id="15-02:recurring-1"]')[0].text == 'recurring 1 of 15/02'
assert resp.pyquery.find('th.date-event[data-id="15-02:recurring-2"]')[0].text == 'recurring 2 of 15/02'
assert len(resp.pyquery.find('td.date')) == 5
assert resp.pyquery.find('td.date[data-id="15-02:event-1:user:1"]')[0].text == ''
assert resp.pyquery.find('td.date[data-id="15-02:event-2:user:1"]')[0].text is None
assert resp.pyquery.find('td.date[data-id="15-02:event-3:user:1"]')[0].text is None
assert resp.pyquery.find('td.date[data-id="15-02:recurring-1:user:1"]')[0].text == ''
assert resp.pyquery.find('td.date[data-id="15-02:recurring-2:user:1"]')[0].text is None
def test_events_timesheet_extra_data(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
@ -732,48 +776,46 @@ def test_events_timesheet_date_display(app, admin_user):
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
datetime.date(2022, 3, 7),
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
datetime.date(2022, 3, 28),
]
assert len(slots['dates']) == 1
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
datetime.date(2022, 3, 7),
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
datetime.date(2022, 3, 28),
]
resp.form['date_display'] = 'month'
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
],
[
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
],
[
datetime.date(2022, 3, 7),
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
datetime.date(2022, 3, 28),
],
assert len(slots['dates']) == 3
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
]
assert [d[0] for d in slots['dates'][1]] == [
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
]
assert [d[0] for d in slots['dates'][2]] == [
datetime.date(2022, 3, 7),
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
datetime.date(2022, 3, 28),
]
resp.form['date_display'] = 'week'
@ -781,20 +823,21 @@ def test_events_timesheet_date_display(app, admin_user):
slots = resp.context['form'].get_slots()
assert resp.text.count('<div class="page_break">') == 12
assert slots['dates'] == [
[datetime.date(2022, 1, 3)],
[datetime.date(2022, 1, 10)],
[datetime.date(2022, 1, 17)],
[datetime.date(2022, 1, 24)],
[datetime.date(2022, 1, 31)],
[datetime.date(2022, 2, 7)],
[datetime.date(2022, 2, 14)],
[datetime.date(2022, 2, 21)],
[datetime.date(2022, 2, 28)],
[datetime.date(2022, 3, 7)],
[datetime.date(2022, 3, 14)],
[datetime.date(2022, 3, 21)],
[datetime.date(2022, 3, 28)],
assert len(slots['dates']) == 13
assert [d[0] for d in itertools.chain(*slots['dates'])] == [
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
datetime.date(2022, 3, 7),
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
datetime.date(2022, 3, 28),
]
resp.form['date_display'] = 'custom'
@ -805,54 +848,52 @@ def test_events_timesheet_date_display(app, admin_user):
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
datetime.date(2022, 3, 7),
],
[
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
datetime.date(2022, 3, 28),
],
assert len(slots['dates']) == 2
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
datetime.date(2022, 3, 7),
]
assert [d[0] for d in slots['dates'][1]] == [
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
datetime.date(2022, 3, 28),
]
resp.form['custom_nb_dates_per_page'] = 3
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
],
[
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
datetime.date(2022, 2, 7),
],
[
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
],
[
datetime.date(2022, 3, 7),
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
],
[
datetime.date(2022, 3, 28),
],
assert len(slots['dates']) == 5
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 1, 3),
datetime.date(2022, 1, 10),
datetime.date(2022, 1, 17),
]
assert [d[0] for d in slots['dates'][1]] == [
datetime.date(2022, 1, 24),
datetime.date(2022, 1, 31),
datetime.date(2022, 2, 7),
]
assert [d[0] for d in slots['dates'][2]] == [
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 28),
]
assert [d[0] for d in slots['dates'][3]] == [
datetime.date(2022, 3, 7),
datetime.date(2022, 3, 14),
datetime.date(2022, 3, 21),
]
assert [d[0] for d in slots['dates'][4]] == [
datetime.date(2022, 3, 28),
]
Subscription.objects.create(
@ -881,8 +922,8 @@ def test_events_timesheet_pdf(app, admin_user):
login(app)
resp = app.get(
'/manage/agendas/%s/events/timesheet?pdf=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname&date_display=all&orientation=portrait'
% agenda.pk
'/manage/agendas/%s/events/timesheet?pdf=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname'
'&date_display=all&activity_display=row&orientation=portrait' % agenda.pk
)
assert resp.headers['Content-Type'] == 'application/pdf'
assert (
@ -892,7 +933,7 @@ def test_events_timesheet_pdf(app, admin_user):
# form invalid
resp = app.get(
'/manage/agendas/%s/events/timesheet?pdf=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname&date_display=all'
'/manage/agendas/%s/events/timesheet?pdf=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname&date_display=all&activity_display=row'
% agenda.pk
)
assert resp.context['form'].errors['orientation'] == ['This field is required.']
@ -946,6 +987,9 @@ def test_event_timesheet_form(app, admin_user):
resp = app.get('/manage/agendas/%s/events/%s/timesheet' % (agenda.pk, event.pk))
assert 'date_start' not in resp.context['form'].fields
assert 'date_end' not in resp.context['form'].fields
assert 'date_display' not in resp.context['form'].fields
assert 'custom_nb_dates_per_page' not in resp.context['form'].fields
assert 'activity_display' not in resp.context['form'].fields
assert resp.context['form'].errors == {}
@ -970,10 +1014,9 @@ def test_event_timesheet_slots(app, admin_user):
assert len(ctx.captured_queries) == 7
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 2, 15),
]
assert len(slots['dates']) == 1
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 2, 15),
]
assert slots['events'] == [event]
assert slots['users'][0]['users'] == [
@ -1027,10 +1070,9 @@ def test_event_timesheet_subscription_limits(app, admin_user):
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
[
datetime.date(2022, 2, 15),
]
assert len(slots['dates']) == 1
assert [d[0] for d in slots['dates'][0]] == [
datetime.date(2022, 2, 15),
]
assert slots['events'] == [