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

1""" 

2crate_anon/crateweb/consent/forms.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CRATE. 

10 

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. 

15 

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. 

20 

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/>. 

23 

24=============================================================================== 

25 

26**Django forms for the consent-to-contact system.** 

27 

28""" 

29 

30import logging 

31 

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 

40 

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) 

50 

51log = logging.getLogger(__name__) 

52 

53 

54class SingleNhsNumberForm(forms.Form): 

55 """ 

56 Form to capture an NHS number. 

57 """ 

58 

59 nhs_number = SingleNhsNumberField(label="NHS number") 

60 

61 

62class AbstractContactRequestForm(forms.Form): 

63 """ 

64 Base class for contact requets. 

65 """ 

66 

67 def clean(self) -> None: 

68 cleaned_data = super().clean() 

69 

70 study = cleaned_data.get("study") 

71 if not study: 

72 raise forms.ValidationError("Must specify study") 

73 

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 ) 

79 

80 

81class SuperuserSubmitContactRequestForm(AbstractContactRequestForm): 

82 """ 

83 Form for superusers (the RDBM) to submit a contact request. 

84 """ 

85 

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) 

100 

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 

107 

108 rids.label = f"{dbinfo.rid_field} ({dbinfo.rid_description}) (RID)" 

109 mrids.label = f"{dbinfo.mrid_field} ({dbinfo.mrid_description}) (MRID)" 

110 

111 

112class ResearcherSubmitContactRequestForm(AbstractContactRequestForm): 

113 """ 

114 Form for researchers to submit a contact request for their own studies. 

115 """ 

116 

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) 

127 

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 

139 

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)" 

147 

148 

149class ClinicianSubmitContactRequestForm(AbstractContactRequestForm): 

150 """ 

151 Form for clinician to request that a patient of their's gets contacted 

152 about a study. 

153 """ 

154 

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 ) 

180 

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 

194 

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 

205 

206 

207class ClinicianResponseForm(forms.ModelForm): 

208 """ 

209 Form for clinicians to respond to a contact request. 

210 """ 

211 

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 } 

227 

228 

229class TeamRepAdminForm(forms.ModelForm): 

230 """ 

231 Custom form for the RDBM to edit team reps. 

232 

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 """ 

237 

238 class Meta: 

239 model = TeamRep 

240 fields = [ 

241 "team", 

242 "user", 

243 ] 

244 

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 )