Skip to content

Commit

Permalink
Merge branch '77-council-ror-download' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
struan committed Oct 16, 2024
2 parents 97e113c + 60372fa commit 3fdb078
Show file tree
Hide file tree
Showing 5 changed files with 222 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": []
}
}
]
12 changes: 12 additions & 0 deletions crowdsourcer/templates/crowdsourcer/authority_section_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,17 @@ <h2 class="mb-3 h4">Help us by providing optional feedback</h2>
</div>
{% endif %}

{% 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 @@ -219,3 +222,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 3fdb078

Please sign in to comment.