diff --git a/hypha/apply/funds/templates/funds/includes/round-block-listing.html b/hypha/apply/funds/templates/funds/includes/round-block-listing.html index 322ad2d892ecbb7eaedaa153a731d90d6cd8d8bf..5fbd25c298386593ef5923aa81192f913929e35b 100644 --- a/hypha/apply/funds/templates/funds/includes/round-block-listing.html +++ b/hypha/apply/funds/templates/funds/includes/round-block-listing.html @@ -21,6 +21,7 @@ {% endif %} </p> <a class="round-block__view" href="{% url 'apply:rounds:detail' pk=round.pk %}">{% trans 'View' %}</a> + <a class="round-block__view" href="{% url 'apply:rounds:export' pk=round.pk %}">{% trans 'Export' %}</a> </li> {% else %} <li class="round-block__item round-block__item--more"> diff --git a/hypha/apply/funds/urls.py b/hypha/apply/funds/urls.py index 02962ba71d538b818f685a007df66860769a6a07..a20169547e197b1c4b6d2c48fccfe3bb90a8afb1 100644 --- a/hypha/apply/funds/urls.py +++ b/hypha/apply/funds/urls.py @@ -4,6 +4,7 @@ from hypha.apply.projects import urls as projects_urls from .views import ( AwaitingReviewSubmissionsListView, + ExportSubmissionsByRound, GroupingApplicationsListView, ReminderDeleteView, ReviewerLeaderboard, @@ -84,6 +85,7 @@ submission_urls = ([ rounds_urls = ([ path('', RoundListView.as_view(), name="list"), path('<int:pk>/', SubmissionsByRound.as_view(), name="detail"), + path('export/<int:pk>/', ExportSubmissionsByRound.as_view(), name="export"), ], 'rounds') diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py index dd13d77510d1ce0650a43174343bbcf8410cf286..9c756db9daa615a1bae0b99f6169f57843fa6bde 100644 --- a/hypha/apply/funds/views.py +++ b/hypha/apply/funds/views.py @@ -1,5 +1,7 @@ +import csv from copy import copy from datetime import timedelta +from io import StringIO import django_tables2 as tables from django.conf import settings @@ -10,7 +12,7 @@ from django.contrib.auth.mixins import UserPassesTestMixin from django.contrib.humanize.templatetags.humanize import intcomma from django.core.exceptions import PermissionDenied from django.db.models import Count, F, Q -from django.http import FileResponse, Http404, HttpResponseRedirect +from django.http import FileResponse, Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.utils import timezone @@ -561,6 +563,56 @@ class SubmissionUserFlaggedView(UserPassesTestMixin, BaseAdminSubmissionsTable): def test_func(self): return self.request.user.is_apply_staff or self.request.user.is_reviewer +@method_decorator(staff_required, name='dispatch') +class ExportSubmissionsByRound(BaseAdminSubmissionsTable): + + def export_submissions(self, round_id): + csv_stream = StringIO() + writer = csv.writer(csv_stream) + header_row,values = [],[] + index = 0 + check = False + + for submission in ApplicationSubmission.objects.filter(round=round_id): + for field_id in submission.question_text_field_ids: + question_field = submission.serialize(field_id) + field_name = question_field['question'] + field_value = question_field['answer'] + if field_id not in submission.named_blocks: + header_row.append(field_name) if not check else header_row + values.append(field_value) + else: + header_row.insert(index,field_name) if not check else header_row + values.insert(index,field_value) + index = index + 1 + + if not check: + writer.writerow(header_row) + check = True + + writer.writerow(values) + values.clear() + index = 0 + + csv_stream.seek(0) + return csv_stream + + def get_queryset(self): + try: + self.obj = Page.objects.get(pk=self.kwargs.get('pk')).specific + except Page.DoesNotExist as exc: + raise Http404(_("No Round or Lab found matching the query")) from exc + + if not isinstance(self.obj, (LabBase, RoundBase)): + raise Http404(_("No Round or Lab found matching the query")) + return super().get_queryset().filter(Q(round=self.obj) | Q(page=self.obj)) + + def get(self, request, pk): + self.get_queryset() + csv_data = self.export_submissions(pk) + response = HttpResponse(csv_data.readlines(), content_type="text/csv") + response['Content-Disposition'] = 'inline; filename=' + str(self.obj) + '.csv' + return response @method_decorator(staff_required, name='dispatch') class SubmissionsByRound(BaseAdminSubmissionsTable, DelegateableListView):