Coverage for tasks/demoquestionnaire.py: 48%

1"""

2 camcops_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

28 import datetime

29 from typing import Any, Optional, Type

30

31 from pendulum import DateTime as Pendulum

32 from sqlalchemy.orm import Mapped, mapped_column

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

34

35 from camcops_server.cc_modules.cc_blob import Blob, blob_relationship

36 from camcops_server.cc_modules.cc_constants import CssClass

37 from camcops_server.cc_modules.cc_db import add_multiple_columns

38 from camcops_server.cc_modules.cc_html import answer

39 from camcops_server.cc_modules.cc_request import CamcopsRequest

40 from camcops_server.cc_modules.cc_sqla_coltypes import (

41 mapped_camcops_column,

42 PendulumDateTimeAsIsoTextColType,

43 DiagnosticCodeColType,

44)

45 from camcops_server.cc_modules.cc_task import Task

46

47

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

49# TASKS: DEMO QUESTIONNAIRE

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

51

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

53 N_MCQBOOL = 3

54 N_MULTIPLERESPONSE = 6

55 N_BOOLTEXT = 22

56 N_BOOLIMAGE = 2

57 N_PICKER = 2

58 N_SLIDER = 2

59

60

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

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

63

64

65 class 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