diff --git a/hypha/apply/funds/templates/funds/base_submissions_table.html b/hypha/apply/funds/templates/funds/base_submissions_table.html index 33b9669cf4de67e728095c83fab48e76c8ea51d2..fddfd0f064d3c4c13c30c57cf159cc1521716d0b 100644 --- a/hypha/apply/funds/templates/funds/base_submissions_table.html +++ b/hypha/apply/funds/templates/funds/base_submissions_table.html @@ -9,7 +9,7 @@ {% block content %} {% block table %} - {% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_search=True filter_action=filter_action use_batch_actions=True filter_classes="filters-open" %} + {% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_search=True filter_action=filter_action use_batch_actions=True filter_classes="filters-open" can_export=can_export %} {% render_table table %} {% endblock %} diff --git a/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html b/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html index 91e5f9f58627db4ac7a16b721bf3b69550b508ce..9d229b7fd4c85435e92acf00511e8823b9598826 100644 --- a/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html +++ b/hypha/apply/funds/templates/funds/includes/table_filter_and_search.html @@ -66,6 +66,11 @@ <a href="{% modify_query archived='1' %}">{% trans "Show archived" %}</a> {% endif %} {% endif %} + {% with request.get_full_path as path %} + {% if 'submission' in path and can_export %} + <a href="{{path}}{% if '?' in path %}&{%else%}?{% endif %}export=true" class="button button--primary">{% trans 'Export CSV' %}</a> + {% endif %} + {% endwith %} {% if filter_classes != 'filters-open' %} <button class="button js-toggle-filters"> {% heroicon_mini "adjustments-horizontal" class="inline me-1 text-dark-blue align-text-bottom" aria_hidden=true %} diff --git a/hypha/apply/funds/templates/funds/rounds.html b/hypha/apply/funds/templates/funds/rounds.html index 426c74a17cd7d7e2561174e8fb8b9ba4d6b107c5..9712508e5e0453e0791e3e0de7df6200a3f05a89 100644 --- a/hypha/apply/funds/templates/funds/rounds.html +++ b/hypha/apply/funds/templates/funds/rounds.html @@ -17,7 +17,7 @@ {% endadminbar %} <div class="wrapper wrapper--large wrapper--inner-space-medium"> - {% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_batch_actions=False %} + {% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_batch_actions=False can_export=can_export %} {% render_table table %} </div> diff --git a/hypha/apply/funds/templates/funds/submissions.html b/hypha/apply/funds/templates/funds/submissions.html index 8c7dc6bfe7a5454309b9dd1f0a01695db9223966..945b02c92ef0076dd18e04bcd690c139a9eeddfc 100644 --- a/hypha/apply/funds/templates/funds/submissions.html +++ b/hypha/apply/funds/templates/funds/submissions.html @@ -34,7 +34,7 @@ {% endif %} {% block table %} - {% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_search=True filter_action=filter_action use_batch_actions=True filter_classes="filters-open" show_archive=show_archive %} + {% include "funds/includes/table_filter_and_search.html" with filter_form=filter_form search_term=search_term use_search=True filter_action=filter_action use_batch_actions=True filter_classes="filters-open" show_archive=show_archive can_export=can_export %} {% render_table table %} {% endblock %} diff --git a/hypha/apply/funds/utils.py b/hypha/apply/funds/utils.py index a23658cac9f916d4ee3f778c2a6e166f9df42725..195264af14a66ab4996d5f522977bf6c93512f6a 100644 --- a/hypha/apply/funds/utils.py +++ b/hypha/apply/funds/utils.py @@ -1,5 +1,9 @@ +import csv +from io import StringIO from itertools import chain +from django.utils.html import strip_tags + from hypha.apply.utils.image import generate_image_tag from .models.screening import ScreeningStatus @@ -92,3 +96,31 @@ def get_statuses_as_params(statuses): for status in statuses: params += "status=" + status + "&" return params + + +def export_submissions_to_csv(submissions_list): + csv_stream = StringIO() + header_row = ["Application #"] + index = 1 + data_list = [] + for submission in submissions_list: + values = {} + values["Application #"] = submission.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_name not in header_row: + if field_id not in submission.named_blocks: + header_row.append(field_name) + else: + header_row.insert(index, field_name) + index = index + 1 + values[field_name] = strip_tags(field_value) + data_list.append(values) + writer = csv.DictWriter(csv_stream, fieldnames=header_row, restval="") + writer.writeheader() + for data in data_list: + writer.writerow(data) + csv_stream.seek(0) + return csv_stream diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py index 0c70f9b7f0b295802d149082b6422088a8143f34..01295cd503e8efbee414f7859e624dc009b1c77e 100644 --- a/hypha/apply/funds/views.py +++ b/hypha/apply/funds/views.py @@ -1,7 +1,5 @@ -import csv from copy import copy from datetime import timedelta -from io import StringIO from typing import Generator, Tuple import django_tables2 as tables @@ -127,6 +125,7 @@ from .tables import ( SummarySubmissionsTable, ) from .utils import ( + export_submissions_to_csv, get_or_create_default_screening_statuses, ) from .workflow import ( @@ -223,9 +222,19 @@ class BaseAdminSubmissionsTable(SingleTableMixin, FilterView): search_term=search_term, search_action=self.search_action, filter_action=self.filter_action, + can_export=can_export_submissions(self.request.user), **kwargs, ) + def dispatch(self, request, *args, **kwargs): + disp = super().dispatch(request, *args, **kwargs) + if "export" in request.GET and can_export_submissions(request.user): + csv_data = export_submissions_to_csv(self.object_list) + response = HttpResponse(csv_data.readlines(), content_type="text/csv") + response["Content-Disposition"] = "attachment; filename=submissions.csv" + return response + return disp + @method_decorator(staff_required, name="dispatch") class BatchUpdateLeadView(DelegatedViewMixin, FormView): @@ -572,37 +581,6 @@ class SubmissionListView(ViewDispatcher): @method_decorator(login_required, name="dispatch") class ExportSubmissionsByRound(UserPassesTestMixin, 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 @@ -615,9 +593,13 @@ class ExportSubmissionsByRound(UserPassesTestMixin, BaseAdminSubmissionsTable): def get(self, request, pk): self.get_queryset() - csv_data = self.export_submissions(pk) + csv_data = export_submissions_to_csv( + ApplicationSubmission.objects.filter(round=pk) + ) response = HttpResponse(csv_data.readlines(), content_type="text/csv") - response["Content-Disposition"] = "inline; filename=" + str(self.obj) + ".csv" + response["Content-Disposition"] = ( + "attachment; filename=" + str(self.obj) + ".csv" + ) return response def test_func(self):