Coverage for tasks/demoquestionnaire.py: 48%
137 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/tasks/demoquestionnaire.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
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.
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.
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/>.
24===============================================================================
26"""
28import datetime
29from typing import Any, Optional, Type
31from pendulum import DateTime as Pendulum
32from sqlalchemy.orm import Mapped, mapped_column
33from sqlalchemy.sql.sqltypes import Float, Time, UnicodeText
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
48# =============================================================================
49# TASKS: DEMO QUESTIONNAIRE
50# =============================================================================
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
61def divtest(divname: str) -> str:
62 return f'<div class="{divname}">.{divname}</div>\n'
65class DemoQuestionnaire(
66 Task,
67):
68 """
69 Server implementation of the demo questionnaire task.
70 """
72 __tablename__ = "demoquestionnaire"
73 shortname = "Demo"
74 is_anonymous = True # type: ignore[assignment]
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)
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
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]
150 @staticmethod
151 def longname(req: "CamcopsRequest") -> str:
152 _ = req.gettext
153 return _("Demonstration Questionnaire")
155 # noinspection PyMethodOverriding
156 @staticmethod
157 def is_complete() -> bool:
158 return True
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>
219 <div>
220 In addition to the data (above), this task demonstrates
221 HTML/CSS styles used in the CamCOPS views.
222 </div>
224 <h1>Header 1</h1>
225 <h2>Header 2</h2>
226 <h3>Header 3</h3>
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>
278 <div>Inlined <code>code looks like this</code>.
280 <div>There are some others, too.</div>
281 """
282 return h