Skip to content

Commit de1b011

Browse files
authored
Merge pull request #1107 from alphagov/2081-ehrc-csv
2081: Add Export to equality body CSV for detailed cases
2 parents b60c65b + 82d4330 commit de1b011

18 files changed

Lines changed: 598 additions & 34 deletions

File tree

accessibility_monitoring_platform/apps/cases/forms.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""Forms - cases"""
22

3-
import re
4-
53
import requests
64
from django import forms
75
from django.contrib.auth.models import User
@@ -15,6 +13,7 @@
1513
BaseCase,
1614
Complaint,
1715
Sort,
16+
extract_id_from_case_url,
1817
)
1918
from ..common.forms import (
2019
AMPCharFieldWide,
@@ -119,18 +118,19 @@ def clean_previous_case_url(self):
119118
if requests.head(previous_case_url, timeout=10).status_code >= 400:
120119
raise ValidationError("Previous case URL does not exist")
121120

122-
# Extract case id from view case URL
123121
try:
124-
case_id: str = re.search(".*/(simplified|detailed)/(.+?)/(view|case-detail)/?", previous_case_url).group( # type: ignore
125-
2
126-
)
122+
case_id: int | None = extract_id_from_case_url(case_url=previous_case_url)
127123
except AttributeError:
128124
raise ValidationError( # pylint: disable=raise-missing-from
129125
"Previous case URL did not contain case id"
130126
)
127+
if case_id is None:
128+
raise ValidationError( # pylint: disable=raise-missing-from
129+
"Previous case URL did not contain case id"
130+
)
131131

132132
# Check if Case exists matching id from URL
133-
if case_id.isdigit() and BaseCase.objects.filter(id=case_id).exists():
133+
if case_id and BaseCase.objects.filter(id=case_id).exists():
134134
return previous_case_url
135135
else:
136136
raise ValidationError("Previous case not found in platform")

accessibility_monitoring_platform/apps/cases/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Models - cases
33
"""
44

5+
import re
56
from dataclasses import dataclass
67
from datetime import datetime
78

@@ -30,6 +31,25 @@
3031
UPDATE_SEPARATOR: str = " -> "
3132

3233

34+
def extract_id_from_case_url(case_url: str) -> int | None:
35+
"""Extract case id from case overview URL"""
36+
regex_search_result = re.search(
37+
".*/(simplified|detailed)/(.+?)/(view|case-detail)/?", case_url
38+
)
39+
if regex_search_result is not None:
40+
case_id: str = regex_search_result.group(2)
41+
if case_id.isdigit():
42+
return int(case_id)
43+
44+
45+
def get_previous_case_identifier(previous_case_url: str) -> str | None:
46+
"""Build case identifier from URL"""
47+
case_id: int | None = extract_id_from_case_url(case_url=previous_case_url)
48+
if case_id:
49+
base_case: BaseCase = BaseCase.objects.get(id=case_id)
50+
return base_case.case_identifier
51+
52+
3353
class DisablePyTestCollectionMixin(object):
3454
__test__ = False
3555

accessibility_monitoring_platform/apps/cases/templates/cases/basecase_list.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ <h2 id="id_header_3" class="govuk-heading-m">Detailed</h2>
110110
Create detailed case
111111
</a>
112112
</li>
113+
<li>
114+
<a
115+
href="{% url 'detailed:case-export-list' %}{% if url_parameters %}?{{ url_parameters }}{% endif %}"
116+
class="govuk-link govuk-link--no-visited-state"
117+
>
118+
Export to CSV
119+
</a>
120+
</li>
113121
<li>
114122
<a
115123
href="{% url 'detailed:export-feedback-survey-cases' %}{% if url_parameters %}?{{ url_parameters }}{% endif %}"
@@ -120,10 +128,10 @@ <h2 id="id_header_3" class="govuk-heading-m">Detailed</h2>
120128
</li>
121129
<li>
122130
<a
123-
href="{% url 'detailed:case-export-list' %}{% if url_parameters %}?{{ url_parameters }}{% endif %}"
131+
href="{% url 'detailed:export-equality-body-cases' %}{% if url_parameters %}?{{ url_parameters }}{% endif %}"
124132
class="govuk-link govuk-link--no-visited-state"
125133
>
126-
Export to CSV
134+
Export to equality body CSV
127135
</a>
128136
</li>
129137
</ul>

accessibility_monitoring_platform/apps/cases/tests/test_models.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
MobileCaseStatus,
2323
SimplifiedCaseStatus,
2424
TestType,
25+
extract_id_from_case_url,
26+
get_previous_case_identifier,
2527
)
2628

2729
REMINDER_DUE_DATE: date = date(2022, 1, 1)
@@ -289,3 +291,38 @@ def test_case_get_case():
289291
simplified_case: SimplifiedCase = SimplifiedCase.objects.create()
290292

291293
assert isinstance(simplified_case.get_case(), SimplifiedCase) is True
294+
295+
296+
@pytest.mark.parametrize(
297+
"case_url, case_id",
298+
[
299+
("https://...gov.uk/simplified/1/view/", 1),
300+
("https://...gov.uk/detailed/2/case-detail/", 2),
301+
("", None),
302+
("https://...gov.uk/audit/191/view/", None),
303+
],
304+
)
305+
def test_extract_id_from_case_url(case_url, case_id):
306+
"""Test extracting case id from case overview URL"""
307+
308+
assert extract_id_from_case_url(case_url) == case_id
309+
310+
311+
@pytest.mark.parametrize(
312+
"previous_case_url, previous_case_identifier",
313+
[
314+
("https://...gov.uk/simplified/1/view/", "#S-1"),
315+
("https://...gov.uk/detailed/1/case-detail/", "#D-1"),
316+
("", None),
317+
("https://...gov.uk/audit/191/view/", None),
318+
],
319+
)
320+
@pytest.mark.django_db
321+
def test_previous_case_identifier(previous_case_url, previous_case_identifier):
322+
"""Test previous case identifier derived from url"""
323+
if "detailed" in previous_case_url:
324+
DetailedCase.objects.create()
325+
else:
326+
SimplifiedCase.objects.create()
327+
328+
assert get_previous_case_identifier(previous_case_url) == previous_case_identifier

accessibility_monitoring_platform/apps/detailed/csv_export.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,159 @@
44
from ..detailed.models import Contact as DetailedContact
55
from ..detailed.models import DetailedCase
66

7+
DETAILED_EQUALITY_BODY_COLUMNS_FOR_EXPORT: list[CSVColumn] = [
8+
CSVColumn(
9+
column_header="Enforcement Body",
10+
source_class=DetailedCase,
11+
source_attr="enforcement_body",
12+
),
13+
CSVColumn(
14+
column_header="Test type",
15+
source_class=DetailedCase,
16+
source_attr="test_type",
17+
),
18+
CSVColumn(
19+
column_header="Case number",
20+
source_class=DetailedCase,
21+
source_attr="case_number",
22+
),
23+
CSVColumn(
24+
column_header="Organisation",
25+
source_class=DetailedCase,
26+
source_attr="organisation_name",
27+
),
28+
CSVColumn(
29+
column_header="Website URL",
30+
source_class=DetailedCase,
31+
source_attr="home_page_url",
32+
),
33+
CSVColumn(
34+
column_header="Parent organisation name",
35+
source_class=DetailedCase,
36+
source_attr="parental_organisation_name",
37+
),
38+
CSVColumn(
39+
column_header="Sub-category",
40+
source_class=DetailedCase,
41+
source_attr="subcategory",
42+
),
43+
CSVColumn(
44+
column_header="Website name",
45+
source_class=DetailedCase,
46+
source_attr="website_name",
47+
),
48+
CSVColumn(
49+
column_header="Previous Case Number",
50+
source_class=DetailedCase,
51+
source_attr="previous_case_identifier",
52+
),
53+
CSVColumn(
54+
column_header="Is it a complaint?",
55+
source_class=DetailedCase,
56+
source_attr="is_complaint",
57+
),
58+
CSVColumn(
59+
column_header="Published report",
60+
source_class=DetailedCase,
61+
source_attr="equality_body_report_url",
62+
),
63+
CSVColumn(
64+
column_header="Enforcement recommendation",
65+
source_class=DetailedCase,
66+
source_attr="recommendation_for_enforcement",
67+
),
68+
CSVColumn(
69+
column_header="Enforcement recommendation notes including exemptions",
70+
source_class=DetailedCase,
71+
source_attr="recommendation_info",
72+
),
73+
CSVColumn(
74+
column_header="Summary of progress made / response from PSB",
75+
source_class=DetailedCase,
76+
source_attr="psb_progress_info",
77+
),
78+
CSVColumn(
79+
column_header="Contact details",
80+
source_class=DetailedCase,
81+
source_attr="equality_body_export_contact_details",
82+
),
83+
CSVColumn(
84+
column_header="Organisation responded to report?",
85+
source_class=DetailedCase,
86+
source_attr="report_acknowledged_yes_no",
87+
),
88+
CSVColumn(
89+
column_header="Report sent on",
90+
source_class=DetailedCase,
91+
source_attr="report_sent_date",
92+
),
93+
CSVColumn(
94+
column_header="Report acknowledged",
95+
source_class=DetailedCase,
96+
source_attr="report_acknowledged_date",
97+
),
98+
CSVColumn(
99+
column_header="12-week deadline",
100+
source_class=DetailedCase,
101+
source_attr="twelve_week_deadline_date",
102+
),
103+
CSVColumn(
104+
column_header="Retest date",
105+
source_class=DetailedCase,
106+
source_attr="retest_start_date",
107+
),
108+
CSVColumn(
109+
column_header="Date when compliance decision email sent to public sector body",
110+
source_class=DetailedCase,
111+
source_attr="recommendation_decision_sent_date",
112+
),
113+
CSVColumn(
114+
column_header="Compliance decision email sent to",
115+
source_class=DetailedCase,
116+
source_attr="recommendation_decision_sent_to",
117+
),
118+
CSVColumn(
119+
column_header="Total number of accessibility issues",
120+
source_class=DetailedCase,
121+
source_attr="initial_total_number_of_issues",
122+
),
123+
CSVColumn(
124+
column_header="Number of issues fixed",
125+
source_class=DetailedCase,
126+
source_attr="number_of_issues_fixed",
127+
),
128+
CSVColumn(
129+
column_header="Number of issues unfixed",
130+
source_class=DetailedCase,
131+
source_attr="retest_total_number_of_issues",
132+
),
133+
CSVColumn(
134+
column_header="Issues fixed as a percentage",
135+
source_class=DetailedCase,
136+
source_attr="percentage_of_issues_fixed",
137+
),
138+
CSVColumn(
139+
column_header="Was an accessibility statement found during the 12-week assessment",
140+
source_class=DetailedCase,
141+
source_attr="equality_body_export_statement_found_at_retest",
142+
),
143+
CSVColumn(
144+
column_header="Retest Accessibility Statement Decision",
145+
source_class=DetailedCase,
146+
source_attr="retest_statement_compliance_state",
147+
),
148+
CSVColumn(
149+
column_header="Retest disproportionate burden claimed?",
150+
source_class=DetailedCase,
151+
source_attr="retest_disproportionate_burden_claim",
152+
),
153+
CSVColumn(
154+
column_header="Retest disproportionate burden details",
155+
source_class=DetailedCase,
156+
source_attr="retest_disproportionate_burden_information",
157+
),
158+
]
159+
7160
DETAILED_CASE_COLUMNS_FOR_EXPORT: list[CSVColumn] = [
8161
# General
9162
CSVColumn(

accessibility_monitoring_platform/apps/detailed/models.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
from django.urls import reverse
1313
from django.utils.safestring import mark_safe
1414

15-
from ..cases.models import UPDATE_SEPARATOR, BaseCase, DetailedCaseStatus
15+
from ..cases.models import (
16+
UPDATE_SEPARATOR,
17+
BaseCase,
18+
DetailedCaseStatus,
19+
get_previous_case_identifier,
20+
)
1621
from ..common.models import Boolean, VersionModel
1722
from ..common.utils import extract_domain_from_url
1823

@@ -274,6 +279,57 @@ def contacts(self) -> QuerySet["Contact"]:
274279
def preferred_contacts(self) -> QuerySet["Contact"]:
275280
return self.contacts.filter(preferred=Contact.Preferred.YES)
276281

282+
@property
283+
def previous_case_identifier(self) -> str:
284+
return get_previous_case_identifier(previous_case_url=self.previous_case_url)
285+
286+
@property
287+
def equality_body_export_contact_details(self) -> str:
288+
contacts_string: str = ""
289+
for contact in self.contacts:
290+
if contacts_string:
291+
contacts_string += "\n"
292+
if contact.name:
293+
contacts_string += f"{contact.name}\n"
294+
if contact.job_title:
295+
contacts_string += f"{contact.job_title}\n"
296+
if contact.contact_details:
297+
contacts_string += f"{contact.contact_details}\n"
298+
if contact.information:
299+
contacts_string += f"{contact.information}\n"
300+
return contacts_string
301+
302+
@property
303+
def report_acknowledged_yes_no(self) -> str:
304+
return (
305+
"Yes"
306+
if self.report_acknowledged_date and self.no_psb_contact == Boolean.NO
307+
else "No"
308+
)
309+
310+
@property
311+
def number_of_issues_fixed(self) -> int | None:
312+
if self.initial_total_number_of_issues and self.retest_total_number_of_issues:
313+
return (
314+
self.initial_total_number_of_issues - self.retest_total_number_of_issues
315+
)
316+
317+
@property
318+
def percentage_of_issues_fixed(self) -> int | None:
319+
if self.initial_total_number_of_issues and self.number_of_issues_fixed:
320+
return int(
321+
self.number_of_issues_fixed * 100 / self.initial_total_number_of_issues
322+
)
323+
324+
@property
325+
def equality_body_export_statement_found_at_retest(self) -> str:
326+
if self.retest_statement_compliance_state in [
327+
DetailedCase.StatementCompliance.COMPLIANT,
328+
DetailedCase.StatementCompliance.NOT_COMPLIANT,
329+
]:
330+
return "Yes"
331+
return "No"
332+
277333

278334
class DetailedEventHistory(models.Model):
279335
"""Model to record events on platform"""

accessibility_monitoring_platform/apps/detailed/tests/test_forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
(
3636
"https://platform.gov.uk/detailed/not-an-id/case-detail",
3737
200,
38-
"Previous case not found in platform",
38+
"Previous case URL did not contain case id",
3939
),
4040
],
4141
)

0 commit comments

Comments
 (0)