Skip to content

Commit d3fd594

Browse files
committed
add a download link to council right of reply section page
This allows a council to download their Right of Reply response as a CSV so they can refer to it later. Fixes ³77
1 parent 96f95eb commit d3fd594

File tree

5 files changed

+212
-4
lines changed

5 files changed

+212
-4
lines changed

ceuk-marking/urls.py

+5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@
8888
rightofreply.AuthorityRORSectionQuestions.as_view(),
8989
name="authority_ror",
9090
),
91+
path(
92+
"authorities/<name>/ror/download/",
93+
rightofreply.AuthorityRORCSVView.as_view(),
94+
name="authority_ror_download",
95+
),
9196
path(
9297
"authority_ror_authorities/",
9398
rightofreply.AuthorityRORList.as_view(),

crowdsourcer/fixtures/ror_responses.json

+44-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"option": 181,
1010
"response_type": 2,
1111
"public_notes": "",
12-
"page_number": "0",
12+
"page_number": "",
1313
"evidence": "",
1414
"private_notes": "",
1515
"agree_with_response": true,
@@ -29,9 +29,9 @@
2929
"user": 3,
3030
"option": 191,
3131
"response_type": 2,
32-
"public_notes": "",
33-
"page_number": "0",
34-
"evidence": "",
32+
"public_notes": "http://example.org/",
33+
"page_number": "20",
34+
"evidence": "We do not agree for reasons",
3535
"private_notes": "a council objection",
3636
"agree_with_response": false,
3737
"revision_type": null,
@@ -40,5 +40,45 @@
4040
"last_update": "2023-03-15T17:22:10+0000",
4141
"multi_option": []
4242
}
43+
},
44+
{
45+
"model": "crowdsourcer.response",
46+
"pk": 101,
47+
"fields": {
48+
"authority": 2,
49+
"question": 272,
50+
"user": 2,
51+
"option": 181,
52+
"response_type": 1,
53+
"public_notes": "a public note",
54+
"page_number": "0",
55+
"evidence": "",
56+
"private_notes": "a private note",
57+
"revision_type": null,
58+
"revision_notes": null,
59+
"created": "2023-03-15T17:22:10+0000",
60+
"last_update": "2023-03-15T17:22:10+0000",
61+
"multi_option": []
62+
}
63+
},
64+
{
65+
"model": "crowdsourcer.response",
66+
"pk": 102,
67+
"fields": {
68+
"authority": 2,
69+
"question": 273,
70+
"user": 2,
71+
"option": 6,
72+
"response_type": 1,
73+
"public_notes": "a public note",
74+
"page_number": "0",
75+
"evidence": "",
76+
"private_notes": "a private note",
77+
"revision_type": null,
78+
"revision_notes": null,
79+
"created": "2023-03-15T17:22:10+0000",
80+
"last_update": "2023-03-15T17:22:10+0000",
81+
"multi_option": []
82+
}
4383
}
4484
]

crowdsourcer/templates/crowdsourcer/authority_section_list.html

+2
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,7 @@ <h1 class="mb-4">Sections</h1>
4040
{% endfor %}
4141
</tbody>
4242
</table>
43+
44+
<a href="{% session_url 'authority_ror_download' authority_name %}">Download</a>
4345
{% endif %}
4446
{% endblock %}

crowdsourcer/tests/test_right_of_reply_views.py

+41
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import io
2+
13
from django.contrib.auth.models import User
24
from django.test import TestCase
35
from django.urls import reverse
46

7+
import pandas as pd
8+
59
from crowdsourcer.models import (
610
Assigned,
711
MarkingSession,
@@ -554,3 +558,40 @@ def test_view_other_session(self):
554558
progress = response.context["progress"]
555559

556560
self.assertEqual(len(progress.keys()), 2)
561+
562+
563+
class TestCSVDownloadView(BaseTestCase):
564+
def test_download(self):
565+
url = reverse("authority_ror_download", args=("Aberdeenshire Council",))
566+
response = self.client.get(url)
567+
self.assertEqual(response.status_code, 200)
568+
569+
content = response.content.decode("utf-8")
570+
# the dtype bit stops pandas doing annoying conversions and ending up
571+
# with page numers as floats etc
572+
df = pd.read_csv(io.StringIO(content), dtype="object")
573+
# avoid nan results
574+
df = df.fillna("")
575+
576+
self.assertEqual(df.shape[0], 2)
577+
b_and_h_q4 = df.iloc[0]
578+
b_and_h_q5 = df.iloc[1]
579+
580+
self.assertEqual(b_and_h_q4.question_no, "4")
581+
self.assertEqual(
582+
b_and_h_q4.first_mark_response,
583+
"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.",
584+
)
585+
self.assertEqual(b_and_h_q4.agree_with_mark, "Yes")
586+
self.assertEqual(b_and_h_q4.council_page_number, "")
587+
self.assertEqual(b_and_h_q4.council_evidence, "")
588+
589+
self.assertEqual(b_and_h_q5.question_no, "5")
590+
self.assertEqual(
591+
b_and_h_q5.first_mark_response,
592+
"The council convenes or is a member of a local retrofit partnership",
593+
)
594+
self.assertEqual(b_and_h_q5.council_evidence, "http://example.org/")
595+
self.assertEqual(b_and_h_q5.agree_with_mark, "No")
596+
self.assertEqual(b_and_h_q5.council_page_number, "20")
597+
self.assertEqual(b_and_h_q5.council_notes, "We do not agree for reasons")

crowdsourcer/views/rightofreply.py

+120
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import csv
12
import logging
3+
from collections import defaultdict
24

35
from django.core.exceptions import PermissionDenied
6+
from django.http import HttpResponse
47
from django.shortcuts import redirect
58
from django.urls import reverse
69
from django.views.generic import ListView
@@ -205,3 +208,120 @@ def get_context_data(self, **kwargs):
205208
context = super().get_context_data(**kwargs)
206209
context["ror_user"] = True
207210
return context
211+
212+
213+
class AuthorityRORCSVView(ListView):
214+
context_object_name = "responses"
215+
216+
def get_queryset(self):
217+
user = self.request.user
218+
219+
rt = ResponseType.objects.get(type="Right of Reply")
220+
if user.is_superuser:
221+
authority_name = self.kwargs["name"]
222+
authority = PublicAuthority.objects.get(name=authority_name)
223+
else:
224+
authority = self.request.user.marker.authority
225+
226+
self.authority = authority
227+
228+
if authority is not None:
229+
return (
230+
Response.objects.filter(
231+
question__section__marking_session=self.request.current_session,
232+
response_type=rt,
233+
authority=authority,
234+
)
235+
.select_related("question", "question__section")
236+
.order_by(
237+
"question__section__title",
238+
"question__number",
239+
"question__number_part",
240+
)
241+
)
242+
243+
return None
244+
245+
def get_first_mark_responses(self):
246+
rt = ResponseType.objects.get(type="First Mark")
247+
responses = (
248+
Response.objects.filter(
249+
question__section__marking_session=self.request.current_session,
250+
response_type=rt,
251+
authority=self.authority,
252+
)
253+
.select_related("question", "question__section")
254+
.order_by(
255+
"question__section__title",
256+
"question__number",
257+
"question__number_part",
258+
)
259+
)
260+
261+
by_section = defaultdict(dict)
262+
263+
for r in responses:
264+
by_section[r.question.section.title][
265+
r.question.number_and_part
266+
] = r.option.description
267+
268+
return by_section
269+
270+
def get_context_data(self, **kwargs):
271+
context = super().get_context_data(**kwargs)
272+
rows = []
273+
rows.append(
274+
[
275+
"section",
276+
"question_no",
277+
"question",
278+
"first_mark_response",
279+
"agree_with_mark",
280+
"council_response",
281+
"council_evidence",
282+
"council_page_number",
283+
"council_notes",
284+
]
285+
)
286+
287+
first_mark_responses = self.get_first_mark_responses()
288+
289+
for response in context["responses"]:
290+
first_mark_response = ""
291+
if first_mark_responses.get(
292+
response.question.section.title
293+
) and first_mark_responses[response.question.section.title].get(
294+
response.question.number_and_part
295+
):
296+
first_mark_response = first_mark_responses[
297+
response.question.section.title
298+
][response.question.number_and_part]
299+
rows.append(
300+
[
301+
response.question.section.title,
302+
response.question.number_and_part,
303+
response.question.description,
304+
first_mark_response,
305+
"Yes" if response.agree_with_response else "No",
306+
response.option,
307+
",".join(response.evidence_links),
308+
response.page_number,
309+
response.evidence,
310+
]
311+
)
312+
313+
context["authority"] = self.authority.name
314+
context["rows"] = rows
315+
316+
return context
317+
318+
def render_to_response(self, context, **response_kwargs):
319+
filename = f"{self.request.current_session.label}_{context['authority']}_Right_of_Reply.csv"
320+
response = HttpResponse(
321+
content_type="text/csv",
322+
headers={"Content-Disposition": 'attachment; filename="' + filename + '"'},
323+
)
324+
writer = csv.writer(response)
325+
for row in context["rows"]:
326+
writer.writerow(row)
327+
return response

0 commit comments

Comments
 (0)