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 

87MINIMUM_PASSWORD_LENGTH = 10 

88 

89 

90# ============================================================================= 

91# Date formats 

92# ============================================================================= 

93 

94class DateFormat(object): 

95 """ 

96 Assorted date/time formats. 

97 """ 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

122 

123 

124# ============================================================================= 

125# Permitted values in fields: some common settings 

126# ============================================================================= 

127 

128class PV(object): 

129 """ 

130 Collections of permitted values. 

131 """ 

132 BIT = [0, 1] 

133 

134 

135NO_CHAR = 'N' 

136YES_CHAR = 'Y' 

137 

138# Database values: 

139SEX_FEMALE = "F" 

140SEX_MALE = "M" 

141SEX_OTHER_UNSPECIFIED = "X" 

142POSSIBLE_SEX_VALUES = [SEX_FEMALE, SEX_MALE, SEX_OTHER_UNSPECIFIED] 

143 

144 

145# ============================================================================= 

146# Field names/specifications 

147# ============================================================================= 

148 

149TABLET_ID_FIELD = "id" 

150MOVE_OFF_TABLET_FIELD = "_move_off_tablet" 

151CLIENT_DATE_FIELD = "when_last_modified" 

152 

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

154FP_ID_NUM = "idnum" 

155FP_ID_DESC = "iddesc" 

156FP_ID_SHORT_DESC = "idshortdesc" 

157 

158# Additional fields for some exports: 

159EXTRA_IDNUM_FIELD_PREFIX = "_patient_idnum" 

160EXTRA_TASK_TABLENAME_FIELD = "_task_tablename" 

161EXTRA_TASK_SERVER_PK_FIELD = "_task_pk" 

162EXTRA_COMMENT_PREFIX = "(EXTRA) " 

163 

164 

165# ============================================================================= 

166# Other special values 

167# ============================================================================= 

168 

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

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

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

172 

173 

174# ============================================================================= 

175# PDF engine: now always "pdfkit". 

176# ============================================================================= 

177 

178# PDF_ENGINE = "xhtml2pdf" # working 

179PDF_ENGINE = "pdfkit" # working 

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

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

182 

183 

184# ============================================================================= 

185# Simple constants for HTML/plots/display 

186# ============================================================================= 

187 

188class PlotDefaults(object): 

189 """ 

190 Defaults used with matplotlib plotting. 

191 """ 

192 DEFAULT_PLOT_DPI = 300 

193 

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

195 

196 # zorder parameter: 

197 # - higher = on top 

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

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

200 # - Patch / PatchCollection = 1 

201 # - Line2D / LineCollection = 2 

202 # - Text = 3 

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

204 # "markers on top of lines" 

205 ZORDER_PRESET_LINES = 1 

206 ZORDER_PRESET_LABELS = 2 

207 ZORDER_DATA_LINES_POINTS = 3 # the default 

208 

209 

210class MatplotlibConstants(object): 

211 """ 

212 Constants used by matplotlib 

213 """ 

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

215 COLOUR_BLACK = "k" 

216 COLOUR_BLUE = "b" 

217 COLOUR_GREEN = "g" 

218 COLOUR_GREY_50 = "0.5" 

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

220 COLOUR_RED = "r" 

221 

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

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

224 LINESTYLE_DOTTED = ":" 

225 LINESTYLE_SOLID = "-" 

226 LINESTYLE_NONE = "None" 

227 

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

229 MARKER_CIRCLE = "o" 

230 MARKER_NONE = "" # also "None", " " 

231 MARKER_PLUS = "+" 

232 MARKER_STAR = "*" 

233 

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

235 

236 

237# Debugging option 

238USE_SVG_IN_HTML = True # set to False for PNG debugging 

239 

240 

241# ============================================================================= 

242# CSS/HTML constants 

243# ============================================================================= 

244 

245CSS_PAGED_MEDIA = (PDF_ENGINE != "pdfkit") 

246 

247WKHTMLTOPDF_OPTIONS = { # dict for pdfkit 

248 "page-size": "A4", 

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

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

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

252 # ... inaccurate 

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

254 # ... inaccurate 

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

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

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

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

259} 

260 

261 

262class CssClass(object): 

263 """ 

264 CSS names. 

265 

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

267 """ 

268 BAD_ID_POLICY_MILD = "badidpolicy_mild" 

269 BAD_ID_POLICY_SEVERE = "badidpolicy_severe" 

270 BANNER = "banner" 

271 BANNER_REFERRAL_GENERAL_ADULT = "banner_referral_general_adult" 

272 BANNER_REFERRAL_OLD_AGE = "banner_referral_old_age" 

273 BANNER_REFERRAL_SUBSTANCE_MISUSE = "banner_referral_substance_misuse" 

274 CENTREGAP_TD = "centregap_td" 

275 CLINICIAN = "clinician" 

276 COPYRIGHT = "copyright" 

277 CTV_DATELIMIT_START = "ctv_datelimit_start" 

278 CTV_DATELIMIT_END = "ctv_datelimit_end" 

279 CTV_TASKHEADING = "ctv_taskheading" 

280 CTV_FIELDHEADING = "ctv_fieldheading" 

281 CTV_FIELDSUBHEADING = "ctv_fieldsubheading" 

282 CTV_FIELDDESCRIPTION = "ctv_fielddescription" 

283 CTV_FIELDCONTENT = "ctv_fieldcontent" 

284 CTV_WARNINGS = "ctv_warnings" 

285 ERROR = "error" 

286 EXPLANATION = "explanation" 

287 EXTRADETAIL = "extradetail" 

288 EXTRADETAIL2 = "extradetail2" 

289 FILTER = "filter" 

290 FILTERS = "filters" 

291 FIGURE = "figure" 

292 FOOTNOTES = "footnotes" 

293 FORMTITLE = "formtitle" 

294 GENERAL = "general" 

295 GREEN = "green" 

296 HANGINGINDENT = "hangingindent" 

297 HEADING = "heading" 

298 HIGHLIGHT = "highlight" 

299 IMAGE_TD = "image_td" 

300 IMPORTANT = "important" 

301 INCOMPLETE = "incomplete" 

302 INDENT = "indent" 

303 INDENTED = "indented" 

304 LIVE_ON_TABLET = "live_on_tablet" 

305 LOGO_LEFT = "logo_left" 

306 LOGO_RIGHT = "logo_right" 

307 NAVIGATION = "navigation" 

308 NOBORDER = "noborder" 

309 NOBORDERPHOTO = "noborderphoto" 

310 OFFICE = "office" 

311 PATIENT = "patient" 

312 PHOTO = "photo" 

313 PDF_LOGO_HEADER = "pdf_logo_header" 

314 QA_TABLE_HEADING = "qa_tableheading" 

315 RESPONDENT = "respondent" 

316 SIGNATURE = "signature" 

317 SIGNATURE_LABEL = "signature_label" 

318 SMALLPRINT = "smallprint" 

319 SPECIALNOTE = "specialnote" 

320 SUBHEADING = "subheading" 

321 SUBSUBHEADING = "subsubheading" 

322 SUMMARY = "summary" 

323 SUPERUSER = "superuser" 

324 TASKCONFIG = "taskconfig" 

325 TASKDETAIL = "taskdetail" 

326 TASKHEADER = "taskheader" 

327 TRACKERHEADER = "trackerheader" 

328 TRACKER_ALL_CONSISTENT = "tracker_all_consistent" 

329 WARNING = "warning" 

330 WEB_LOGO_HEADER = "web_logo_header" 

331 

332 

333# ============================================================================= 

334# Task constants 

335# ============================================================================= 

336 

337ANON_PATIENT = "XXXX" 

338DATA_COLLECTION_ONLY_DIV = """ 

339 <div class="copyright"> 

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

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

342 a licensed copy of the original task. 

343 </div> 

344""" 

345DATA_COLLECTION_UNLESS_UPGRADED_DIV = """ 

346 <div class="copyright"> 

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

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

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

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

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

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

353 original task. 

354 </div> 

355""" 

356ICD10_COPYRIGHT_DIV = """ 

357 <div class="copyright"> 

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

359 Used here with permission. 

360 </div> 

361""" 

362INVALID_VALUE = "[invalid_value]" 

363 

364TSV_PATIENT_FIELD_PREFIX = "_patient_" 

365 

366QUESTION = "Question" 

367 

368 

369# ============================================================================= 

370# Config constants 

371# ============================================================================= 

372 

373CONFIG_FILE_SITE_SECTION = "site" 

374CONFIG_FILE_SERVER_SECTION = "server" 

375CONFIG_FILE_EXPORT_SECTION = "export" 

376 

377 

378class ConfigParamSite(object): 

379 """ 

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

381 file. 

382 """ 

383 ALLOW_INSECURE_COOKIES = "ALLOW_INSECURE_COOKIES" 

384 CAMCOPS_LOGO_FILE_ABSOLUTE = "CAMCOPS_LOGO_FILE_ABSOLUTE" 

385 CLIENT_API_LOGLEVEL = "CLIENT_API_LOGLEVEL" 

386 CTV_FILENAME_SPEC = "CTV_FILENAME_SPEC" 

387 DB_URL = "DB_URL" 

388 DB_ECHO = "DB_ECHO" 

389 DISABLE_PASSWORD_AUTOCOMPLETE = "DISABLE_PASSWORD_AUTOCOMPLETE" 

390 EMAIL_FROM = "EMAIL_FROM" 

391 EMAIL_HOST = "EMAIL_HOST" 

392 EMAIL_HOST_PASSWORD = "EMAIL_HOST_PASSWORD" 

393 EMAIL_HOST_USERNAME = "EMAIL_HOST_USERNAME" 

394 EMAIL_PORT = "EMAIL_PORT" 

395 EMAIL_REPLY_TO = "EMAIL_REPLY_TO" 

396 EMAIL_SENDER = "EMAIL_SENDER" 

397 EMAIL_USE_TLS = "EMAIL_USE_TLS" 

398 EXTRA_STRING_FILES = "EXTRA_STRING_FILES" 

399 LANGUAGE = "LANGUAGE" 

400 LOCAL_INSTITUTION_URL = "LOCAL_INSTITUTION_URL" 

401 LOCAL_LOGO_FILE_ABSOLUTE = "LOCAL_LOGO_FILE_ABSOLUTE" 

402 LOCKOUT_DURATION_INCREMENT_MINUTES = "LOCKOUT_DURATION_INCREMENT_MINUTES" 

403 LOCKOUT_THRESHOLD = "LOCKOUT_THRESHOLD" 

404 PASSWORD_CHANGE_FREQUENCY_DAYS = "PASSWORD_CHANGE_FREQUENCY_DAYS" 

405 PATIENT_SPEC = "PATIENT_SPEC" 

406 PATIENT_SPEC_IF_ANONYMOUS = "PATIENT_SPEC_IF_ANONYMOUS" 

407 PERMIT_IMMEDIATE_DOWNLOADS = "PERMIT_IMMEDIATE_DOWNLOADS" 

408 RESTRICTED_TASKS = "RESTRICTED_TASKS" 

409 SESSION_COOKIE_SECRET = "SESSION_COOKIE_SECRET" 

410 SESSION_TIMEOUT_MINUTES = "SESSION_TIMEOUT_MINUTES" 

411 SNOMED_TASK_XML_FILENAME = "SNOMED_TASK_XML_FILENAME" 

412 SNOMED_ICD9_XML_FILENAME = "SNOMED_ICD9_XML_FILENAME" 

413 SNOMED_ICD10_XML_FILENAME = "SNOMED_ICD10_XML_FILENAME" 

414 TASK_FILENAME_SPEC = "TASK_FILENAME_SPEC" 

415 TRACKER_FILENAME_SPEC = "TRACKER_FILENAME_SPEC" 

416 USER_DOWNLOAD_DIR = "USER_DOWNLOAD_DIR" 

417 USER_DOWNLOAD_FILE_LIFETIME_MIN = "USER_DOWNLOAD_FILE_LIFETIME_MIN" 

418 USER_DOWNLOAD_MAX_SPACE_MB = "USER_DOWNLOAD_MAX_SPACE_MB" 

419 WEBVIEW_LOGLEVEL = "WEBVIEW_LOGLEVEL" 

420 WKHTMLTOPDF_FILENAME = "WKHTMLTOPDF_FILENAME" 

421 

422 

423class ConfigParamServer(object): 

424 """ 

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

426 config file. 

427 """ 

428 CHERRYPY_LOG_SCREEN = "CHERRYPY_LOG_SCREEN" 

429 CHERRYPY_ROOT_PATH = "CHERRYPY_ROOT_PATH" 

430 CHERRYPY_SERVER_NAME = "CHERRYPY_SERVER_NAME" 

431 CHERRYPY_THREADS_MAX = "CHERRYPY_THREADS_MAX" 

432 CHERRYPY_THREADS_START = "CHERRYPY_THREADS_START" 

433 DEBUG_REVERSE_PROXY = "DEBUG_REVERSE_PROXY" 

434 DEBUG_SHOW_GUNICORN_OPTIONS = "DEBUG_SHOW_GUNICORN_OPTIONS" 

435 DEBUG_TOOLBAR = "DEBUG_TOOLBAR" 

436 GUNICORN_DEBUG_RELOAD = "GUNICORN_DEBUG_RELOAD" 

437 GUNICORN_NUM_WORKERS = "GUNICORN_NUM_WORKERS" 

438 GUNICORN_TIMEOUT_S = "GUNICORN_TIMEOUT_S" 

439 HOST = "HOST" 

440 PORT = "PORT" 

441 PROXY_HTTP_HOST = "PROXY_HTTP_HOST" 

442 PROXY_REMOTE_ADDR = "PROXY_REMOTE_ADDR" 

443 PROXY_REWRITE_PATH_INFO = "PROXY_REWRITE_PATH_INFO" 

444 PROXY_SCRIPT_NAME = "PROXY_SCRIPT_NAME" 

445 PROXY_SERVER_NAME = "PROXY_SERVER_NAME" 

446 PROXY_SERVER_PORT = "PROXY_SERVER_PORT" 

447 PROXY_URL_SCHEME = "PROXY_URL_SCHEME" 

448 SHOW_REQUEST_IMMEDIATELY = "SHOW_REQUEST_IMMEDIATELY" 

449 SHOW_REQUESTS = "SHOW_REQUESTS" 

450 SHOW_RESPONSE = "SHOW_RESPONSE" 

451 SHOW_TIMING = "SHOW_TIMING" 

452 SSL_CERTIFICATE = "SSL_CERTIFICATE" 

453 SSL_PRIVATE_KEY = "SSL_PRIVATE_KEY" 

454 STATIC_CACHE_DURATION_S = "STATIC_CACHE_DURATION_S" 

455 TRUSTED_PROXY_HEADERS = "TRUSTED_PROXY_HEADERS" 

456 UNIX_DOMAIN_SOCKET = "UNIX_DOMAIN_SOCKET" 

457 

458 

459class ConfigParamExportGeneral(object): 

460 """ 

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

462 """ 

463 CELERY_BEAT_EXTRA_ARGS = "CELERY_BEAT_EXTRA_ARGS" 

464 CELERY_BEAT_SCHEDULE_DATABASE = "CELERY_BEAT_SCHEDULE_DATABASE" 

465 CELERY_BROKER_URL = "CELERY_BROKER_URL" 

466 CELERY_WORKER_EXTRA_ARGS = "CELERY_WORKER_EXTRA_ARGS" 

467 CELERY_EXPORT_TASK_RATE_LIMIT = "CELERY_EXPORT_TASK_RATE_LIMIT" 

468 EXPORT_LOCKDIR = "EXPORT_LOCKDIR" 

469 RECIPIENTS = "RECIPIENTS" 

470 SCHEDULE = "SCHEDULE" 

471 SCHEDULE_TIMEZONE = "SCHEDULE_TIMEZONE" 

472 

473 

474class ConfigParamExportRecipient(object): 

475 """ 

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

477 definitions. 

478 """ 

479 ALL_GROUPS = "ALL_GROUPS" 

480 DB_ADD_SUMMARIES = "DB_ADD_SUMMARIES" 

481 DB_ECHO = "DB_ECHO" 

482 DB_INCLUDE_BLOBS = "DB_INCLUDE_BLOBS" 

483 DB_PATIENT_ID_PER_ROW = "DB_PATIENT_ID_PER_ROW" 

484 DB_URL = "DB_URL" 

485 EMAIL_BCC = "EMAIL_BCC" 

486 EMAIL_BODY = "EMAIL_BODY" 

487 EMAIL_BODY_IS_HTML = "EMAIL_BODY_IS_HTML" 

488 EMAIL_CC = "EMAIL_CC" 

489 EMAIL_KEEP_MESSAGE = "EMAIL_KEEP_MESSAGE" 

490 EMAIL_RECIPIENTS = "EMAIL_RECIPIENTS" 

491 EMAIL_PATIENT_SPEC = "EMAIL_PATIENT_SPEC" 

492 EMAIL_PATIENT_SPEC_IF_ANONYMOUS = "EMAIL_PATIENT_SPEC_IF_ANONYMOUS" 

493 EMAIL_SUBJECT = "EMAIL_SUBJECT" 

494 EMAIL_TIMEOUT = "EMAIL_TIMEOUT" 

495 EMAIL_TO = "EMAIL_TO" 

496 END_DATETIME_UTC = "END_DATETIME_UTC" 

497 FHIR_API_URL = "FHIR_API_URL" 

498 FHIR_APP_SECRET = "FHIR_APP_SECRET" 

499 FHIR_LAUNCH_TOKEN = "FHIR_LAUNCH_TOKEN" 

500 FILE_EXPORT_RIO_METADATA = "FILE_EXPORT_RIO_METADATA" 

501 FILE_FILENAME_SPEC = "FILE_FILENAME_SPEC" 

502 FILE_MAKE_DIRECTORY = "FILE_MAKE_DIRECTORY" 

503 FILE_OVERWRITE_FILES = "FILE_OVERWRITE_FILES" 

504 FILE_PATIENT_SPEC = "FILE_PATIENT_SPEC" 

505 FILE_PATIENT_SPEC_IF_ANONYMOUS = "FILE_PATIENT_SPEC_IF_ANONYMOUS" 

506 FILE_SCRIPT_AFTER_EXPORT = "FILE_SCRIPT_AFTER_EXPORT" 

507 FINALIZED_ONLY = "FINALIZED_ONLY" 

508 GROUPS = "GROUPS" 

509 HL7_DEBUG_DIVERT_TO_FILE = "HL7_DEBUG_DIVERT_TO_FILE" 

510 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = "HL7_DEBUG_TREAT_DIVERTED_AS_SENT" 

511 HL7_HOST = "HL7_HOST" 

512 HL7_KEEP_MESSAGE = "HL7_KEEP_MESSAGE" 

513 HL7_KEEP_REPLY = "HL7_KEEP_REPLY" 

514 HL7_NETWORK_TIMEOUT_MS = "HL7_NETWORK_TIMEOUT_MS" 

515 HL7_PING_FIRST = "HL7_PING_FIRST" 

516 HL7_PORT = "HL7_PORT" 

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

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

519 INCLUDE_ANONYMOUS = "INCLUDE_ANONYMOUS" 

520 PRIMARY_IDNUM = "PRIMARY_IDNUM" 

521 PUSH = "PUSH" 

522 REDCAP_API_KEY = "REDCAP_API_KEY" 

523 REDCAP_API_URL = "REDCAP_API_URL" 

524 REDCAP_FIELDMAP_FILENAME = "REDCAP_FIELDMAP_FILENAME" 

525 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = "REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY" # noqa 

526 RIO_DOCUMENT_TYPE = "RIO_DOCUMENT_TYPE" 

527 RIO_IDNUM = "RIO_IDNUM" 

528 RIO_UPLOADING_USER = "RIO_UPLOADING_USER" 

529 START_DATETIME_UTC = "START_DATETIME_UTC" 

530 TASK_FORMAT = "TASK_FORMAT" 

531 TASKS = "TASKS" 

532 TRANSMISSION_METHOD = "TRANSMISSION_METHOD" 

533 XML_FIELD_COMMENTS = "XML_FIELD_COMMENTS" 

534 

535 

536class StandardPorts(object): 

537 """ 

538 Standard TCP port numbers. 

539 """ 

540 ALTERNATIVE_HTTP = 8000 

541 AMQP = 5672 

542 SMTP = 25 

543 SMTP_TLS = 587 

544 HL7_MLLP = 2575 

545 MYSQL = 3306 

546 

547 

548class DockerConstants(object): 

549 """ 

550 Constants for the Docker environment. 

551 """ 

552 # Directories 

553 DOCKER_CAMCOPS_ROOT_DIR = "/camcops" 

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

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

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

557 

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

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

560 

561 # Container (internal) names 

562 CONTAINER_RABBITMQ = "rabbitmq" 

563 CONTAINER_MYSQL = "mysql" 

564 

565 # Other 

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

567 DEFAULT_MYSQL_CAMCOPS_USER = "camcops" 

568 HOST = "0.0.0.0" 

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

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

571 

572 

573# ============================================================================= 

574# Configuration defaults 

575# ============================================================================= 

576 

577class ConfigDefaults(object): 

578 """ 

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

580 generating specimen config files. 

581 

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

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

584 """ 

585 # [site] section 

586 ALLOW_INSECURE_COOKIES = False 

587 CAMCOPS_LOGO_FILE_ABSOLUTE = os.path.join(STATIC_ROOT_DIR, 

588 "logo_camcops.png") 

589 CLIENT_API_LOGLEVEL = logging.INFO 

590 CLIENT_API_LOGLEVEL_TEXTFORMAT = "info" # should match CLIENT_API_LOGLEVEL 

591 DB_DATABASE = "camcops" # for demo configs only 

592 DB_ECHO = False 

593 DB_PORT = StandardPorts.MYSQL # for demo configs only 

594 DB_SERVER = "localhost" # for demo configs only 

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

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

597 DISABLE_PASSWORD_AUTOCOMPLETE = True 

598 EMAIL_PORT = StandardPorts.SMTP_TLS 

599 EMAIL_USE_TLS = True 

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

601 LANGUAGE = DEFAULT_LOCALE 

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

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

604 LOCKOUT_DURATION_INCREMENT_MINUTES = 10 

605 LOCKOUT_THRESHOLD = 10 

606 PASSWORD_CHANGE_FREQUENCY_DAYS = 0 # zero for never 

607 PATIENT_SPEC_IF_ANONYMOUS = "anonymous" 

608 PERMIT_IMMEDIATE_DOWNLOADS = False 

609 SESSION_TIMEOUT_MINUTES = 30 

610 USER_DOWNLOAD_DIR = LINUX_DEFAULT_USER_DOWNLOAD_DIR # for demo configs only # noqa 

611 USER_DOWNLOAD_FILE_LIFETIME_MIN = 60 

612 USER_DOWNLOAD_MAX_SPACE_MB = 100 

613 WEBVIEW_LOGLEVEL = logging.INFO 

614 WEBVIEW_LOGLEVEL_TEXTFORMAT = "info" # should match WEBVIEW_LOGLEVEL 

615 

616 # Not yet user-configurable 

617 PLOT_FONTSIZE = 8 

618 

619 # [server] section 

620 CHERRYPY_LOG_SCREEN = True 

621 CHERRYPY_ROOT_PATH = "/" 

622 CHERRYPY_SERVER_NAME = "localhost" 

623 CHERRYPY_THREADS_MAX = 100 

624 CHERRYPY_THREADS_START = 10 

625 DEBUG_REVERSE_PROXY = False 

626 DEBUG_SHOW_GUNICORN_OPTIONS = False 

627 DEBUG_TOOLBAR = False 

628 GUNICORN_DEBUG_RELOAD = False 

629 GUNICORN_NUM_WORKERS = 2 * multiprocessing.cpu_count() 

630 GUNICORN_TIMEOUT_S = 30 

631 HOST = "127.0.0.1" 

632 PORT = StandardPorts.ALTERNATIVE_HTTP 

633 PROXY_REWRITE_PATH_INFO = False 

634 SHOW_REQUEST_IMMEDIATELY = False 

635 SHOW_REQUESTS = False 

636 SHOW_RESPONSE = False 

637 SHOW_TIMING = False 

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

639 

640 # [export] section 

641 CELERY_BROKER_URL = "amqp://" 

642 CELERY_BEAT_SCHEDULE_DATABASE = os.path.join( 

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

644 EXPORT_LOCKDIR = LINUX_DEFAULT_LOCK_DIR # for demo configs only 

645 SCHEDULE_TIMEZONE = "UTC" 

646 

647 # Individual export recipients 

648 # DB_ECHO: as above 

649 ALL_GROUPS = False 

650 DB_ADD_SUMMARIES = True 

651 DB_INCLUDE_BLOBS = True 

652 DB_PATIENT_ID_PER_ROW = False 

653 EMAIL_BODY_IS_HTML = False 

654 EMAIL_KEEP_MESSAGE = False 

655 FILE_EXPORT_RIO_METADATA = False 

656 FILE_MAKE_DIRECTORY = False 

657 FILE_OVERWRITE_FILES = False 

658 FILE_PATIENT_SPEC_IF_ANONYMOUS = "anonymous" 

659 FINALIZED_ONLY = True 

660 HL7_DEBUG_DIVERT_TO_FILE = False 

661 HL7_DEBUG_TREAT_DIVERTED_AS_SENT = False 

662 HL7_KEEP_MESSAGE = False 

663 HL7_KEEP_REPLY = False 

664 HL7_NETWORK_TIMEOUT_MS = 10000 

665 HL7_PING_FIRST = True 

666 HL7_PORT = StandardPorts.HL7_MLLP 

667 INCLUDE_ANONYMOUS = False 

668 PUSH = False 

669 REQUIRE_PRIMARY_IDNUM_MANDATORY_IN_POLICY = True 

670 TASK_FORMAT = FileType.PDF 

671 XML_FIELD_COMMENTS = True 

672 

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

674 """ 

675 Args: 

676 docker: 

677 Amend defaults so it works within a Docker Compose application 

678 without much fiddling? 

679 

680 Defaults for use within Docker: 

681 

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

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

684 Compose file. 

685 """ 

686 self._docker = docker 

687 if docker: 

688 self.CELERY_BROKER_URL = DockerConstants.CELERY_BROKER_URL 

689 self.CELERY_BEAT_SCHEDULE_DATABASE = os.path.join( 

690 DockerConstants.DEFAULT_LOCKDIR, "camcops_celerybeat_schedule") 

691 self.DB_SERVER = DockerConstants.CONTAINER_MYSQL 

692 self.DB_USER = DockerConstants.DEFAULT_MYSQL_CAMCOPS_USER 

693 self.EXPORT_LOCKDIR = DockerConstants.DEFAULT_LOCKDIR 

694 self.HOST = DockerConstants.HOST 

695 self.USER_DOWNLOAD_DIR = DockerConstants.DEFAULT_USER_DOWNLOAD_DIR 

696 

697 @property 

698 def demo_db_url(self) -> str: 

699 """ 

700 The demonstration SQLAlchemy URL. 

701 """ 

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

703 # pymysql for standard installations -- fewer dependencies 

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

705 return make_mysql_url(driver=driver, 

706 host=self.DB_SERVER, 

707 port=self.DB_PORT, 

708 username=self.DB_USER, 

709 password=self.DB_PASSWORD, 

710 dbname=self.DB_DATABASE) 

711 

712 

713# ============================================================================= 

714# String length limits 

715# ============================================================================= 

716# 

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

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

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

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

721# 

722# Re commenting variables in Sphinx: 

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

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

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

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

727# works. 

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

729# because it will appear in the Sphinx string. 

730 

731 

732class StringLengths: 

733 # ------------------------------------------------------------------------- 

734 # Primary 

735 # ------------------------------------------------------------------------- 

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

737 

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

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

740 CHARSET_MAX_LEN = 64 

741 

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

743 

744 DATABASE_TITLE_MIN_LEN = 1 

745 DATABASE_TITLE_MAX_LEN = 255 #: our choice 

746 

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

748 #: must be compatible with tablet 

749 DEVICE_NAME_MAX_LEN = 191 

750 

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

752 EMAIL_ADDRESS_MAX_LEN = 255 

753 

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

755 EXPORT_RECIPIENT_NAME_MIN_LEN = 1 

756 EXPORT_RECIPIENT_NAME_MAX_LEN = 191 

757 

758 #: Our choice 

759 FILTER_TEXT_MAX_LEN = 255 

760 

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

762 FULLNAME_MAX_LEN = 255 

763 

764 #: Our choice 

765 FILESPEC_MAX_LEN = 255 

766 

767 GROUP_DESCRIPTION_MIN_LEN = 1 

768 #: Our choice 

769 GROUP_DESCRIPTION_MAX_LEN = 255 

770 

771 GROUP_NAME_MIN_LEN = 1 

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

773 GROUP_NAME_MAX_LEN = 191 

774 

775 HASHED_PW_MAX_LEN = 60 

776 """ 

777 : 

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

779 

780 .. code-block:: none 

781 

782 "$2a$" (4) 

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

784 b64-enc 128-bit salt (22) 

785 b64enc 184-bit hash (31) 

786 

787 ... total 60 

788 

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

790 """ # noqa 

791 

792 HL7_AA_MAX_LEN = 20 

793 """ 

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

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

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

797 

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

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

800 

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

802 example of a Namespace ID 

803 

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

805 3-char. 

806 

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

808 meaning 1-20. 

809 """ 

810 

811 HL7_ID_TYPE_MAX_LEN = 5 

812 """ 

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

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

815 """ 

816 

817 HOSTNAME_MAX_LEN = 255 

818 """ 

819 FQDN; see 

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

821 """ # noqa 

822 

823 ICD9_CODE_MAX_LEN = 6 

824 """ 

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

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

827 """ # noqa 

828 

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

830 ICD10_CODE_MAX_LEN = 7 

831 

832 #: Our choice 

833 ID_DESCRIPTOR_MAX_LEN = 255 

834 

835 #: Our choice 

836 ID_POLICY_MAX_LEN = 255 

837 

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

839 IP_ADDRESS_MAX_LEN = 45 

840 

841 ISO8601_DATETIME_STRING_MAX_LEN = 32 

842 """ 

843 Max length e.g. 

844 

845 .. code-block:: none 

846 

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

848 1234567890123456789012345678901234567890 

849 

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

851 """ 

852 

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

854 ISO8601_DURATION_STRING_MAX_LEN = 29 

855 

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

857 LANGUAGE_CODE_MAX_LEN = 6 

858 

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

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

861 

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

863 MIMETYPE_MAX_LEN = 255 

864 

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

866 PATIENT_NAME_MAX_LEN = 255 

867 

868 RFC_2822_DATE_MAX_LEN = 31 

869 """ 

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

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

872 """ 

873 

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

875 SENDING_FORMAT_MAX_LEN = 50 

876 

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

878 SESSION_TOKEN_MAX_BYTES = 64 

879 

880 SQL_SEARCH_LITERAL_MIN_LENGTH = 0 # permits: LIKE '' 

881 SQL_SEARCH_LITERAL_MAX_LENGTH = 255 # arbitrary 

882 

883 TABLENAME_MAX_LEN = 128 

884 """ 

885 For 

886 

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

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

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

890 """ 

891 

892 TASK_SUMMARY_TEXT_FIELD_DEFAULT_MAX_LEN = 50 

893 """ 

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

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

896 the core database. 

897 """ 

898 

899 #: Our choice 

900 URL_MAX_LEN = 255 

901 

902 USERNAME_CAMCOPS_MIN_LEN = 1 

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

904 USERNAME_CAMCOPS_MAX_LEN = 191 

905 

906 #: Our choice 

907 USERNAME_EXTERNAL_MAX_LEN = 255 

908 

909 # ------------------------------------------------------------------------- 

910 # Derived 

911 # ------------------------------------------------------------------------- 

912 

913 DIAGNOSTIC_CODE_MAX_LEN = max(ICD9_CODE_MAX_LEN, ICD10_CODE_MAX_LEN) 

914 

915 SESSION_TOKEN_MAX_LEN = len( 

916 create_base64encoded_randomness(SESSION_TOKEN_MAX_BYTES))