Skip to content

Commit

Permalink
add a download link to council right of reply section page
Browse files Browse the repository at this point in the history
This allows a council to download their Right of Reply response as a CSV
so they can refer to it later.

Fixes ³77
  • Loading branch information
struan committed Oct 16, 2024
1 parent 1252c73 commit 60372fa
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 4 deletions.
5 changes: 5 additions & 0 deletions ceuk-marking/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@
rightofreply.AuthorityRORSectionQuestions.as_view(),
name="authority_ror",
),
path(
"authorities/<name>/ror/download/",
rightofreply.AuthorityRORCSVView.as_view(),
name="authority_ror_download",
),
path(
"authority_ror_authorities/",
rightofreply.AuthorityRORList.as_view(),
Expand Down
48 changes: 44 additions & 4 deletions crowdsourcer/fixtures/ror_responses.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"option": 181,
"response_type": 2,
"public_notes": "",
"page_number": "0",
"page_number": "",
"evidence": "",
"private_notes": "",
"agree_with_response": true,
Expand All @@ -29,9 +29,9 @@
"user": 3,
"option": 191,
"response_type": 2,
"public_notes": "",
"page_number": "0",
"evidence": "",
"public_notes": "http://example.org/",
"page_number": "20",
"evidence": "We do not agree for reasons",
"private_notes": "a council objection",
"agree_with_response": false,
"revision_type": null,
Expand All @@ -40,5 +40,45 @@
"last_update": "2023-03-15T17:22:10+0000",
"multi_option": []
}
},
{
"model": "crowdsourcer.response",
"pk": 101,
"fields": {
"authority": 2,
"question": 272,
"user": 2,
"option": 181,
"response_type": 1,
"public_notes": "a public note",
"page_number": "0",
"evidence": "",
"private_notes": "a private note",
"revision_type": null,
"revision_notes": null,
"created": "2023-03-15T17:22:10+0000",
"last_update": "2023-03-15T17:22:10+0000",
"multi_option": []
}
},
{
"model": "crowdsourcer.response",
"pk": 102,
"fields": {
"authority": 2,
"question": 273,
"user": 2,
"option": 6,
"response_type": 1,
"public_notes": "a public note",
"page_number": "0",
"evidence": "",
"private_notes": "a private note",
"revision_type": null,
"revision_notes": null,
"created": "2023-03-15T17:22:10+0000",
"last_update": "2023-03-15T17:22:10+0000",
"multi_option": []
}
}
]
13 changes: 13 additions & 0 deletions crowdsourcer/templates/crowdsourcer/authority_section_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,18 @@ <h1 class="mb-4">Sections</h1>
{% endfor %}
</tbody>
</table>

{% if marking_session.label == "Scorecards 2025" %}
<div class="my-5" style="max-width: 40rem">
<h2 class="mb-3 h4">Download your Right of Reply response</h2>
<p>You can download a CSV spreadsheet of the Right of Reply responses you have provided for the 2025 Council Climate Action Scorecards.</p>
<p>Please keep these responses private, this is for your own council’s internal use only.</p>
<a href="{% session_url 'authority_ror_download' authority_name %}" class="btn btn-primary d-inline-flex align-items-center">
{% include 'crowdsourcer/icons/download.svg' with classes="me-2" %}
Download responses CSV
</a>
</div>
{% endif %}

{% endif %}
{% endblock %}
41 changes: 41 additions & 0 deletions crowdsourcer/tests/test_right_of_reply_views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import io

from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse

import pandas as pd

from crowdsourcer.models import (
Assigned,
MarkingSession,
Expand Down Expand Up @@ -554,3 +558,40 @@ def test_view_other_session(self):
progress = response.context["progress"]

self.assertEqual(len(progress.keys()), 2)


class TestCSVDownloadView(BaseTestCase):
def test_download(self):
url = reverse("authority_ror_download", args=("Aberdeenshire Council",))
response = self.client.get(url)
self.assertEqual(response.status_code, 200)

content = response.content.decode("utf-8")
# the dtype bit stops pandas doing annoying conversions and ending up
# with page numers as floats etc
df = pd.read_csv(io.StringIO(content), dtype="object")
# avoid nan results
df = df.fillna("")

self.assertEqual(df.shape[0], 2)
b_and_h_q4 = df.iloc[0]
b_and_h_q5 = df.iloc[1]

self.assertEqual(b_and_h_q4.question_no, "4")
self.assertEqual(
b_and_h_q4.first_mark_response,
"The council has completed an exercise to measure how much, approximately, it will cost them to retrofit all homes (to EPC C or higher, or equivalent) and there is a target date of 2030.",
)
self.assertEqual(b_and_h_q4.agree_with_mark, "Yes")
self.assertEqual(b_and_h_q4.council_page_number, "")
self.assertEqual(b_and_h_q4.council_evidence, "")

self.assertEqual(b_and_h_q5.question_no, "5")
self.assertEqual(
b_and_h_q5.first_mark_response,
"The council convenes or is a member of a local retrofit partnership",
)
self.assertEqual(b_and_h_q5.council_evidence, "http://example.org/")
self.assertEqual(b_and_h_q5.agree_with_mark, "No")
self.assertEqual(b_and_h_q5.council_page_number, "20")
self.assertEqual(b_and_h_q5.council_notes, "We do not agree for reasons")
120 changes: 120 additions & 0 deletions crowdsourcer/views/rightofreply.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import csv
import logging
from collections import defaultdict

from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic import ListView
Expand Down Expand Up @@ -205,3 +208,120 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["ror_user"] = True
return context


class AuthorityRORCSVView(ListView):
context_object_name = "responses"

def get_queryset(self):
user = self.request.user

rt = ResponseType.objects.get(type="Right of Reply")
if user.is_superuser:
authority_name = self.kwargs["name"]
authority = PublicAuthority.objects.get(name=authority_name)
else:
authority = self.request.user.marker.authority

self.authority = authority

if authority is not None:
return (
Response.objects.filter(
question__section__marking_session=self.request.current_session,
response_type=rt,
authority=authority,
)
.select_related("question", "question__section")
.order_by(
"question__section__title",
"question__number",
"question__number_part",
)
)

return None

def get_first_mark_responses(self):
rt = ResponseType.objects.get(type="First Mark")
responses = (
Response.objects.filter(
question__section__marking_session=self.request.current_session,
response_type=rt,
authority=self.authority,
)
.select_related("question", "question__section")
.order_by(
"question__section__title",
"question__number",
"question__number_part",
)
)

by_section = defaultdict(dict)

for r in responses:
by_section[r.question.section.title][
r.question.number_and_part
] = r.option.description

return by_section

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
rows = []
rows.append(
[
"section",
"question_no",
"question",
"first_mark_response",
"agree_with_mark",
"council_response",
"council_evidence",
"council_page_number",
"council_notes",
]
)

first_mark_responses = self.get_first_mark_responses()

for response in context["responses"]:
first_mark_response = ""
if first_mark_responses.get(
response.question.section.title
) and first_mark_responses[response.question.section.title].get(
response.question.number_and_part
):
first_mark_response = first_mark_responses[
response.question.section.title
][response.question.number_and_part]
rows.append(
[
response.question.section.title,
response.question.number_and_part,
response.question.description,
first_mark_response,
"Yes" if response.agree_with_response else "No",
response.option,
",".join(response.evidence_links),
response.page_number,
response.evidence,
]
)

context["authority"] = self.authority.name
context["rows"] = rows

return context

def render_to_response(self, context, **response_kwargs):
filename = f"{self.request.current_session.label}_{context['authority']}_Right_of_Reply.csv"
response = HttpResponse(
content_type="text/csv",
headers={"Content-Disposition": 'attachment; filename="' + filename + '"'},
)
writer = csv.writer(response)
for row in context["rows"]:
writer.writerow(row)
return response

0 comments on commit 60372fa

Please sign in to comment.