diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py
index 5a7d36db..c6d9cff5 100644
--- a/chrono/manager/forms.py
+++ b/chrono/manager/forms.py
@@ -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']
diff --git a/chrono/manager/static/css/style.scss b/chrono/manager/static/css/style.scss
index b4f10d88..4250bebd 100644
--- a/chrono/manager/static/css/style.scss
+++ b/chrono/manager/static/css/style.scss
@@ -494,6 +494,10 @@ table.timesheet {
width: 20px;
text-align: center;
}
+ &.date-event {
+ width: 60px;
+ text-align: center;
+ }
}
td {
padding: 0.5em 0.5ex;
diff --git a/chrono/manager/static/css/timesheet.scss b/chrono/manager/static/css/timesheet.scss
index 819c9088..da6669fd 100644
--- a/chrono/manager/static/css/timesheet.scss
+++ b/chrono/manager/static/css/timesheet.scss
@@ -25,6 +25,10 @@ table.timesheet {
width: 30px;
text-align: center;
}
+ &.date-event {
+ width: 60px;
+ text-align: center;
+ }
}
td {
border: 0.5px solid black;
diff --git a/chrono/manager/templates/chrono/manager_events_timesheet_fragment.html b/chrono/manager/templates/chrono/manager_events_timesheet_fragment.html
index b1a2238a..da15bbd8 100644
--- a/chrono/manager/templates/chrono/manager_events_timesheet_fragment.html
+++ b/chrono/manager/templates/chrono/manager_events_timesheet_fragment.html
@@ -11,24 +11,47 @@
{% trans "First name" %} |
{% trans "Last name" %} |
{% for k in slots.extra_data %}{{ k }} | {% endfor %}
- {% if events_num > 1 %}{% trans "Activity" %} | {% endif %}
- {% for date in dates %}{{ date|date:"D d/m" }} | {% endfor %}
+ {% if events_num > 1 and form.cleaned_data.activity_display != 'col' %}{% trans "Activity" %} | {% endif %}
+ {% for date, events in dates %}
+ {% if form.cleaned_data.activity_display == 'col' %}
+ {% for event in events %}
+ {% blocktrans with date=date|date:"d/m" %}{{ event }} of {{ date }}{% endblocktrans %} |
+ {% endfor %}
+ {% else %}
+ {{ date|date:"D d/m" }} |
+ {% endif %}
+ {% endfor %}
- {% for user in grouper.users %}{% for event in user.events %}
-
- {% if forloop.first %}
- 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_first_name }} |
- 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_last_name }} |
- {% for k in slots.extra_data %} 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.extra_data|get:k }} | {% endfor %}
- {% endif %}
- {% if events_num > 1 %}{{ event.event }} | {% endif %}
- {% for date in dates %}
- {% with booked=event.dates|get:date %}{% if booked is True %}☐{% elif booked is None %}-{% endif %} | {% endwith %}
+ {% if form.cleaned_data.activity_display == 'col' %}
+ {% for user in grouper.users %}
+
+ {{ user.user_first_name }} |
+ {{ user.user_last_name }} |
+ {% for k in slots.extra_data %}{{ user.extra_data|get:k }} | {% 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 %}{% if booked is True %}☐{% elif booked is None %}-{% endif %} | {% endwith %}{% endif %}{% endfor %}
+ {% endfor %}
+ {% endfor %}
+
{% endfor %}
-
- {% endfor %}{% endfor %}
+ {% else %}
+ {% for user in grouper.users %}{% for item in user.events %}
+
+ {% if forloop.first %}
+ 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_first_name }} |
+ 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_last_name }} |
+ {% for k in slots.extra_data %} 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.extra_data|get:k }} | {% endfor %}
+ {% endif %}
+ {% if events_num > 1 %}{{ item.event }} | {% endif %}
+ {% for date, events in dates %}
+ {% with booked=item.dates|get:date %}{% if booked is True %}☐{% elif booked is None %}-{% endif %} | {% endwith %}
+ {% endfor %}
+
+ {% endfor %}{% endfor %}
+ {% endif %}
{% if form.cleaned_data.with_page_break %}{% if not forloop.last or not forloop.parentloop.last %}{% endif %}{% endif %}
diff --git a/tests/manager/test_event_timesheet.py b/tests/manager/test_event_timesheet.py
index eb82737d..5e3daf2f 100644
--- a/tests/manager/test_event_timesheet.py
+++ b/tests/manager/test_event_timesheet.py
@@ -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('') == 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'] == [