Skip to content

Commit 3f5fcb9

Browse files
committed
Disable response set submission unless "complete"
A response_Set is complete when all responses have been created
1 parent a8da212 commit 3f5fcb9

11 files changed

Lines changed: 191 additions & 12 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class Delegator(object):
2+
def __getattr__(self, called_method):
3+
def wrapper(*args, **kwargs):
4+
delegation_config = getattr(self, 'DELEGATED_METHODS', None)
5+
6+
if not isinstance(delegation_config, dict):
7+
raise AttributeError("'%s' object has not defined any delegated methods" % (self.__class__.__name__))
8+
9+
for delegate_object_str, delegated_methods in delegation_config.items():
10+
if called_method in delegated_methods:
11+
break
12+
else:
13+
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, called_method))
14+
15+
delegate_object = getattr(self, delegate_object_str, None)
16+
17+
return getattr(delegate_object, called_method)(*args, **kwargs)
18+
19+
return wrapper

lung_cancer_screening/questions/jinja2/responses.jinja

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
<form action="{{ request.path }}" method="post">
4343
{{ csrf_input }}
4444
{{ button({
45-
"text": "Submit"
45+
"text": "Submit",
46+
"disabled": not response_set.is_complete()
4647
}) }}
4748
</form>
4849
</div>

lung_cancer_screening/questions/models/family_history_lung_cancer_response.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ class FamilyHistoryLungCancerResponse(BaseModel):
1515
response_set = models.OneToOneField(ResponseSet, on_delete=models.CASCADE, related_name='family_history_lung_cancer')
1616
value = models.CharField(max_length=1, choices=FamilyHistoryLungCancerValues.choices)
1717

18+
def is_truthy(self):
19+
return self.value == FamilyHistoryLungCancerValues.YES
20+
1821

1922
@receiver(post_save, sender=FamilyHistoryLungCancerResponse)
2023
def remove_relatives_age_when_diagnosed_if_not_yes(sender, instance, **kwargs):
2124
if (
22-
instance.value != FamilyHistoryLungCancerValues.YES
25+
not instance.is_truthy()
2326
and instance.response_set
2427
and hasattr(instance.response_set, "relatives_age_when_diagnosed")
2528
):

lung_cancer_screening/questions/models/response_set.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,45 @@ class Meta:
3838
violation_error_message="An unsubmitted response set already exists for this user"
3939
)
4040
]
41-
41+
q
4242
def clean(self):
4343
super().clean()
4444
self._validate_any_submitted_response_set_recently()
4545

4646

4747
def has_user_submitted_response_set_recently(self):
48-
return self.user and self.user.has_recently_submitted_responses()
48+
return self.user and self.user.has_recently_submitted_responses(excluding=self)
4949

5050

5151
def _validate_any_submitted_response_set_recently(self):
5252
if self.has_user_submitted_response_set_recently():
5353
raise ValidationError(
5454
"Responses have already been submitted for this user"
5555
)
56+
57+
58+
def _response_attrs(self):
59+
response_attrs = [
60+
'asbestos_exposure_response',
61+
'cancer_diagnosis_response',
62+
'check_need_appointment_response',
63+
'date_of_birth_response',
64+
'education_response',
65+
'ethnicity_response',
66+
'family_history_lung_cancer',
67+
'gender_response',
68+
'have_you_ever_smoked_response',
69+
'height_response',
70+
'respiratory_conditions_response',
71+
'sex_at_birth_response',
72+
'weight_response',
73+
]
74+
75+
if hasattr(self, 'family_history_lung_cancer') and self.family_history_lung_cancer.is_truthy():
76+
response_attrs.append('relatives_age_when_diagnosed')
77+
78+
return response_attrs
79+
80+
81+
def is_complete(self):
82+
return all(hasattr(self, attr) for attr in self._response_attrs())

lung_cancer_screening/questions/models/user.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,10 @@ class Meta:
3636
verbose_name = 'user'
3737
verbose_name_plural = 'users'
3838

39-
def has_recently_submitted_responses(self):
40-
return self.responseset_set.recently_submitted().exists()
39+
def has_recently_submitted_responses(self, excluding=None):
40+
query = self.responseset_set.recently_submitted()
41+
42+
if excluding:
43+
query = query.exclude(id=excluding.id)
44+
45+
return query.exists()

lung_cancer_screening/questions/presenters/response_set_presenter.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
from decimal import Decimal
22
from django.urls import reverse
33

4+
from lung_cancer_screening.core.utils.delegator import Delegator
45
from ..models.education_response import EducationValues
56
from ..models.respiratory_conditions_response import RespiratoryConditionValues
67
from ..models.family_history_lung_cancer_response import FamilyHistoryLungCancerValues
78

8-
class ResponseSetPresenter:
9+
class ResponseSetPresenter(Delegator):
910
DATE_FORMAT = "%-d %B %Y" # eg 8 September 2000
1011

12+
DELEGATED_METHODS = {
13+
"response_set": [
14+
"is_complete",
15+
]
16+
}
17+
1118
def __init__(self, response_set):
1219
self.response_set = response_set
1320

@@ -218,6 +225,7 @@ def family_history_responses_items(self):
218225
return items
219226

220227

228+
221229
def _list_to_sentence(self, list, final_separator = "and"):
222230
if len(list) == 0:
223231
return ''

lung_cancer_screening/questions/tests/factories/response_set_factory.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,63 @@ class Meta:
99
model = ResponseSet
1010

1111
user = factory.SubFactory(UserFactory)
12+
13+
class Params:
14+
complete = factory.Trait(
15+
asbestos_exposure_response=factory.RelatedFactory(
16+
"lung_cancer_screening.questions.tests.factories.asbestos_exposure_response_factory.AsbestosExposureResponseFactory",
17+
factory_related_name="response_set"
18+
),
19+
cancer_diagnosis_response=factory.RelatedFactory(
20+
"lung_cancer_screening.questions.tests.factories.cancer_diagnosis_response_factory.CancerDiagnosisResponseFactory",
21+
factory_related_name="response_set"
22+
),
23+
check_need_appointment_response=factory.RelatedFactory(
24+
"lung_cancer_screening.questions.tests.factories.check_need_appointment_response_factory.CheckNeedAppointmentResponseFactory",
25+
factory_related_name="response_set"
26+
),
27+
date_of_birth_response=factory.RelatedFactory(
28+
"lung_cancer_screening.questions.tests.factories.date_of_birth_response_factory.DateOfBirthResponseFactory",
29+
factory_related_name="response_set"
30+
),
31+
education_response=factory.RelatedFactory(
32+
"lung_cancer_screening.questions.tests.factories.education_response_factory.EducationResponseFactory",
33+
factory_related_name="response_set"
34+
),
35+
ethnicity_response=factory.RelatedFactory(
36+
"lung_cancer_screening.questions.tests.factories.ethnicity_response_factory.EthnicityResponseFactory",
37+
factory_related_name="response_set"
38+
),
39+
family_history_lung_cancer_response=factory.RelatedFactory(
40+
"lung_cancer_screening.questions.tests.factories.family_history_lung_cancer_response_factory.FamilyHistoryLungCancerResponseFactory",
41+
factory_related_name="response_set"
42+
),
43+
gender_response=factory.RelatedFactory(
44+
"lung_cancer_screening.questions.tests.factories.gender_response_factory.GenderResponseFactory",
45+
factory_related_name="response_set"
46+
),
47+
have_you_ever_smoked_response=factory.RelatedFactory(
48+
"lung_cancer_screening.questions.tests.factories.have_you_ever_smoked_response_factory.HaveYouEverSmokedResponseFactory",
49+
factory_related_name="response_set"
50+
),
51+
height_response=factory.RelatedFactory(
52+
"lung_cancer_screening.questions.tests.factories.height_response_factory.HeightResponseFactory",
53+
factory_related_name="response_set"
54+
),
55+
relatives_age_when_diagnosed_response=factory.RelatedFactory(
56+
"lung_cancer_screening.questions.tests.factories.relatives_age_when_diagnosed_response_factory.RelativesAgeWhenDiagnosedResponseFactory",
57+
factory_related_name="response_set"
58+
),
59+
respiratory_conditions_response=factory.RelatedFactory(
60+
"lung_cancer_screening.questions.tests.factories.respiratory_conditions_response_factory.RespiratoryConditionsResponseFactory",
61+
factory_related_name="response_set"
62+
),
63+
sex_at_birth_response=factory.RelatedFactory(
64+
"lung_cancer_screening.questions.tests.factories.sex_at_birth_response_factory.SexAtBirthResponseFactory",
65+
factory_related_name="response_set"
66+
),
67+
weight_response=factory.RelatedFactory(
68+
"lung_cancer_screening.questions.tests.factories.weight_response_factory.WeightResponseFactory",
69+
factory_related_name="response_set"
70+
),
71+
)

lung_cancer_screening/questions/tests/unit/models/test_family_history_lung_cancer_response.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ def test_has_value_as_string(self):
4040
self.assertIsInstance(response.value, str)
4141

4242

43+
def test_is_truthy_returns_true_if_value_is_yes(self):
44+
response = FamilyHistoryLungCancerResponseFactory(
45+
value=FamilyHistoryLungCancerValues.YES
46+
)
47+
48+
self.assertTrue(response.is_truthy())
49+
50+
51+
def test_is_truthy_returns_false_if_value_is_no(self):
52+
response = FamilyHistoryLungCancerResponseFactory(
53+
value=FamilyHistoryLungCancerValues.NO
54+
)
55+
56+
self.assertFalse(response.is_truthy())
57+
58+
4359
def test_deletes_family_age_when_diagnosed_if_response_is_no(self):
4460
RelativesAgeWhenDiagnosedResponseFactory.create(response_set=self.response_set)
4561

lung_cancer_screening/questions/tests/unit/models/test_response_set.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.test import TestCase
1+
from django.test import TestCase, tag
22
from datetime import datetime
33
from dateutil.relativedelta import relativedelta
44
from django.utils import timezone
@@ -9,7 +9,10 @@
99
from ...factories.response_set_factory import ResponseSetFactory
1010
from ....models.user import User
1111
from ....models.response_set import ResponseSet
12+
from ....models.family_history_lung_cancer_response import FamilyHistoryLungCancerValues
1213

14+
15+
@tag("ResponseSet")
1316
class TestResponseSet(TestCase):
1417
def setUp(self):
1518
self.user = UserFactory()
@@ -80,7 +83,16 @@ def test_is_invalid_if_another_response_set_was_submitted_within_the_recently_su
8083
"Responses have already been submitted for this user"
8184
)
8285

83-
# Query managers
86+
87+
def test_submitted_response_set_is_valid_on_update(self):
88+
self.response_set.submitted_at = timezone.now()
89+
self.response_set.save()
90+
91+
self.response_set.full_clean()
92+
93+
94+
# Query managers
95+
8496
def test_objects_returns_all_response_sets(self):
8597
unsubmitted_response_set = ResponseSetFactory()
8698
submitted_response_set = ResponseSetFactory(submitted_at=timezone.now())
@@ -95,6 +107,7 @@ def test_objects_returns_all_response_sets(self):
95107
response_sets,
96108
)
97109

110+
98111
def test_unsubmitted_returns_only_unsubmitted_response_sets(self):
99112
unsubmitted_response_set = ResponseSetFactory()
100113
submitted_response_set = ResponseSetFactory(submitted_at=timezone.now())
@@ -144,3 +157,29 @@ def test_submitted_recently_returns_only_submitted_response_sets_in_the_recently
144157
old_submitted_response,
145158
recently_submitted_response_sets,
146159
)
160+
161+
162+
def test_is_complete_returns_true_if_all_questions_are_answered(self):
163+
response_set = ResponseSetFactory.create(complete=True)
164+
165+
self.assertTrue(response_set.is_complete())
166+
167+
168+
def test_is_complete_returns_false_if_a_single_question_is_not_answered(self):
169+
response_set = ResponseSetFactory.create(complete=True)
170+
response_set.asbestos_exposure_response.delete()
171+
response_set.refresh_from_db()
172+
173+
self.assertFalse(response_set.is_complete())
174+
175+
176+
def test_is_complete_returns_true_if_family_history_cancer_no_and_none_age_diagnosed(self):
177+
response_set = ResponseSetFactory.create(complete=True)
178+
179+
family_history = response_set.family_history_lung_cancer
180+
family_history.value = FamilyHistoryLungCancerValues.NO
181+
family_history.save()
182+
183+
response_set.refresh_from_db()
184+
185+
self.assertTrue(response_set.is_complete())

lung_cancer_screening/questions/tests/unit/models/test_user.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ def test_raises_a_validation_error_if_nhs_number_is_duplicate(self):
8080
context.exception.messages
8181
)
8282

83+
8384
def test_has_recently_submitted_responses_returns_true_if_has_recently_submitted_response_set(self):
8485
self.user.responseset_set.create(
8586
submitted_at=timezone.now() - timedelta(days=ResponseSet.RECENTLY_SUBMITTED_PERIOD_DAYS - 1)
8687
)
8788
self.assertTrue(self.user.has_recently_submitted_responses())
8889

90+
8991
def test_has_recently_submitted_responses_returns_false_if_has_no_recently_submitted_response_set(self):
9092
self.user.responseset_set.create(
9193
submitted_at=timezone.now() - timedelta(days=ResponseSet.RECENTLY_SUBMITTED_PERIOD_DAYS)

0 commit comments

Comments
 (0)