Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_constants.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

12 CamCOPS is free software: you can redistribute it and/or modify 

13 it under the terms of the GNU General Public License as published by 

14 the Free Software Foundation, either version 3 of the License, or 

15 (at your option) any later version. 

16 

17 CamCOPS is distributed in the hope that it will be useful, 

18 but WITHOUT ANY WARRANTY; without even the implied warranty of 

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

22 You should have received a copy of the GNU General Public License 

23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

24 

25=============================================================================== 

26 

27**Various constants.** 

28 

29""" 

30 

31# Helpful UTF-8 characters: ‘’ “” – — × • ≤ ≥ ≠ ± → 

32 

33import logging 

34import multiprocessing 

35import os 

36 

37from cardinal_pythonlib.randomness import create_base64encoded_randomness 

38from cardinal_pythonlib.sqlalchemy.session import make_mysql_url 

39 

40from camcops_server.cc_modules.cc_baseconstants import ( 

41 DEFAULT_EXTRA_STRINGS_DIR, 

42 LINUX_DEFAULT_LOCK_DIR, 

43 LINUX_DEFAULT_USER_DOWNLOAD_DIR, 

44 STATIC_ROOT_DIR, 

45) 

46from camcops_server.cc_modules.cc_language import DEFAULT_LOCALE 

47 

48 

49# ============================================================================= 

50# Number of ID numbers. Don't alter this lightly; influences database fields. 

51# ============================================================================= 

52 

53NUMBER_OF_IDNUMS_DEFUNCT = 8 # DEFUNCT BUT DO NOT REMOVE OR ALTER. EIGHT. 

54# ... In older versions: determined number of ID number fields. 

55# (Now this is arbitrary.) Still used to support old clients. 

56 

57 

58# ============================================================================= 

59# File types 

60# ============================================================================= 

61 

62class FileType(object): 

63 """ 

64 Used to represent output formats and their file extensions. 

65 """ 

66 HTML = "html" 

67 PDF = "pdf" 

68 XML = "xml" 

69 

70 

71# ============================================================================= 

72# Launching 

73# ============================================================================= 

74 

75DEFAULT_FLOWER_ADDRESS = "127.0.0.1" 

76DEFAULT_FLOWER_PORT = 5555 # http://docs.celeryproject.org/en/latest/userguide/monitoring.html # noqa 

77 

78 

79# ============================================================================= 

80# Webview constants 

81# ============================================================================= 

82 

83DEFAULT_ROWS_PER_PAGE = 25 

84DEVICE_NAME_FOR_SERVER = "server" # Do not alter. 

85USER_NAME_FOR_SYSTEM = "system" # Do not alter. 

86 

87# Address to download Windows/Mac versions: 

88GITHUB_RELEASES_URL = "https://github.com/RudolfCardinal/camcops/releases/" 

89 

90MINIMUM_PASSWORD_LENGTH = 10 

91 

92 

93# ============================================================================= 

94# Date formats 

95# ============================================================================= 

96 

97class DateFormat(object): 

98 """ 

99 Assorted date/time formats. 

100 """ 

101 SHORT_DATE = "%d %b %Y" # e.g. 24 Jul 2013 

102 LONG_DATE = "%d %B %Y" # e.g. 24 July 2013 

103 LONG_DATE_WITH_DAY = "%a %d %B %Y" # e.g. Wed 24 July 2013 

104 LONG_DATETIME = "%d %B %Y, %H:%M %z" # e.g. 24 July 2013, 20:04 +0100 

105 LONG_DATETIME_WITH_DAY = "%a %d %B %Y, %H:%M %z" # e.g. Wed 24 July 2013, 20:04 +0100 # noqa 

106 LONG_DATETIME_WITH_DAY_NO_TZ = "%a %d %B %Y, %H:%M" # e.g. Wed 24 July 2013, 20:04 # noqa 

107 SHORT_DATETIME_WITH_DAY_NO_TZ = "%a %d %b %Y, %H:%M" # e.g. Wed 24 Jul 2013, 20:04 # noqa 

108 LONG_DATETIME_SECONDS = "%d %B %Y, %H:%M:%S %z" 

109 SHORT_DATETIME = "%d %b %Y, %H:%M %z" # e.g. 24 Jul 2013, 20:04 +0100 

110 SHORT_DATETIME_NO_TZ = "%d %b %Y, %H:%M" # e.g. 24 Jul 2013, 20:04 

111 SHORT_DATETIME_SECONDS = "%d %b %Y, %H:%M:%S %z" # e.g. 24 Jul 2013, 20:04:23 +0100 # noqa 

112 HOURS_MINUTES = "%H:%M" # e.g. 20:04 

113 ISO8601 = "%Y-%m-%dT%H:%M:%S%z" # e.g. 2013-07-24T20:04:07+0100 

114 ISO8601_HUMANIZED_TO_MINUTES = "%Y-%m-%d %H:%M" # e.g. 2013-07-24 20:04 

115 ISO8601_HUMANIZED_TO_SECONDS = "%Y-%m-%d %H:%M:%S" # e.g. 2013-07-24 20:04:23 # noqa 

116 ISO8601_HUMANIZED_TO_SECONDS_TZ = "%Y-%m-%d %H:%M:%S %z" # e.g. 2013-07-24 20:04:23 +0100 # noqa 

117 ISO8601_DATE_ONLY = "%Y-%m-%d" # e.g. 2013-07-24 

118 FILENAME = "%Y-%m-%dT%H%M%S" # e.g. 2013-07-24T200459 

119 FILENAME_DATE_ONLY = "%Y-%m-%d" # e.g. 20130724 

120 HL7_DATETIME = "%Y%m%d%H%M%S%z" # e.g. 20130724200407+0100 

121 HL7_DATE = "%Y%m%d" # e.g. 20130724 

122 ERA = "%Y-%m-%dT%H:%M:%SZ" # e.g. 2013-07-24T20:03:07Z 

123 # http://www.hl7standards.com/blog/2008/07/25/hl7-time-zone-qualification/ 

124 RIO_EXPORT_UK = "%d/%m/%Y %H:%M" # e.g. 01/12/2014 09:45 

125 

126 

127# ============================================================================= 

128# Permitted values in fields: some common settings 

129# ============================================================================= 

130 

131class PV(object): 

132 """ 

133 Collections of permitted values. 

134 """ 

135 BIT = [0, 1] 

136 

137 

138NO_CHAR = 'N' 

139YES_CHAR = 'Y' 

140 

141# Database values: 

142SEX_FEMALE = "F" 

143SEX_MALE = "M" 

144SEX_OTHER_UNSPECIFIED = "X" 

145POSSIBLE_SEX_VALUES = [SEX_FEMALE, SEX_MALE, SEX_OTHER_UNSPECIFIED] 

146 

147 

148# ============================================================================= 

149# Field names/specifications 

150# ============================================================================= 

151 

152TABLET_ID_FIELD = "id" 

153MOVE_OFF_TABLET_FIELD = "_move_off_tablet" 

154CLIENT_DATE_FIELD = "when_last_modified" 

155 

156# Used for old client support, and TSV field names etc.: 

157FP_ID_NUM = "idnum" 

158FP_ID_DESC = "iddesc" 

159FP_ID_SHORT_DESC = "idshortdesc" 

160 

161# Additional fields for some exports: 

162EXTRA_IDNUM_FIELD_PREFIX = "_patient_idnum" 

163EXTRA_TASK_TABLENAME_FIELD = "_task_tablename" 

164EXTRA_TASK_SERVER_PK_FIELD = "_task_pk" 

165EXTRA_COMMENT_PREFIX = "(EXTRA) " 

166 

167 

168# ============================================================================= 

169# Other special values 

170# ============================================================================= 

171 

172# CAMCOPS_URL = "http://www.camcops.org/" 

173CAMCOPS_URL = "https://camcops.readthedocs.io/" 

174ERA_NOW = "NOW" # defines the current era in database records 

175 

176 

177# ============================================================================= 

178# PDF engine: now always "pdfkit". 

179# ============================================================================= 

180 

181# PDF_ENGINE = "xhtml2pdf" # working 

182PDF_ENGINE = "pdfkit" # working 

183# PDF_ENGINE = "weasyprint" # working but table <tr> element bugs 

184# ... value must be one of: xhtml2pdf, weasyprint, pdfkit 

185 

186 

187# ============================================================================= 

188# Simple constants for HTML/plots/display 

189# ============================================================================= 

190 

191class PlotDefaults(object): 

192 """ 

193 Defaults used with matplotlib plotting. 

194 """ 

195 DEFAULT_PLOT_DPI = 300 

196 

197 FULLWIDTH_PLOT_WIDTH = 6.7 # inches: full width is ~170mm 

198 

199 # zorder parameter: 

200 # - higher = on top 

201 # - defaults relate to the type of thing being plotted: 

202 # https://matplotlib.org/3.1.1/gallery/misc/zorder_demo.html 

203 # - Patch / PatchCollection = 1 

204 # - Line2D / LineCollection = 2 

205 # - Text = 3 

206 # - within a Line2D object (points and lines), the default is 

207 # "markers on top of lines" 

208 ZORDER_PRESET_LINES = 1 

209 ZORDER_PRESET_LABELS = 2 

210 ZORDER_DATA_LINES_POINTS = 3 # the default 

211 

212 

213class MatplotlibConstants(object): 

214 """ 

215 Constants used by matplotlib 

216 """ 

217 # https://matplotlib.org/tutorials/colors/colors.html 

218 COLOUR_BLACK = "k" 

219 COLOUR_BLUE = "b" 

220 COLOUR_GREEN = "g" 

221 COLOUR_GREY_50 = "0.5" 

222 COLOUR_GREY_90 = "0.9" # 0.9 is close to white (0 black, 1 white) 

223 COLOUR_RED = "r" 

224 

225 # https://matplotlib.org/gallery/lines_bars_and_markers/line_styles_reference.html # noqa 

226 # https://matplotlib.org/3.1.0/gallery/lines_bars_and_markers/linestyles.html # noqa 

227 LINESTYLE_DOTTED = ":" 

228 LINESTYLE_SOLID = "-" 

229 LINESTYLE_NONE = "None" 

230 

231 # https://matplotlib.org/3.1.1/api/markers_api.html 

232 MARKER_CIRCLE = "o" 

233 MARKER_NONE = "" # also "None", " " 

234 MARKER_PLUS = "+" 

235 MARKER_STAR = "*" 

236 

237 WHOLE_PANEL = 111 # as in: ax = fig.add_subplot(111) 

238 

239 

240# Debugging option 

241USE_SVG_IN_HTML = True # set to False for PNG debugging 

242 

243 

244# ============================================================================= 

245# CSS/HTML constants 

246# ============================================================================= 

247 

248CSS_PAGED_MEDIA = (PDF_ENGINE != "pdfkit") 

249 

250WKHTMLTOPDF_OPTIONS = { # dict for pdfkit 

251 "page-size": "A4", 

252 "margin-left": "20mm", 

253 "margin-right": "20mm", 

254 "margin-top": "21mm", # from paper edge down to top of content? 

255 # ... inaccurate 

256 "margin-bottom": "24mm", # from paper edge up to bottom of content? 

257 # ... inaccurate 

258 "header-spacing": "3", # mm, from content up to bottom of header 

259 "footer-spacing": "3", # mm, from content down to top of footer 

260 "quiet": "", # Suppress "Loading pages (1/6)" etc. 

261 "enable-local-file-access": "", 

262} 

263 

264 

265class CssClass(object): 

266 """ 

267 CSS names. 

268 

269 Values should match e.g. ``camcops_server/templates/css/css_base.mako``. 

270 """ 

271 BAD_ID_POLICY_MILD = "badidpolicy_mild" 

272 BAD_ID_POLICY_SEVERE = "badidpolicy_severe" 

273 BANNER = "banner" 

274 BANNER_REFERRAL_GENERAL_ADULT = "banner_referral_general_adult" 

275 BANNER_REFERRAL_OLD_AGE = "banner_referral_old_age" 

276 BANNER_REFERRAL_SUBSTANCE_MISUSE = "banner_referral_substance_misuse" 

277 CENTREGAP_TD = "centregap_td" 

278 CLINICIAN = "clinician" 

279 COPYRIGHT = "copyright" 

280 CTV_DATELIMIT_START = "ctv_datelimit_start" 

281 CTV_DATELIMIT_END = "ctv_datelimit_end" 

282 CTV_TASKHEADING = "ctv_taskheading" 

283 CTV_FIELDHEADING = "ctv_fieldheading" 

284 CTV_FIELDSUBHEADING = "ctv_fieldsubheading" 

285 CTV_FIELDDESCRIPTION = "ctv_fielddescription" 

286 CTV_FIELDCONTENT = "ctv_fieldcontent" 

287 CTV_WARNINGS = "ctv_warnings" 

288 ERROR = "error" 

289 EXPLANATION = "explanation" 

290 EXTRADETAIL = "extradetail" 

291 EXTRADETAIL2 = "extradetail2" 

292 FILTER = "filter" 

293 FILTERS = "filters" 

294 FIGURE = "figure" 

295 FOOTNOTES = "footnotes" 

296 FORMTITLE = "formtitle" 

297 GENERAL = "general" 

298 GREEN = "green" 

299 HANGINGINDENT = "hangingindent" 

300 HEADING = "heading" 

301 HIGHLIGHT = "highlight" 

302 IMAGE_TD = "image_td" 

303 IMPORTANT = "important" 

304 INCOMPLETE = "incomplete" 

305 INDENT = "indent" 

306 INDENTED = "indented" 

307 LIVE_ON_TABLET = "live_on_tablet" 

308 LOGO_LEFT = "logo_left" 

309 LOGO_RIGHT = "logo_right" 

310 NAVIGATION = "navigation" 

311 NOBORDER = "noborder" 

312 NOBORDERPHOTO = "noborderphoto" 

313 OFFICE = "office" 

314 PATIENT = "patient" 

315 PHOTO = "photo" 

316 PDF_LOGO_HEADER = "pdf_logo_header" 

317 QA_TABLE_HEADING = "qa_tableheading" 

318 RESPONDENT = "respondent" 

319 SIGNATURE = "signature" 

320 SIGNATURE_LABEL = "signature_label" 

321 SMALLPRINT = "smallprint" 

322 SPECIALNOTE = "specialnote" 

323 SUBHEADING = "subheading" 

324 SUBSUBHEADING = "subsubheading" 

325 SUMMARY = "summary" 

326 SUPERUSER = "superuser" 

327 TASKCONFIG = "taskconfig" 

328 TASKDETAIL = "taskdetail" 

329 TASKHEADER = "taskheader" 

330 TRACKERHEADER = "trackerheader" 

331 TRACKER_ALL_CONSISTENT = "tracker_all_consistent" 

332 WARNING = "warning" 

333 WEB_LOGO_HEADER = "web_logo_header" 

334 

335 

336# ============================================================================= 

337# Task constants 

338# ============================================================================= 

339 

340ANON_PATIENT = "XXXX" 

341DATA_COLLECTION_ONLY_DIV = """ 

342 <div class="copyright"> 

343 Reproduction of the original task/scale is not permitted. 

344 This is a data collection tool only; use it only in conjunction with 

345 a licensed copy of the original task. 

346 </div> 

347""" 

348DATA_COLLECTION_UNLESS_UPGRADED_DIV = """ 

349 <div class="copyright"> 

350 Reproduction of the original task/scale is not permitted as part of 

351 CamCOPS. This is a data collection tool only, unless the hosting 

352 institution has supplied task text via its own permissions. <b>Any such 

353 text, if shown here, is not part of CamCOPS, and copyright in 

354 it belongs to the original task’s copyright holder.</b> Use this data 

355 collection tool only in conjunction with a licensed copy of the 

356 original task. 

357 </div> 

358""" 

359ICD10_COPYRIGHT_DIV = """ 

360 <div class="copyright"> 

361 ICD-10 criteria: Copyright © 1992 World Health Organization. 

362 Used here with permission. 

363 </div> 

364""" 

365INVALID_VALUE = "[invalid_value]" 

366 

367TSV_PATIENT_FIELD_PREFIX = "_patient_" 

368 

369QUESTION = "Question" 

370 

371 

372# ============================================================================= 

373# Config constants 

374# ============================================================================= 

375 

376CONFIG_FILE_SITE_SECTION = "site" 

377CONFIG_FILE_SERVER_SECTION = "server" 

378CONFIG_FILE_EXPORT_SECTION = "export" 

379 

380 

381class ConfigParamSite(object): 

382 """ 

383 Parameters allowed in the main ``[site]`` section of the CamCOPS config 

384 file. 

385 """ 

386 ALLOW_INSECURE_COOKIES = "ALLOW_INSECURE_COOKIES" 

387 CAMCOPS_LOGO_FILE_ABSOLUTE = "CAMCOPS_LOGO_FILE_ABSOLUTE" 

388 CLIENT_API_LOGLEVEL = "CLIENT_API_LOGLEVEL" 

389 CTV_FILENAME_SPEC = "CTV_FILENAME_SPEC" 

390 DB_URL = "DB_URL" 

391 DB_ECHO = "DB_ECHO" 

392 DISABLE_PASSWORD_AUTOCOMPLETE = "DISABLE_PASSWORD_AUTOCOMPLETE" 

393 EMAIL_FROM = "EMAIL_FROM" 

394 EMAIL_HOST = "EMAIL_HOST" 

395 EMAIL_HOST_PASSWORD = "EMAIL_HOST_PASSWORD" 

396 EMAIL_HOST_USERNAME = "EMAIL_HOST_USERNAME" 

397 EMAIL_PORT = "EMAIL_PORT" 

398 EMAIL_REPLY_TO = "EMAIL_REPLY_TO" 

399 EMAIL_SENDER = "EMAIL_SENDER" 

400 EMAIL_USE_TLS = "EMAIL_USE_TLS" 

401 EXTRA_STRING_FILES = "EXTRA_STRING_FILES" 

402 LANGUAGE = "LANGUAGE" 

403 LOCAL_INSTITUTION_URL = "LOCAL_INSTITUTION_URL" 

404 LOCAL_LOGO_FILE_ABSOLUTE = "LOCAL_LOGO_FILE_ABSOLUTE" 

405 LOCKOUT_DURATION_INCREMENT_MINUTES = "LOCKOUT_DURATION_INCREMENT_MINUTES" 

406 LOCKOUT_THRESHOLD = "LOCKOUT_THRESHOLD" 

407 PASSWORD_CHANGE_FREQUENCY_DAYS = "PASSWORD_CHANGE_FREQUENCY_DAYS" 

408 PATIENT_SPEC = "PATIENT_SPEC" 

409 PATIENT_SPEC_IF_ANONYMOUS = "PATIENT_SPEC_IF_ANONYMOUS" 

410 PERMIT_IMMEDIATE_DOWNLOADS = "PERMIT_IMMEDIATE_DOWNLOADS" 

411 RESTRICTED_TASKS = "RESTRICTED_TASKS" 

412 SESSION_COOKIE_SECRET = "SESSION_COOKIE_SECRET" 

413 SESSION_TIMEOUT_MINUTES = "SESSION_TIMEOUT_MINUTES" 

414 SNOMED_TASK_XML_FILENAME = "SNOMED_TASK_XML_FILENAME" 

415 SNOMED_ICD9_XML_FILENAME = "SNOMED_ICD9_XML_FILENAME" 

416 SNOMED_ICD10_XML_FILENAME = "SNOMED_ICD10_XML_FILENAME" 

417 TASK_FILENAME_SPEC = "TASK_FILENAME_SPEC" 

418 TRACKER_FILENAME_SPEC = "TRACKER_FILENAME_SPEC" 

419 USER_DOWNLOAD_DIR = "USER_DOWNLOAD_DIR" 

420 USER_DOWNLOAD_FILE_LIFETIME_MIN = "USER_DOWNLOAD_FILE_LIFETIME_MIN" 

421 USER_DOWNLOAD_MAX_SPACE_MB = "USER_DOWNLOAD_MAX_SPACE_MB" 

422 WEBVIEW_LOGLEVEL = "WEBVIEW_LOGLEVEL" 

423 WKHTMLTOPDF_FILENAME = "WKHTMLTOPDF_FILENAME" 

424 

425 

426class ConfigParamServer(object): 

427 """ 

428 Parameters allowed in the web server (``[server]``) section of the CamCOPS 

429 config file. 

430 """ 

431 CHERRYPY_LOG_SCREEN = "CHERRYPY_LOG_SCREEN" 

432 CHERRYPY_ROOT_PATH = "CHERRYPY_ROOT_PATH" 

433 CHERRYPY_SERVER_NAME = "CHERRYPY_SERVER_NAME" 

434 CHERRYPY_THREADS_MAX = "CHERRYPY_THREADS_MAX" 

435 CHERRYPY_THREADS_START = "CHERRYPY_THREADS_START" 

436 DEBUG_REVERSE_PROXY = "DEBUG_REVERSE_PROXY" 

437 DEBUG_SHOW_GUNICORN_OPTIONS = "DEBUG_SHOW_GUNICORN_OPTIONS" 

438 DEBUG_TOOLBAR = "DEBUG_TOOLBAR" 

439 GUNICORN_DEBUG_RELOAD = "GUNICORN_DEBUG_RELOAD" 

440 GUNICORN_NUM_WORKERS = "GUNICORN_NUM_WORKERS" 

441 GUNICORN_TIMEOUT_S = "GUNICORN_TIMEOUT_S" 

442 HOST = "HOST" 

443 PORT = "PORT" 

444 PROXY_HTTP_HOST = "PROXY_HTTP_HOST" 

445 PROXY_REMOTE_ADDR = "PROXY_REMOTE_ADDR" 

446 PROXY_REWRITE_PATH_INFO = "PROXY_REWRITE_PATH_INFO" 

447 PROXY_SCRIPT_NAME = "PROXY_SCRIPT_NAME" 

448 PROXY_SERVER_NAME = "PROXY_SERVER_NAME" 

449 PROXY_SERVER_PORT = "PROXY_SERVER_PORT" 

450 PROXY_URL_SCHEME = "PROXY_URL_SCHEME" 

451 SHOW_REQUEST_IMMEDIATELY = "SHOW_REQUEST_IMMEDIATELY" 

452 SHOW_REQUESTS = "SHOW_REQUESTS" 

453 SHOW_RESPONSE = "SHOW_RESPONSE" 

454 SHOW_TIMING = "SHOW_TIMING" 

455 SSL_CERTIFICATE = "SSL_CERTIFICATE" 

456 SSL_PRIVATE_KEY = "SSL_PRIVATE_KEY" 

457 STATIC_CACHE_DURATION_S = "STATIC_CACHE_DURATION_S" 

458 TRUSTED_PROXY_HEADERS = "TRUSTED_PROXY_HEADERS" 

459 UNIX_DOMAIN_SOCKET = "UNIX_DOMAIN_SOCKET" 

460 

461 

462class ConfigParamExportGeneral(object): 

463 """ 

464 Parameters allowed in the ``[export]`` section of the CamCOPS config file. 

465 """ 

466 CELERY_BEAT_EXTRA_ARGS = "CELERY_BEAT_EXTRA_ARGS" 

467 CELERY_BEAT_SCHEDULE_DATABASE = "CELERY_BEAT_SCHEDULE_DATABASE" 

468 CELERY_BROKER_URL = "CELERY_BROKER_URL" 

469 CELERY_WORKER_EXTRA_ARGS = "CELERY_WORKER_EXTRA_ARGS" 

470 CELERY_EXPORT_TASK_RATE_LIMIT = "CELERY_EXPORT_TASK_RATE_LIMIT" 

471 EXPORT_LOCKDIR = "EXPORT_LOCKDIR" 

472 RECIPIENTS = "RECIPIENTS" 

473 SCHEDULE = "SCHEDULE" 

474 SCHEDULE_TIMEZONE = "SCHEDULE_TIMEZONE" 

475 

476 

477class ConfigParamExportRecipient(object): 

478 """ 

479 Possible configuration file parameters that relate to "export recipient" 

480 definitions. 

481 """ 

482 ALL_GROUPS = "ALL_GROUPS" 

483 DB_ADD_SUMMARIES = "DB_ADD_SUMMARIES" 

484 DB_ECHO = "DB_ECHO" 

485 DB_INCLUDE_BLOBS = "DB_INCLUDE_BLOBS" 

486 DB_PATIENT_ID_PER_ROW = "DB_PATIENT_ID_PER_ROW" 

487 DB_URL = "DB_URL" 

488 EMAIL_BCC = "EMAIL_BCC" 

489 EMAIL_BODY = "EMAIL_BODY" 

490 EMAIL_BODY_IS_HTML = "EMAIL_BODY_IS_HTML" 

491 EMAIL_CC = "EMAIL_CC" 

492 EMAIL_KEEP_MESSAGE = "EMAIL_KEEP_MESSAGE" 

493 EMAIL_RECIPIENTS = "EMAIL_RECIPIENTS" 

494 EMAIL_PATIENT_SPEC = "EMAIL_PATIENT_SPEC" 

495 EMAIL_PATIENT_SPEC_IF_ANONYMOUS = "EMAIL_PATIENT_SPEC_IF_ANONYMOUS" 

496 EMAIL_SUBJECT = "EMAIL_SUBJECT" 

497 EMAIL_TIMEOUT = "EMAIL_TIMEOUT" 

498 EMAIL_TO = "EMAIL_TO" 

499 END_DATETIME_UTC = "END_DATETIME_UTC" 

500 FHIR_API_URL = "FHIR_API_URL" 

501 FHIR_APP_SECRET = "FHIR_APP_SECRET" 

502 FHIR_LAUNCH_TOKEN = "FHIR_LAUNCH_TOKEN" 

503 FILE_EXPORT_RIO_METADATA = "FILE_EXPORT_RIO_METADATA" 

504 FILE_FILENAME_SPEC = "FILE_FILENAME_SPEC" 

505 FILE_MAKE_DIRECTORY = "FILE_MAKE_DIRECTORY" 

506 FILE_OVERWRITE_FILES = "FILE_OVERWRITE_FILES" 

507 FILE_PATIENT_SPEC = "FILE_PATIENT_SPEC" 

508 FILE_PATIENT_SPEC_IF_ANONYMOUS = "FILE_PATIENT_SPEC_IF_ANONYMOUS" 

509 FILE_SCRIPT_AFTER_EXPORT = "FILE_SCRIPT_AFTER_EXPORT" 

510 FINALIZED_ONLY = "FINALIZED_ONLY" 

511 GROUPS = "GROUPS" 

512 HL7_DEBUG_DIVERT_TO_FILE = "HL7_DEBUG_DIVERT_TO_FILE" 

513 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = "HL7_DEBUG_TREAT_DIVERTED_AS_SENT" 

514 HL7_HOST = "HL7_HOST" 

515 HL7_KEEP_MESSAGE = "HL7_KEEP_MESSAGE" 

516 HL7_KEEP_REPLY = "HL7_KEEP_REPLY" 

517 HL7_NETWORK_TIMEOUT_MS = "HL7_NETWORK_TIMEOUT_MS" 

518 HL7_PING_FIRST = "HL7_PING_FIRST" 

519 HL7_PORT = "HL7_PORT" 

520 IDNUM_AA_PREFIX = "IDNUM_AA_" # unusual; prefix not parameter 

521 IDNUM_TYPE_PREFIX = "IDNUM_TYPE_" # unusual; prefix not parameter 

522 INCLUDE_ANONYMOUS = "INCLUDE_ANONYMOUS" 

523 PRIMARY_IDNUM = "PRIMARY_IDNUM" 

524 PUSH = "PUSH" 

525 REDCAP_API_KEY = "REDCAP_API_KEY" 

526 REDCAP_API_URL = "REDCAP_API_URL" 

527 REDCAP_FIELDMAP_FILENAME = "REDCAP_FIELDMAP_FILENAME" 

528 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = "REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY" # noqa 

529 RIO_DOCUMENT_TYPE = "RIO_DOCUMENT_TYPE" 

530 RIO_IDNUM = "RIO_IDNUM" 

531 RIO_UPLOADING_USER = "RIO_UPLOADING_USER" 

532 START_DATETIME_UTC = "START_DATETIME_UTC" 

533 TASK_FORMAT = "TASK_FORMAT" 

534 TASKS = "TASKS" 

535 TRANSMISSION_METHOD = "TRANSMISSION_METHOD" 

536 XML_FIELD_COMMENTS = "XML_FIELD_COMMENTS" 

537 

538 

539class StandardPorts(object): 

540 """ 

541 Standard TCP port numbers. 

542 """ 

543 ALTERNATIVE_HTTP = 8000 

544 AMQP = 5672 

545 SMTP = 25 

546 SMTP_TLS = 587 

547 HL7_MLLP = 2575 

548 MYSQL = 3306 

549 

550 

551class DockerConstants(object): 

552 """ 

553 Constants for the Docker environment. 

554 """ 

555 # Directories 

556 DOCKER_CAMCOPS_ROOT_DIR = "/camcops" 

557 CONFIG_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "cfg") 

558 TMP_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "tmp") 

559 VENV_DIR = os.path.join(DOCKER_CAMCOPS_ROOT_DIR, "venv") 

560 

561 DEFAULT_USER_DOWNLOAD_DIR = os.path.join(TMP_DIR, "user_downloads") 

562 DEFAULT_LOCKDIR = os.path.join(TMP_DIR, "lock") 

563 

564 # Container (internal) names 

565 CONTAINER_RABBITMQ = "rabbitmq" 

566 CONTAINER_MYSQL = "mysql" 

567 

568 # Other 

569 CELERY_BROKER_URL = f"amqp://{CONTAINER_RABBITMQ}:{StandardPorts.AMQP}/" 

570 DEFAULT_MYSQL_CAMCOPS_USER = "camcops" 

571 HOST = "0.0.0.0" 

572 # ... not "localhost" or "127.0.0.1"; see 

573 # https://nickjanetakis.com/blog/docker-tip-54-fixing-connection-reset-by-peer-or-similar-errors # noqa 

574 

575 

576# ============================================================================= 

577# Configuration defaults 

578# ============================================================================= 

579 

580class ConfigDefaults(object): 

581 """ 

582 Contains default values for the config, plus some cosmetic defaults for 

583 generating specimen config files. 

584 

585 - Re ``CHERRYPY_THREADS_MAX``: beware the default MySQL connection limit of 

586 151; https://dev.mysql.com/doc/refman/5.7/en/too-many-connections.html 

587 """ 

588 # [site] section 

589 ALLOW_INSECURE_COOKIES = False 

590 CAMCOPS_LOGO_FILE_ABSOLUTE = os.path.join(STATIC_ROOT_DIR, 

591 "logo_camcops.png") 

592 CLIENT_API_LOGLEVEL = logging.INFO 

593 CLIENT_API_LOGLEVEL_TEXTFORMAT = "info" # should match CLIENT_API_LOGLEVEL 

594 DB_DATABASE = "camcops" # for demo configs only 

595 DB_ECHO = False 

596 DB_PORT = StandardPorts.MYSQL # for demo configs only 

597 DB_SERVER = "localhost" # for demo configs only 

598 DB_USER = "YYY_USERNAME_REPLACE_ME" # cosmetic; for demo configs only 

599 DB_PASSWORD = "ZZZ_PASSWORD_REPLACE_ME" # cosmetic; for demo configs only 

600 DISABLE_PASSWORD_AUTOCOMPLETE = True 

601 EMAIL_PORT = StandardPorts.SMTP_TLS 

602 EMAIL_USE_TLS = True 

603 EXTRA_STRING_FILES = os.path.join(DEFAULT_EXTRA_STRINGS_DIR, "*.xml") # cosmetic; for demo configs only # noqa 

604 LANGUAGE = DEFAULT_LOCALE 

605 LOCAL_INSTITUTION_URL = "http://www.camcops.org/" 

606 LOCAL_LOGO_FILE_ABSOLUTE = os.path.join(STATIC_ROOT_DIR, "logo_local.png") 

607 LOCKOUT_DURATION_INCREMENT_MINUTES = 10 

608 LOCKOUT_THRESHOLD = 10 

609 PASSWORD_CHANGE_FREQUENCY_DAYS = 0 # zero for never 

610 PATIENT_SPEC_IF_ANONYMOUS = "anonymous" 

611 PERMIT_IMMEDIATE_DOWNLOADS = False 

612 SESSION_TIMEOUT_MINUTES = 30 

613 USER_DOWNLOAD_DIR = LINUX_DEFAULT_USER_DOWNLOAD_DIR # for demo configs only # noqa 

614 USER_DOWNLOAD_FILE_LIFETIME_MIN = 60 

615 USER_DOWNLOAD_MAX_SPACE_MB = 100 

616 WEBVIEW_LOGLEVEL = logging.INFO 

617 WEBVIEW_LOGLEVEL_TEXTFORMAT = "info" # should match WEBVIEW_LOGLEVEL 

618 

619 # Not yet user-configurable 

620 PLOT_FONTSIZE = 8 

621 

622 # [server] section 

623 CHERRYPY_LOG_SCREEN = True 

624 CHERRYPY_ROOT_PATH = "/" 

625 CHERRYPY_SERVER_NAME = "localhost" 

626 CHERRYPY_THREADS_MAX = 100 

627 CHERRYPY_THREADS_START = 10 

628 DEBUG_REVERSE_PROXY = False 

629 DEBUG_SHOW_GUNICORN_OPTIONS = False 

630 DEBUG_TOOLBAR = False 

631 GUNICORN_DEBUG_RELOAD = False 

632 GUNICORN_NUM_WORKERS = 2 * multiprocessing.cpu_count() 

633 GUNICORN_TIMEOUT_S = 30 

634 HOST = "127.0.0.1" 

635 PORT = StandardPorts.ALTERNATIVE_HTTP 

636 PROXY_REWRITE_PATH_INFO = False 

637 SHOW_REQUEST_IMMEDIATELY = False 

638 SHOW_REQUESTS = False 

639 SHOW_RESPONSE = False 

640 SHOW_TIMING = False 

641 STATIC_CACHE_DURATION_S = 1 * 24 * 60 * 60 # 1 day, in seconds = 86400 

642 

643 # [export] section 

644 CELERY_BROKER_URL = "amqp://" 

645 CELERY_BEAT_SCHEDULE_DATABASE = os.path.join( 

646 LINUX_DEFAULT_LOCK_DIR, "camcops_celerybeat_schedule") # for demo configs only # noqa 

647 EXPORT_LOCKDIR = LINUX_DEFAULT_LOCK_DIR # for demo configs only 

648 SCHEDULE_TIMEZONE = "UTC" 

649 

650 # Individual export recipients 

651 # DB_ECHO: as above 

652 ALL_GROUPS = False 

653 DB_ADD_SUMMARIES = True 

654 DB_INCLUDE_BLOBS = True 

655 DB_PATIENT_ID_PER_ROW = False 

656 EMAIL_BODY_IS_HTML = False 

657 EMAIL_KEEP_MESSAGE = False 

658 FILE_EXPORT_RIO_METADATA = False 

659 FILE_MAKE_DIRECTORY = False 

660 FILE_OVERWRITE_FILES = False 

661 FILE_PATIENT_SPEC_IF_ANONYMOUS = "anonymous" 

662 FINALIZED_ONLY = True 

663 HL7_DEBUG_DIVERT_TO_FILE = False 

664 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = False 

665 HL7_KEEP_MESSAGE = False 

666 HL7_KEEP_REPLY = False 

667 HL7_NETWORK_TIMEOUT_MS = 10000 

668 HL7_PING_FIRST = True 

669 HL7_PORT = StandardPorts.HL7_MLLP 

670 INCLUDE_ANONYMOUS = False 

671 PUSH = False 

672 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = True 

673 TASK_FORMAT = FileType.PDF 

674 XML_FIELD_COMMENTS = True 

675 

676 def __init__(self, docker: bool = False) -> None: 

677 """ 

678 Args: 

679 docker: 

680 Amend defaults so it works within a Docker Compose application 

681 without much fiddling? 

682 

683 Defaults for use within Docker: 

684 

685 - Note that a URL to another container/service looks like 

686 ``protocol://container:port/``. Values here must match the Docker 

687 Compose file. 

688 """ 

689 self._docker = docker 

690 if docker: 

691 self.CELERY_BROKER_URL = DockerConstants.CELERY_BROKER_URL 

692 self.CELERY_BEAT_SCHEDULE_DATABASE = os.path.join( 

693 DockerConstants.DEFAULT_LOCKDIR, "camcops_celerybeat_schedule") 

694 self.DB_SERVER = DockerConstants.CONTAINER_MYSQL 

695 self.DB_USER = DockerConstants.DEFAULT_MYSQL_CAMCOPS_USER 

696 self.EXPORT_LOCKDIR = DockerConstants.DEFAULT_LOCKDIR 

697 self.HOST = DockerConstants.HOST 

698 self.USER_DOWNLOAD_DIR = DockerConstants.DEFAULT_USER_DOWNLOAD_DIR 

699 

700 @property 

701 def demo_db_url(self) -> str: 

702 """ 

703 The demonstration SQLAlchemy URL. 

704 """ 

705 # mysqlclient ("mysqldb") for Docker -- the C-based fast one 

706 # pymysql for standard installations -- fewer dependencies 

707 driver = "mysqldb" if self._docker else "pymysql" 

708 return make_mysql_url(driver=driver, 

709 host=self.DB_SERVER, 

710 port=self.DB_PORT, 

711 username=self.DB_USER, 

712 password=self.DB_PASSWORD, 

713 dbname=self.DB_DATABASE) 

714 

715 

716# ============================================================================= 

717# String length limits 

718# ============================================================================= 

719# 

720# Note: "191" relates to MySQL indexing of VARCHAR fields using utf8mb4; 

721# - https://stackoverflow.com/questions/6172798/ 

722# - https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html 

723# There are alternative workarounds, but these fields are OK at 191. 

724# 

725# Re commenting variables in Sphinx: 

726# - https://stackoverflow.com/questions/20227051/how-to-document-a-module-constant-in-python # noqa 

727# - http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#directive-autodata # noqa 

728# - URLs are a bit tricky; the colons get re-interpreted sometimes; it seems 

729# that inserting an extra colon after "#:", e.g. "#: : see http://somewhere" 

730# works. 

731# - If a comment needs "# noqa" for the linter, then make it a docstring, 

732# because it will appear in the Sphinx string. 

733 

734 

735class StringLengths: 

736 # ------------------------------------------------------------------------- 

737 # Primary 

738 # ------------------------------------------------------------------------- 

739 AUDIT_SOURCE_MAX_LEN = 20 #: our choice based on use in CamCOPS code 

740 

741 #: : See https://docs.python.org/3.7/library/codecs.html#standard-encodings. 

742 #: Probably ~18 so give it some headroom. 

743 CHARSET_MAX_LEN = 64 

744 

745 CURRENCY_MAX_LEN = 3 #: Can have Unicode symbols like € or text like "GBP" 

746 

747 DATABASE_TITLE_MIN_LEN = 1 

748 DATABASE_TITLE_MAX_LEN = 255 #: our choice 

749 

750 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index; 

751 #: must be compatible with tablet 

752 DEVICE_NAME_MAX_LEN = 191 

753 

754 #: : See https://en.wikipedia.org/wiki/Email_address. 

755 EMAIL_ADDRESS_MAX_LEN = 255 

756 

757 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index 

758 EXPORT_RECIPIENT_NAME_MIN_LEN = 1 

759 EXPORT_RECIPIENT_NAME_MAX_LEN = 191 

760 

761 #: Our choice 

762 FILTER_TEXT_MAX_LEN = 255 

763 

764 #: Our choice; used for user full names on the server 

765 FULLNAME_MAX_LEN = 255 

766 

767 #: Our choice 

768 FILESPEC_MAX_LEN = 255 

769 

770 GROUP_DESCRIPTION_MIN_LEN = 1 

771 #: Our choice 

772 GROUP_DESCRIPTION_MAX_LEN = 255 

773 

774 GROUP_NAME_MIN_LEN = 1 

775 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index 

776 GROUP_NAME_MAX_LEN = 191 

777 

778 HASHED_PW_MAX_LEN = 60 

779 """ 

780 : 

781 We use ``bcrypt``. Empirically, the length of its hashed output is: 

782 

783 .. code-block:: none 

784 

785 "$2a$" (4) 

786 cost parameter, e.g. "$09" for 9 rounds (3) 

787 b64-enc 128-bit salt (22) 

788 b64enc 184-bit hash (31) 

789 

790 ... total 60 

791 

792 See https://stackoverflow.com/questions/5881169/what-column-type-length-should-i-use-for-storing-a-bcrypt-hashed-password-in-a-d 

793 """ # noqa 

794 

795 HL7_AA_MAX_LEN = 20 

796 """ 

797 - The AA appears in Table 4.6 "Extended composite ID", p46-47 of 

798 hl7guide-1-4-2012-08.pdf 

799 - ... but is defined in Table 4.9 "Entity Identifier", p50, in which: 

800 

801 - component 2 is the Assigning Authority (see component 1) 

802 - component 2 is also a Namespace ID with a length of 20 

803 

804 - ... and multiple other examples of an Assigning Authority being one 

805 example of a Namespace ID 

806 

807 - ... and examples are in Table 0363 (p229 of the PDF), which are all 

808 3-char. 

809 

810 - ... and several other examples of "Namespace ID" being of length 1..20 

811 meaning 1-20. 

812 """ 

813 

814 HL7_ID_TYPE_MAX_LEN = 5 

815 """ 

816 Table 4.6 "Extended composite ID", p46-47 of hl7guide-1-4-2012-08.pdf, 

817 and Table 0203 "Identifier type", p204 of that PDF, in Appendix B. 

818 """ 

819 

820 HOSTNAME_MAX_LEN = 255 

821 """ 

822 FQDN; see 

823 https://stackoverflow.com/questions/8724954/what-is-the-maximum-number-of-characters-for-a-host-name-in-unix 

824 """ # noqa 

825 

826 ICD9_CODE_MAX_LEN = 6 

827 """ 

828 Longest is "xxx.xx"; thus, 6; see 

829 https://www.cms.gov/Medicare/Quality-Initiatives-Patient-Assessment-Instruments/HospitalQualityInits/Downloads/HospitalAppendix_F.pdf 

830 """ # noqa 

831 

832 #: longest is e.g. "F00.000"; "F10.202"; thus, 7 

833 ICD10_CODE_MAX_LEN = 7 

834 

835 #: Our choice 

836 ID_DESCRIPTOR_MAX_LEN = 255 

837 

838 #: Our choice 

839 ID_POLICY_MAX_LEN = 255 

840 

841 #: : See http://stackoverflow.com/questions/166132 

842 IP_ADDRESS_MAX_LEN = 45 

843 

844 ISO8601_DATETIME_STRING_MAX_LEN = 32 

845 """ 

846 Max length e.g. 

847 

848 .. code-block:: none 

849 

850 2013-07-24T20:04:07.123456+01:00 

851 1234567890123456789012345678901234567890 

852 

853 (with punctuation, T, microseconds, colon in timezone). 

854 """ 

855 

856 #: See :func:`cardinal_pythonlib.datetimefunc.duration_to_iso` 

857 ISO8601_DURATION_STRING_MAX_LEN = 29 

858 

859 #: Two-letter language, hyphen, 2/3-letter country 

860 LANGUAGE_CODE_MAX_LEN = 6 

861 

862 # LONGBLOB_LONGTEXT_MAX_LEN = (2 ** 32) - 1 

863 # ... https://dev.mysql.com/doc/refman/8.0/en/storage-requirements.html 

864 

865 #: See https://stackoverflow.com/questions/643690 

866 MIMETYPE_MAX_LEN = 255 

867 

868 #: For forename and surname, each; our choice but must match tablet 

869 PATIENT_NAME_MAX_LEN = 255 

870 

871 RFC_2822_DATE_MAX_LEN = 31 

872 """ 

873 e.g. ``Fri, 09 Nov 2001 01:08:47 -0000``; 3.3 in 

874 https://tools.ietf.org/html/rfc2822, assuming extra white space not added 

875 """ 

876 

877 #: for export; our choice based on use in CamCOPS code 

878 SENDING_FORMAT_MAX_LEN = 50 

879 

880 #: our choice; 64 bytes => 512 bits, which is a lot in 2017 

881 SESSION_TOKEN_MAX_BYTES = 64 

882 

883 SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE '' 

884 SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary 

885 

886 TABLENAME_MAX_LEN = 128 

887 """ 

888 For 

889 

890 - MySQL: 64 -- https://dev.mysql.com/doc/refman/5.7/en/identifiers.html 

891 - SQL Server: 128 -- https://msdn.microsoft.com/en-us/library/ms191240.aspx 

892 - Oracle: 32, then 128 from v12.2 (2017) 

893 """ 

894 

895 TASK_SUMMARY_TEXT_FIELD_DEFAULT_MAX_LEN = 50 

896 """ 

897 ... our choice, contains short strings like "normal", "abnormal", "severe". 

898 Easy to change, since it's only used when exporting summaries, and not in 

899 the core database. 

900 """ 

901 

902 #: Our choice 

903 URL_MAX_LEN = 255 

904 

905 USERNAME_CAMCOPS_MIN_LEN = 1 

906 #: 191 is the maximum for MySQL + InnoDB + VARCHAR + utf8mb4 + index 

907 USERNAME_CAMCOPS_MAX_LEN = 191 

908 

909 #: Our choice 

910 USERNAME_EXTERNAL_MAX_LEN = 255 

911 

912 # ------------------------------------------------------------------------- 

913 # Derived 

914 # ------------------------------------------------------------------------- 

915 

916 DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN) 

917 

918 SESSION_TOKEN_MAX_LEN = len( 

919 create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES))