manager: export timesheet as CSV (#68513)
This commit is contained in:
parent
95ccebb3cb
commit
1c19d18801
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue