Coverage for crateweb/consent/forms.py: 58%
81 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-27 10:34 -0500
« prev ^ index » next coverage.py v7.8.0, created at 2025-08-27 10:34 -0500
1"""
2crate_anon/crateweb/consent/forms.py
4===============================================================================
6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CRATE.
11 CRATE is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CRATE is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CRATE. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26**Django forms for the consent-to-contact system.**
28"""
30import logging
32from cardinal_pythonlib.django.forms import (
33 MultipleNhsNumberAreaField,
34 MultipleWordAreaField,
35 SingleNhsNumberField,
36)
37from django import forms
38from django.conf import settings
39from django.utils.safestring import mark_safe
41from crate_anon.crateweb.consent.models import (
42 ClinicianResponse,
43 Study,
44 TeamInfo,
45 TeamRep,
46)
47from crate_anon.crateweb.research.research_db_info import (
48 SingleResearchDatabase,
49)
51log = logging.getLogger(__name__)
54class SingleNhsNumberForm(forms.Form):
55 """
56 Form to capture an NHS number.
57 """
59 nhs_number = SingleNhsNumberField(label="NHS number")
62class AbstractContactRequestForm(forms.Form):
63 """
64 Base class for contact requets.
65 """
67 def clean(self) -> None:
68 cleaned_data = super().clean()
70 study = cleaned_data.get("study")
71 if not study:
72 raise forms.ValidationError("Must specify study")
74 request_direct_approach = cleaned_data.get("request_direct_approach")
75 if request_direct_approach and not study.request_direct_approach:
76 raise forms.ValidationError(
77 "Study not approved for direct approach."
78 )
81class SuperuserSubmitContactRequestForm(AbstractContactRequestForm):
82 """
83 Form for superusers (the RDBM) to submit a contact request.
84 """
86 study = forms.ModelChoiceField(
87 queryset=Study.get_queryset_possible_contact_studies()
88 )
89 request_direct_approach = forms.BooleanField(
90 label="Request direct approach to patient, if available "
91 "(UNTICK to ask clinician for additional info)",
92 required=False,
93 initial=True,
94 )
95 nhs_numbers = MultipleNhsNumberAreaField(
96 label="NHS numbers", required=False
97 )
98 rids = MultipleWordAreaField(required=False)
99 mrids = MultipleWordAreaField(required=False)
101 def __init__(
102 self, *args, dbinfo: SingleResearchDatabase, **kwargs
103 ) -> None:
104 super().__init__(*args, **kwargs)
105 rids = self.fields["rids"] # type: MultipleWordAreaField
106 mrids = self.fields["mrids"] # type: MultipleWordAreaField
108 rids.label = f"{dbinfo.rid_field} ({dbinfo.rid_description}) (RID)"
109 mrids.label = f"{dbinfo.mrid_field} ({dbinfo.mrid_description}) (MRID)"
112class ResearcherSubmitContactRequestForm(AbstractContactRequestForm):
113 """
114 Form for researchers to submit a contact request for their own studies.
115 """
117 study = forms.ModelChoiceField(queryset=None)
118 # ... queryset changed below
119 request_direct_approach = forms.BooleanField(
120 label="Request direct approach to patient, if available "
121 "(UNTICK to ask clinician for additional info)",
122 required=False,
123 initial=True,
124 )
125 rids = MultipleWordAreaField(required=False)
126 mrids = MultipleWordAreaField(required=False)
128 def __init__(
129 self,
130 *args,
131 user: settings.AUTH_USER_MODEL,
132 dbinfo: SingleResearchDatabase,
133 **kwargs,
134 ) -> None:
135 super().__init__(*args, **kwargs)
136 study = self.fields["study"] # type: forms.ModelChoiceField
137 rids = self.fields["rids"] # type: MultipleWordAreaField
138 mrids = self.fields["mrids"] # type: MultipleWordAreaField
140 study.queryset = Study.filter_studies_for_researcher(
141 queryset=Study.get_queryset_possible_contact_studies(), user=user
142 )
143 # https://docs.djangoproject.com/en/1.8/ref/models/querysets/#field-lookups # noqa: E501
144 # https://stackoverflow.com/questions/5329586/django-modelchoicefield-filtering-query-set-and-setting-default-value-as-an-obj # noqa: E501
145 rids.label = f"{dbinfo.rid_field} ({dbinfo.rid_description}) (RID)"
146 mrids.label = f"{dbinfo.mrid_field} ({dbinfo.mrid_description}) (MRID)"
149class ClinicianSubmitContactRequestForm(AbstractContactRequestForm):
150 """
151 Form for clinician to request that a patient of their's gets contacted
152 about a study.
153 """
155 study = forms.ModelChoiceField(
156 queryset=Study.get_queryset_possible_contact_studies()
157 )
158 nhs_numbers = MultipleNhsNumberAreaField(
159 label="NHS numbers", required=False
160 )
161 rids = MultipleWordAreaField(required=False)
162 mrids = MultipleWordAreaField(required=False)
163 email = forms.EmailField(
164 required=True,
165 label=mark_safe(
166 "Please ensure we have the correct details for you." "<br />Email"
167 ),
168 )
169 signatory_title = forms.CharField(
170 required=False,
171 label="Title for signature (e.g. 'Consultant psychiatrist')",
172 )
173 title = forms.CharField(required=True, label="Title")
174 firstname = forms.CharField(required=True, label="First name")
175 lastname = forms.CharField(required=True, label="Last name")
176 let_rdbm_contact_pt = forms.BooleanField(
177 label="Get the database manager to contact the patient directly",
178 required=False,
179 )
181 def __init__(
182 self,
183 *args,
184 dbinfo: SingleResearchDatabase,
185 email_addr: str,
186 title: str,
187 firstname: str,
188 lastname: str,
189 **kwargs,
190 ) -> None:
191 super().__init__(*args, **kwargs)
192 rids = self.fields["rids"] # type: MultipleWordAreaField
193 mrids = self.fields["mrids"] # type: MultipleWordAreaField
195 rids.label = f"{dbinfo.rid_field} ({dbinfo.rid_description}) (RID)"
196 mrids.label = f"{dbinfo.mrid_field} ({dbinfo.mrid_description}) (MRID)"
197 email = self.fields["email"]
198 clinician_title = self.fields["title"]
199 first = self.fields["firstname"]
200 last = self.fields["lastname"]
201 email.initial = email_addr
202 clinician_title.initial = title
203 first.initial = firstname
204 last.initial = lastname
207class ClinicianResponseForm(forms.ModelForm):
208 """
209 Form for clinicians to respond to a contact request.
210 """
212 class Meta:
213 model = ClinicianResponse
214 fields = [
215 "token",
216 "email_choice",
217 "response",
218 "veto_reason",
219 "ineligible_reason",
220 "pt_uncontactable_reason",
221 "clinician_confirm_name",
222 ]
223 widgets = {
224 "token": forms.HiddenInput(),
225 "email_choice": forms.HiddenInput(),
226 }
229class TeamRepAdminForm(forms.ModelForm):
230 """
231 Custom form for the RDBM to edit team reps.
233 The purposes is so that we only ask the database for team information at
234 the point of use (and not e.g. to run database migrations from the command
235 line!).
236 """
238 class Meta:
239 model = TeamRep
240 fields = [
241 "team",
242 "user",
243 ]
245 def __init__(self, *args, **kwargs) -> None:
246 """
247 Set the possible teams.
248 """
249 super().__init__(*args, **kwargs)
250 self.fields["team"] = forms.ChoiceField(
251 choices=TeamInfo.team_choices()
252 )