Coverage for tasks/demoquestionnaire.py: 48%

137 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/tasks/demoquestionnaire.py 

3 

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

5 

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

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

8 

9 This file is part of CamCOPS. 

10 

11 CamCOPS 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 CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

23 

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

25 

26""" 

27 

28import datetime 

29from typing import Any, Optional, Type 

30 

31from pendulum import DateTime as Pendulum 

32from sqlalchemy.orm import Mapped, mapped_column 

33from sqlalchemy.sql.sqltypes import Float, Time, UnicodeText 

34 

35from camcops_server.cc_modules.cc_blob import Blob, blob_relationship 

36from camcops_server.cc_modules.cc_constants import CssClass 

37from camcops_server.cc_modules.cc_db import add_multiple_columns 

38from camcops_server.cc_modules.cc_html import answer 

39from camcops_server.cc_modules.cc_request import CamcopsRequest 

40from camcops_server.cc_modules.cc_sqla_coltypes import ( 

41 mapped_camcops_column, 

42 PendulumDateTimeAsIsoTextColType, 

43 DiagnosticCodeColType, 

44) 

45from camcops_server.cc_modules.cc_task import Task 

46 

47 

48# ============================================================================= 

49# TASKS: DEMO QUESTIONNAIRE 

50# ============================================================================= 

51 

52N_MCQ = 10 # 8 in v1; 10 in v2 

53N_MCQBOOL = 3 

54N_MULTIPLERESPONSE = 6 

55N_BOOLTEXT = 22 

56N_BOOLIMAGE = 2 

57N_PICKER = 2 

58N_SLIDER = 2 

59 

60 

61def divtest(divname: str) -> str: 

62 return f'<div class="{divname}">.{divname}</div>\n' 

63 

64 

65class DemoQuestionnaire( 

66 Task, 

67): 

68 """ 

69 Server implementation of the demo questionnaire task. 

70 """ 

71 

72 __tablename__ = "demoquestionnaire" 

73 shortname = "Demo" 

74 is_anonymous = True # type: ignore[assignment] 

75 

76 @classmethod 

77 def extend_columns(cls: Type["DemoQuestionnaire"], **kwargs: Any) -> None: 

78 add_multiple_columns(cls, "mcq", 1, N_MCQ) 

79 add_multiple_columns(cls, "mcqbool", 1, N_MCQBOOL) 

80 add_multiple_columns(cls, "multipleresponse", 1, N_MULTIPLERESPONSE) 

81 add_multiple_columns(cls, "booltext", 1, N_BOOLTEXT) 

82 add_multiple_columns(cls, "boolimage", 1, N_BOOLIMAGE) 

83 add_multiple_columns(cls, "picker", 1, N_PICKER) 

84 add_multiple_columns(cls, "slider", 1, N_SLIDER, Float) 

85 

86 mcqtext_1a: Mapped[Optional[str]] = mapped_column(UnicodeText) 

87 mcqtext_1b: Mapped[Optional[str]] = mapped_column(UnicodeText) 

88 mcqtext_2a: Mapped[Optional[str]] = mapped_column(UnicodeText) 

89 mcqtext_2b: Mapped[Optional[str]] = mapped_column(UnicodeText) 

90 mcqtext_3a: Mapped[Optional[str]] = mapped_column(UnicodeText) 

91 mcqtext_3b: Mapped[Optional[str]] = mapped_column(UnicodeText) 

92 typedvar_text: Mapped[Optional[str]] = mapped_column(UnicodeText) 

93 typedvar_text_multiline: Mapped[Optional[str]] = mapped_column(UnicodeText) 

94 typedvar_text_rich: Mapped[Optional[str]] = mapped_column( 

95 UnicodeText 

96 ) # v2 

97 typedvar_int: Mapped[Optional[int]] = mapped_column() 

98 typedvar_real: Mapped[Optional[float]] = mapped_column() 

99 date_only: Mapped[Optional[datetime.date]] = mapped_column() 

100 date_time: Mapped[Optional[Pendulum]] = mapped_column( 

101 PendulumDateTimeAsIsoTextColType 

102 ) 

103 thermometer: Mapped[Optional[int]] = mapped_column() 

104 diagnosticcode_code: Mapped[Optional[str]] = mapped_column( 

105 DiagnosticCodeColType 

106 ) 

107 diagnosticcode_description: Mapped[Optional[str]] = mapped_camcops_column( 

108 UnicodeText, 

109 exempt_from_anonymisation=True, 

110 ) 

111 diagnosticcode2_code: Mapped[Optional[str]] = mapped_column( 

112 DiagnosticCodeColType 

113 ) # v2 

114 diagnosticcode2_description: Mapped[Optional[str]] = mapped_camcops_column( 

115 UnicodeText, 

116 exempt_from_anonymisation=True, 

117 ) # v2 

118 photo_blobid: Mapped[Optional[int]] = mapped_camcops_column( 

119 is_blob_id_field=True, 

120 blob_relationship_attr_name="photo", 

121 ) 

122 # IGNORED. REMOVE WHEN ALL PRE-2.0.0 TABLETS GONE: 

123 photo_rotation: Mapped[Optional[int]] = ( 

124 mapped_column() 

125 ) # DEFUNCT as of v2.0.0 

126 canvas_blobid: Mapped[Optional[int]] = mapped_camcops_column( 

127 is_blob_id_field=True, 

128 blob_relationship_attr_name="canvas", 

129 ) 

130 canvas2_blobid: Mapped[Optional[int]] = mapped_camcops_column( 

131 is_blob_id_field=True, 

132 blob_relationship_attr_name="canvas2", 

133 ) 

134 spinbox_int: Mapped[Optional[int]] = mapped_column() # v2 

135 spinbox_real: Mapped[Optional[float]] = mapped_column() # v2 

136 time_only: Mapped[Optional[datetime.time]] = mapped_column( 

137 "time_only", Time 

138 ) # v2 

139 

140 photo = blob_relationship( # type: ignore[assignment] 

141 "DemoQuestionnaire", "photo_blobid" 

142 ) # type: Optional[Blob] 

143 canvas = blob_relationship( # type: ignore[assignment] 

144 "DemoQuestionnaire", "canvas_blobid" 

145 ) # type: Optional[Blob] 

146 canvas2 = blob_relationship( # type: ignore[assignment] 

147 "DemoQuestionnaire", "canvas2_blobid" 

148 ) # type: Optional[Blob] 

149 

150 @staticmethod 

151 def longname(req: "CamcopsRequest") -> str: 

152 _ = req.gettext 

153 return _("Demonstration Questionnaire") 

154 

155 # noinspection PyMethodOverriding 

156 @staticmethod 

157 def is_complete() -> bool: 

158 return True 

159 

160 def get_task_html(self, req: CamcopsRequest) -> str: 

161 h = f""" 

162 <div class="{CssClass.SUMMARY}"> 

163 <table class="{CssClass.SUMMARY}"> 

164 {self.get_is_complete_tr(req)} 

165 </table> 

166 </div> 

167 <div class="{CssClass.EXPLANATION}"> 

168 This is a demo questionnaire, containing no genuine 

169 information. 

170 </div> 

171 <table class="{CssClass.TASKDETAIL}"> 

172 <tr> 

173 <th width="50%">Question</th> 

174 <th width="50%">Answer</th> 

175 </tr> 

176 """ 

177 for i in range(1, N_MCQ + 1): 

178 h += self.get_twocol_val_row("mcq" + str(i)) 

179 for i in range(1, N_MCQBOOL + 1): 

180 h += self.get_twocol_bool_row(req, "mcqbool" + str(i)) 

181 for i in range(1, N_MULTIPLERESPONSE + 1): 

182 h += self.get_twocol_bool_row(req, "multipleresponse" + str(i)) 

183 for i in range(1, N_BOOLTEXT + 1): 

184 h += self.get_twocol_bool_row(req, "booltext" + str(i)) 

185 for i in range(1, N_BOOLIMAGE + 1): 

186 h += self.get_twocol_bool_row(req, "boolimage" + str(i)) 

187 for i in range(1, N_PICKER + 1): 

188 h += self.get_twocol_val_row("picker" + str(i)) 

189 for i in range(1, N_SLIDER + 1): 

190 h += self.get_twocol_val_row("slider" + str(i)) 

191 h += self.get_twocol_string_row("mcqtext_1a") 

192 h += self.get_twocol_string_row("mcqtext_1b") 

193 h += self.get_twocol_string_row("mcqtext_2a") 

194 h += self.get_twocol_string_row("mcqtext_2b") 

195 h += self.get_twocol_string_row("mcqtext_3a") 

196 h += self.get_twocol_string_row("mcqtext_3b") 

197 h += self.get_twocol_string_row("typedvar_text") 

198 h += self.get_twocol_string_row("typedvar_text_multiline") 

199 h += self.get_twocol_string_row("typedvar_text_rich") 

200 h += self.get_twocol_val_row("typedvar_int") 

201 h += self.get_twocol_val_row("typedvar_real") 

202 h += self.get_twocol_val_row("date_only") 

203 h += self.get_twocol_val_row("date_time") 

204 h += self.get_twocol_val_row("thermometer") 

205 h += self.get_twocol_string_row("diagnosticcode_code") 

206 h += self.get_twocol_string_row("diagnosticcode_description") 

207 h += self.get_twocol_string_row("diagnosticcode2_code") 

208 h += self.get_twocol_string_row("diagnosticcode2_description") 

209 # noinspection PyTypeChecker 

210 h += self.get_twocol_picture_row(self.photo, "photo") 

211 # noinspection PyTypeChecker 

212 h += self.get_twocol_picture_row(self.canvas, "canvas") 

213 # noinspection PyTypeChecker 

214 h += self.get_twocol_picture_row(self.canvas2, "canvas2") 

215 h += ( 

216 """ 

217 </table> 

218 

219 <div> 

220 In addition to the data (above), this task demonstrates 

221 HTML/CSS styles used in the CamCOPS views. 

222 </div> 

223 

224 <h1>Header 1</h1> 

225 <h2>Header 2</h2> 

226 <h3>Header 3</h3> 

227 

228 <div> 

229 Plain div with <sup>superscript</sup> and <sub>subscript</sub>. 

230 <br> 

231 Answers look like this: """ 

232 + answer("Answer") 

233 + """<br> 

234 Missing answers look liks this: """ 

235 + answer(None) 

236 + """<br> 

237 </div> 

238 """ 

239 ) 

240 h += divtest(CssClass.BAD_ID_POLICY_MILD) 

241 h += divtest(CssClass.BAD_ID_POLICY_SEVERE) 

242 h += divtest(CssClass.CLINICIAN) 

243 h += divtest(CssClass.COPYRIGHT) 

244 h += divtest(CssClass.ERROR) 

245 h += divtest(CssClass.EXPLANATION) 

246 h += divtest(CssClass.FOOTNOTES) 

247 h += divtest(CssClass.FORMTITLE) 

248 h += divtest(CssClass.GREEN) 

249 h += divtest(CssClass.HEADING) 

250 h += divtest(CssClass.IMPORTANT) 

251 h += divtest(CssClass.INCOMPLETE) 

252 h += divtest(CssClass.INDENTED) 

253 h += divtest(CssClass.LIVE_ON_TABLET) 

254 h += divtest(CssClass.NAVIGATION) 

255 h += divtest(CssClass.OFFICE) 

256 h += divtest(CssClass.PATIENT) 

257 h += divtest(CssClass.RESPONDENT) 

258 h += divtest(CssClass.SMALLPRINT) 

259 h += divtest(CssClass.SPECIALNOTE) 

260 h += divtest(CssClass.SUBHEADING) 

261 h += divtest(CssClass.SUBSUBHEADING) 

262 h += divtest(CssClass.SUMMARY) 

263 h += divtest(CssClass.SUPERUSER) 

264 h += divtest(CssClass.TASKHEADER) 

265 h += divtest(CssClass.TRACKERHEADER) 

266 h += divtest(CssClass.TRACKER_ALL_CONSISTENT) 

267 h += divtest(CssClass.WARNING) 

268 h += """ 

269 <table> 

270 <tr> 

271 <th>Standard table heading; column 1</th><th>Column 2</th> 

272 </tr> 

273 <tr> 

274 <td>Standard table row; column 1</td><td>Column 2</td> 

275 </tr> 

276 </table> 

277 

278 <div>Inlined <code>code looks like this</code>. 

279 

280 <div>There are some others, too.</div> 

281 """ 

282 return h