Skip to content

Commit 516168e

Browse files
committed
Move RespiratoryConditionsResponse to its own model
1 parent fbf5026 commit 516168e

File tree

8 files changed

+178
-50
lines changed

8 files changed

+178
-50
lines changed

lung_cancer_screening/questions/forms/respiratory_conditions_form.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
from django import forms
22

33
from ...nhsuk_forms.choice_field import MultipleChoiceField
4-
from ..models.response_set import ResponseSet, RespiratoryConditionValues
4+
from ..models.respiratory_conditions_response import RespiratoryConditionsResponse, RespiratoryConditionValues
55

66

77
class RespiratoryConditionsForm(forms.ModelForm):
88

99
def __init__(self, *args, **kwargs):
1010
super().__init__(*args, **kwargs)
1111

12-
self.fields["respiratory_conditions"] = MultipleChoiceField(
12+
self.fields["value"] = MultipleChoiceField(
1313
choices=RespiratoryConditionValues.choices,
1414
widget=forms.CheckboxSelectMultiple,
1515
label=(
@@ -31,7 +31,7 @@ def __init__(self, *args, **kwargs):
3131
)
3232

3333
# Add hints for each choice
34-
respiratory_conditions_field = self["respiratory_conditions"]
34+
respiratory_conditions_field = self["value"]
3535
respiratory_conditions_field.add_hint_for_choice(
3636
RespiratoryConditionValues.PNEUMONIA,
3737
"An infection of the lungs, usually diagnosed by a chest x-ray"
@@ -66,5 +66,5 @@ def __init__(self, *args, **kwargs):
6666
)
6767

6868
class Meta:
69-
model = ResponseSet
70-
fields = ['respiratory_conditions']
69+
model = RespiratoryConditionsResponse
70+
fields = ['value']
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Generated manually to create RespiratoryConditionsResponse and copy data
2+
3+
import django.contrib.postgres.fields
4+
import django.db.models.deletion
5+
import lung_cancer_screening.questions.models.respiratory_conditions_response
6+
from django.db import migrations, models
7+
8+
9+
def copy_respiratory_conditions_data(apps, schema_editor):
10+
with schema_editor.connection.cursor() as cursor:
11+
cursor.execute("""
12+
INSERT INTO questions_respiratoryconditionsresponse (created_at, updated_at, value, response_set_id)
13+
SELECT NOW(), NOW(), respiratory_conditions, id
14+
FROM questions_responseset
15+
WHERE respiratory_conditions IS NOT NULL
16+
ON CONFLICT (response_set_id) DO NOTHING
17+
""")
18+
19+
20+
def reverse_copy_respiratory_conditions_data(apps, schema_editor):
21+
with schema_editor.connection.cursor() as cursor:
22+
cursor.execute("""
23+
UPDATE questions_responseset rs
24+
SET respiratory_conditions = r.value
25+
FROM questions_respiratoryconditionsresponse r
26+
WHERE rs.id = r.response_set_id
27+
""")
28+
29+
30+
class Migration(migrations.Migration):
31+
32+
dependencies = [
33+
('questions', '0030_heightresponse'),
34+
]
35+
36+
operations = [
37+
migrations.CreateModel(
38+
name='RespiratoryConditionsResponse',
39+
fields=[
40+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
41+
('created_at', models.DateTimeField(auto_now_add=True)),
42+
('updated_at', models.DateTimeField(auto_now=True)),
43+
('value', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(choices=[('P', 'Pneumonia'), ('E', 'Emphysema'), ('B', 'Bronchitis'), ('T', 'Tuberculosis (TB)'), ('C', 'Chronic obstructive pulmonary disease (COPD)'), ('N', 'No, I have not had any of these respiratory conditions')], max_length=1), size=None, validators=[lung_cancer_screening.questions.models.respiratory_conditions_response.validate_singleton_option])),
44+
('response_set', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='respiratory_conditions_response', to='questions.responseset')),
45+
],
46+
options={
47+
'abstract': False,
48+
},
49+
),
50+
migrations.RunPython(copy_respiratory_conditions_data, reverse_copy_respiratory_conditions_data),
51+
migrations.RemoveField(
52+
model_name='responseset',
53+
name='respiratory_conditions',
54+
),
55+
]

lung_cancer_screening/questions/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
from .ethnicity_response import EthnicityResponse # noqa: F401
77
from .gender_response import GenderResponse # noqa: F401
88
from .height_response import HeightResponse # noqa: F401
9+
from .respiratory_conditions_response import RespiratoryConditionsResponse # noqa: F401
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from django.db import models
2+
from django.contrib.postgres.fields import ArrayField
3+
from django.core.exceptions import ValidationError
4+
5+
from .base import BaseModel
6+
from .response_set import ResponseSet
7+
8+
9+
class RespiratoryConditionValues(models.TextChoices):
10+
PNEUMONIA = "P", "Pneumonia"
11+
EMPHYSEMA = "E", "Emphysema"
12+
BRONCHITIS = "B", "Bronchitis"
13+
TUBERCULOSIS = "T", "Tuberculosis (TB)"
14+
COPD = "C", "Chronic obstructive pulmonary disease (COPD)"
15+
NONE = "N", "No, I have not had any of these respiratory conditions"
16+
17+
18+
def validate_singleton_option(value):
19+
if value and "N" in value and len(value) > 1:
20+
raise ValidationError(
21+
"Cannot have singleton value and other values selected",
22+
code="singleton_option",
23+
)
24+
25+
26+
class RespiratoryConditionsResponse(BaseModel):
27+
response_set = models.OneToOneField(ResponseSet, on_delete=models.CASCADE, related_name='respiratory_conditions_response')
28+
value = ArrayField(
29+
models.CharField(max_length=1, choices=RespiratoryConditionValues.choices),
30+
validators=[validate_singleton_option]
31+
)
32+
33+
@property
34+
def formatted(self):
35+
if not self.value:
36+
return None
37+
display_values = [
38+
RespiratoryConditionValues(code).label
39+
for code in self.value
40+
]
41+
return ", ".join(display_values)

lung_cancer_screening/questions/tests/unit/forms/test_respiratory_conditions_form.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,95 @@
11
from django.test import TestCase
22

3-
from ...factories.user_factory import UserFactory
4-
from ....models.response_set import ResponseSet
3+
from ...factories.response_set_factory import ResponseSetFactory
4+
from ....models.respiratory_conditions_response import RespiratoryConditionsResponse
55
from ....forms.respiratory_conditions_form import RespiratoryConditionsForm
66

77

88
class TestRespiratoryConditionsForm(TestCase):
99
def setUp(self):
10-
self.user = UserFactory()
11-
self.response_set = ResponseSet(user=self.user)
10+
self.response_set = ResponseSetFactory()
11+
self.response = RespiratoryConditionsResponse.objects.create(
12+
response_set=self.response_set,
13+
value=["P"]
14+
)
1215

1316
def test_is_valid_with_single_condition(self):
1417
form = RespiratoryConditionsForm(
15-
instance=self.response_set,
18+
instance=self.response,
1619
data={
17-
"respiratory_conditions": ["P"]
20+
"value": ["P"]
1821
}
1922
)
2023
self.assertTrue(form.is_valid())
2124
self.assertEqual(
22-
form.cleaned_data["respiratory_conditions"],
25+
form.cleaned_data["value"],
2326
["P"]
2427
)
2528

2629
def test_is_valid_with_multiple_conditions(self):
2730
form = RespiratoryConditionsForm(
28-
instance=self.response_set,
31+
instance=self.response,
2932
data={
30-
"respiratory_conditions": ["P", "E", "C"]
33+
"value": ["P", "E", "C"]
3134
}
3235
)
3336
self.assertTrue(form.is_valid())
3437
self.assertEqual(
35-
form.cleaned_data["respiratory_conditions"],
38+
form.cleaned_data["value"],
3639
["P", "E", "C"]
3740
)
3841

3942
def test_is_valid_with_none_of_the_above(self):
4043
form = RespiratoryConditionsForm(
41-
instance=self.response_set,
44+
instance=self.response,
4245
data={
43-
"respiratory_conditions": ["N"]
46+
"value": ["N"]
4447
}
4548
)
4649
self.assertTrue(form.is_valid())
4750
self.assertEqual(
48-
form.cleaned_data["respiratory_conditions"],
51+
form.cleaned_data["value"],
4952
["N"]
5053
)
5154

5255
def test_is_invalid_with_an_invalid_value(self):
5356
form = RespiratoryConditionsForm(
54-
instance=self.response_set,
57+
instance=self.response,
5558
data={
56-
"respiratory_conditions": ["invalid"]
59+
"value": ["invalid"]
5760
}
5861
)
5962
self.assertFalse(form.is_valid())
6063
self.assertEqual(
61-
form.errors["respiratory_conditions"],
64+
form.errors["value"],
6265
["Select a valid choice. invalid is not one of the available choices."]
6366
)
6467

6568
def test_is_invalid_when_no_option_is_selected(self):
6669
form = RespiratoryConditionsForm(
67-
instance=self.response_set,
70+
instance=self.response,
6871
data={
69-
"respiratory_conditions": []
72+
"value": []
7073
}
7174
)
7275
self.assertFalse(form.is_valid())
7376
self.assertEqual(
74-
form.errors["respiratory_conditions"],
77+
form.errors["value"],
7578
["Select if you have had any respiratory conditions"]
7679
)
7780

7881
def test_is_invalid_with_none_of_the_above_selected_and_other_options_selected(
7982
self
8083
):
8184
form = RespiratoryConditionsForm(
82-
instance=self.response_set,
85+
instance=self.response,
8386
data={
84-
"respiratory_conditions": ["N", "P", "E", "C"]
87+
"value": ["N", "P", "E", "C"]
8588
}
8689
)
8790
self.assertFalse(form.is_valid())
8891
self.assertEqual(
89-
form.errors["respiratory_conditions"][0],
92+
form.errors["value"][0],
9093
(
9194
"Select if you have had any respiratory conditions, "
9295
"or select 'No, I have not had any of these "
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.test import TestCase
2+
3+
from ...factories.response_set_factory import ResponseSetFactory
4+
from ....models.respiratory_conditions_response import RespiratoryConditionsResponse, RespiratoryConditionValues
5+
6+
7+
class TestRespiratoryConditionsResponse(TestCase):
8+
def test_has_response_set_as_foreign_key(self):
9+
response_set = ResponseSetFactory()
10+
response = RespiratoryConditionsResponse.objects.create(
11+
response_set=response_set,
12+
value=[RespiratoryConditionValues.PNEUMONIA]
13+
)
14+
15+
self.assertEqual(response.response_set, response_set)
16+
17+
def test_has_value_as_list(self):
18+
response_set = ResponseSetFactory()
19+
response = RespiratoryConditionsResponse.objects.create(
20+
response_set=response_set,
21+
value=[RespiratoryConditionValues.PNEUMONIA, RespiratoryConditionValues.BRONCHITIS]
22+
)
23+
24+
self.assertIsInstance(response.value, list)

lung_cancer_screening/questions/tests/unit/views/test_respiratory_conditions.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.utils import timezone
55

66
from .helpers.authentication import login_user
7+
from lung_cancer_screening.questions.models.respiratory_conditions_response import RespiratoryConditionsResponse
78

89

910
class TestGetRespiratoryConditions(TestCase):
@@ -68,7 +69,7 @@ class TestPostRespiratoryConditions(TestCase):
6869
def setUp(self):
6970
self.user = login_user(self.client)
7071

71-
self.valid_params = {"respiratory_conditions": ["P", "E"]}
72+
self.valid_params = {"value": ["P", "E"]}
7273

7374
def test_post_redirects_if_the_user_is_not_logged_in(self):
7475
self.client.logout()
@@ -96,8 +97,8 @@ def test_post_creates_unsubmitted_response_set_when_no_response_set_exists(
9697
self.assertEqual(self.user.responseset_set.count(), 1)
9798
self.assertEqual(response_set.submitted_at, None)
9899
self.assertEqual(
99-
response_set.respiratory_conditions,
100-
self.valid_params["respiratory_conditions"]
100+
RespiratoryConditionsResponse.objects.get(response_set=response_set).value,
101+
self.valid_params["value"]
101102
)
102103
self.assertEqual(response_set.user, self.user)
103104

@@ -113,8 +114,8 @@ def test_post_updates_unsubmitted_response_set_when_one_exists(self):
113114
self.assertEqual(self.user.responseset_set.count(), 1)
114115
self.assertEqual(response_set.submitted_at, None)
115116
self.assertEqual(
116-
response_set.respiratory_conditions,
117-
self.valid_params["respiratory_conditions"]
117+
RespiratoryConditionsResponse.objects.get(response_set=response_set).value,
118+
self.valid_params["value"]
118119
)
119120
self.assertEqual(response_set.user, self.user)
120121

@@ -136,8 +137,8 @@ def test_post_creates_new_unsubmitted_response_set_when_submitted_exists_over_ye
136137
response_set = self.user.responseset_set.last()
137138
self.assertEqual(response_set.submitted_at, None)
138139
self.assertEqual(
139-
response_set.respiratory_conditions,
140-
self.valid_params["respiratory_conditions"]
140+
RespiratoryConditionsResponse.objects.get(response_set=response_set).value,
141+
self.valid_params["value"]
141142
)
142143
self.assertEqual(response_set.user, self.user)
143144

@@ -163,20 +164,20 @@ def test_post_stores_a_valid_response_for_the_user(self):
163164

164165
response_set = self.user.responseset_set.first()
165166
self.assertEqual(
166-
response_set.respiratory_conditions,
167-
self.valid_params["respiratory_conditions"]
167+
RespiratoryConditionsResponse.objects.get(response_set=response_set).value,
168+
self.valid_params["value"]
168169
)
169170
self.assertEqual(response_set.user, self.user)
170171

171172
def test_post_stores_single_selection(self):
172173
self.client.post(
173174
reverse("questions:respiratory_conditions"),
174-
{"respiratory_conditions": ["N"]}
175+
{"value": ["N"]}
175176
)
176177

177178
response_set = self.user.responseset_set.first()
178179
self.assertEqual(
179-
response_set.respiratory_conditions,
180+
RespiratoryConditionsResponse.objects.get(response_set=response_set).value,
180181
["N"]
181182
)
182183

@@ -201,7 +202,7 @@ def test_post_responds_with_422_if_the_response_fails_to_create(self):
201202
def test_post_responds_with_422_if_no_selection_is_made(self):
202203
response = self.client.post(
203204
reverse("questions:respiratory_conditions"),
204-
{"respiratory_conditions": []}
205+
{"value": []}
205206
)
206207

207208
self.assertEqual(response.status_code, 422)
@@ -212,7 +213,6 @@ def test_post_does_not_update_response_set_on_invalid_data(self):
212213
{"respiratory_conditions": ["INVALID"]}
213214
)
214215

215-
self.assertEqual(
216-
self.user.responseset_set.first().respiratory_conditions,
217-
None
218-
)
216+
response_set = self.user.responseset_set.first()
217+
if response_set:
218+
self.assertFalse(RespiratoryConditionsResponse.objects.filter(response_set=response_set).exists())

0 commit comments

Comments
 (0)