manager: export timesheet as CSV (#68513)

This commit is contained in:
Lauréline Guérin 2022-08-29 22:56:09 +02:00
parent 95ccebb3cb
commit 1c19d18801
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
3 changed files with 300 additions and 11 deletions

View File

@ -28,6 +28,7 @@
<button class="submit-button">{% trans "See timesheet" %}</button>
{% if request.GET and form.is_valid %}
<button class="submit-button" name="pdf">{% trans "Get PDF file" %}</button>
<button class="submit-button" name="csv">{% trans "Get CSV file" %}</button>
{% endif %}
</form>

View File

@ -40,6 +40,7 @@ from django.urls import reverse, reverse_lazy
from django.utils import lorem_ipsum
from django.utils.dates import MONTHS
from django.utils.encoding import force_text
from django.utils.formats import date_format
from django.utils.html import format_html
from django.utils.timezone import localtime, make_aware, make_naive, now
from django.utils.translation import ugettext_lazy as _
@ -2084,8 +2085,23 @@ class EventsTimesheetView(ViewableAgendaMixin, DetailView):
context = self.get_context_data(object=self.object)
if 'pdf' in request.GET and context['form'].is_valid():
return self.pdf(request, context)
if 'csv' in request.GET and context['form'].is_valid():
return self.csv(request, context)
return self.render_to_response(context)
def get_export_filename(self, context):
if self.event is not None:
return 'timesheet_{}_{}'.format(
self.agenda.slug,
self.event.slug,
)
else:
return 'timesheet_{}_{}_{}'.format(
self.agenda.slug,
context['form'].cleaned_data['date_start'].strftime('%Y-%m-%d'),
context['form'].cleaned_data['date_end'].strftime('%Y-%m-%d'),
)
def pdf(self, request, context):
context['base_uri'] = request.build_absolute_uri('/')
html = HTML(
@ -2093,17 +2109,84 @@ class EventsTimesheetView(ViewableAgendaMixin, DetailView):
)
pdf = html.write_pdf()
response = HttpResponse(pdf, content_type='application/pdf')
if self.event is not None:
response['Content-Disposition'] = 'attachment; filename="timesheet_{}_{}.pdf"'.format(
self.agenda.slug,
self.event.slug,
)
else:
response['Content-Disposition'] = 'attachment; filename="timesheet_{}_{}_{}.pdf"'.format(
self.agenda.slug,
context['form'].cleaned_data['date_start'].strftime('%Y-%m-%d'),
context['form'].cleaned_data['date_end'].strftime('%Y-%m-%d'),
)
response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % self.get_export_filename(context)
return response
def csv(self, request, context):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s.csv"' % self.get_export_filename(context)
writer = csv.writer(response)
form = context['form']
slots = form.get_slots()
events_num = len(slots['events'])
activity_display = form.cleaned_data.get('activity_display')
def get_booked(item, date):
booked = item['dates'].get(date)
if booked is True:
return ''
if booked is None:
return '-'
return ''
def write_group(dates, grouper):
# title
if form.cleaned_data['group_by']:
writer.writerow(['%s: %s' % (form.cleaned_data['group_by'], grouper['grouper'])])
# headers
headers = [
_('First name'),
_('Last name'),
]
headers += slots['extra_data']
if activity_display != 'col' and events_num > 1:
headers.append(_('Activity'))
for date, events in dates:
if activity_display == 'col':
for event in events:
headers.append(
_('%(event)s of %(date)s') % {'event': event, 'date': date_format(date, 'd-m')}
)
else:
headers.append(date_format(date, 'D d/m'))
writer.writerow(headers)
# and data
for user in grouper['users']:
if activity_display == 'col':
data = [
user['user_first_name'],
user['user_last_name'],
]
data += [user['extra_data'].get(k) or '' for k in slots['extra_data']]
for date, events in dates:
for event in events:
for item in user['events']:
if item['event'] != event:
continue
data.append(get_booked(item, date))
break
writer.writerow(data)
else:
for i, item in enumerate(user['events']):
if i == 0:
data = [
user['user_first_name'],
user['user_last_name'],
]
data += [user['extra_data'].get(k) or '' for k in slots['extra_data']]
else:
data = ['', '']
data += ['' for k in slots['extra_data']]
if events_num > 1:
data.append(item['event'])
for date, events in dates:
data.append(get_booked(item, date))
writer.writerow(data)
for dates in slots['dates']:
for grouper in slots['users']:
write_group(dates, grouper)
return response

View File

@ -975,6 +975,145 @@ def test_events_timesheet_pdf(app, admin_user):
assert resp.context['form'].errors['orientation'] == ['This field is required.']
def test_events_timesheet_csv(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
login(app)
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&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'] == 'text/csv'
assert (
resp.headers['Content-Disposition']
== 'attachment; filename="timesheet_events_2022-02-01_2022-02-28.csv"'
)
# form invalid
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&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.']
# only one event
recurring_event = Event.objects.create(
label='recurring 1',
start_datetime=make_aware(datetime.datetime(2022, 2, 1, 12, 0)),
places=10,
agenda=agenda,
recurrence_days=[0],
recurrence_end_date=datetime.date(2022, 4, 1),
)
recurring_event.create_all_recurrences()
recurring_event_occurence = recurring_event.recurrences.first()
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='01',
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2022, 4, 1),
extra_data={'foo': 'bar'},
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:2',
user_first_name='Subscription',
user_last_name='02',
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2022, 4, 1),
extra_data={'foo': 'bar'},
)
Booking.objects.create(
event=recurring_event_occurence,
user_external_id='user:2',
user_first_name='User',
user_last_name='02',
)
# display row
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&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.text == (
'First name,Last name,Mon 07/02,Mon 14/02,Mon 21/02,Mon 28/02\r\n'
'Subscription,01,,,,\r\n'
'Subscription,02,☐,,,\r\n'
)
# display col
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname'
'&date_display=all&activity_display=col&orientation=portrait' % agenda.pk
)
assert resp.text == (
'First name,Last name,recurring 1 of 07-02,recurring 1 of 14-02,recurring 1 of 21-02,recurring 1 of 28-02\r\n'
'Subscription,01,,,,\r\n'
'Subscription,02,☐,,,\r\n'
)
# more than one event
event = Event.objects.create(
start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)),
places=10,
agenda=agenda,
)
Booking.objects.create(
event=event,
user_external_id='user:1',
user_first_name='User',
user_last_name='01',
)
# display row
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&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.text == (
'First name,Last name,Activity,Mon 07/02,Mon 14/02,Tue 15/02,Mon 21/02,Mon 28/02\r\n'
'Subscription,01,recurring 1,,,-,,\r\n'
',,"Feb. 15, 2022, 5 p.m.",-,-,☐,-,-\r\n'
'Subscription,02,recurring 1,☐,,-,,\r\n'
',,"Feb. 15, 2022, 5 p.m.",-,-,,-,-\r\n'
)
# display col
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname'
'&date_display=all&activity_display=col&orientation=portrait' % agenda.pk
)
assert resp.text == (
'First name,Last name,recurring 1 of 07-02,recurring 1 of 14-02,"Feb. 15, 2022, 5 p.m. of 15-02",recurring 1 of 21-02,recurring 1 of 28-02\r\n'
'Subscription,01,,,☐,,\r\n'
'Subscription,02,☐,,,,\r\n'
)
# with extra_data and grouper
# display row
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname'
'&date_display=all&activity_display=row&orientation=portrait&extra_data=foo&group_by=foo' % agenda.pk
)
assert resp.text == (
'foo: bar\r\n'
'First name,Last name,foo,Activity,Mon 07/02,Mon 14/02,Tue 15/02,Mon 21/02,Mon 28/02\r\n'
'Subscription,01,bar,recurring 1,,,-,,\r\n'
',,,"Feb. 15, 2022, 5 p.m.",-,-,☐,-,-\r\n'
'Subscription,02,bar,recurring 1,☐,,-,,\r\n'
',,,"Feb. 15, 2022, 5 p.m.",-,-,,-,-\r\n'
)
# display col
resp = app.get(
'/manage/agendas/%s/events/timesheet?csv=&date_start=2022-02-01&date_end=2022-02-28&sort=lastname,firstname'
'&date_display=all&activity_display=col&orientation=portrait&extra_data=foo&group_by=foo' % agenda.pk
)
assert resp.text == (
'foo: bar\r\n'
'First name,Last name,foo,recurring 1 of 07-02,recurring 1 of 14-02,"Feb. 15, 2022, 5 p.m. of 15-02",recurring 1 of 21-02,recurring 1 of 28-02\r\n'
'Subscription,01,bar,,,☐,,\r\n'
'Subscription,02,bar,☐,,,,\r\n'
)
def test_event_timesheet_wrong_kind(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
event = Event.objects.create(
@ -1150,3 +1289,69 @@ def test_event_timesheet_pdf(app, admin_user):
% (agenda.pk, event.pk)
)
assert resp.context['form'].errors['orientation'] == ['This field is required.']
def test_event_timesheet_csv(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
event = Event.objects.create(
label='Bar',
start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)),
places=10,
agenda=agenda,
)
login(app)
resp = app.get(
'/manage/agendas/%s/events/%s/timesheet?csv=&sort=lastname,firstname&date_display=all&orientation=portrait'
% (agenda.pk, event.pk)
)
assert resp.headers['Content-Type'] == 'text/csv'
assert resp.headers['Content-Disposition'] == 'attachment; filename="timesheet_events_bar.csv"'
# form invalid
resp = app.get(
'/manage/agendas/%s/events/%s/timesheet?csv=&sort=lastname,firstname&date_display=all'
% (agenda.pk, event.pk)
)
assert resp.context['form'].errors['orientation'] == ['This field is required.']
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='01',
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2022, 4, 1),
extra_data={'foo': 'bar'},
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:2',
user_first_name='Subscription',
user_last_name='02',
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2022, 4, 1),
extra_data={'foo': 'bar'},
)
Booking.objects.create(
event=event,
user_external_id='user:2',
user_first_name='User',
user_last_name='02',
)
resp = app.get(
'/manage/agendas/%s/events/%s/timesheet?csv=&sort=lastname,firstname&date_display=all&orientation=portrait'
% (agenda.pk, event.pk)
)
assert resp.text == ('First name,Last name,Tue 15/02\r\n' 'Subscription,01,\r\n' 'Subscription,02,☐\r\n')
resp = app.get(
'/manage/agendas/%s/events/%s/timesheet?csv=&sort=lastname,firstname&date_display=all&orientation=portrait&extra_data=foo&group_by=foo'
% (agenda.pk, event.pk)
)
assert resp.text == (
'foo: bar\r\n'
'First name,Last name,foo,Tue 15/02\r\n'
'Subscription,01,bar,\r\n'
'Subscription,02,bar,☐\r\n'
)