8.3. Web config file

8.3.1. Specimen config file

To obtain a specimen file, use

crate_print_demo_crateweb_config

Specimen web config as of 2017-02-28:

#!/usr/bin/env python
# crate_anon/crateweb/specimen_secret_local_settings/crateweb_local_settings.py

"""
===============================================================================
    Copyright (C) 2015-2017 Rudolf Cardinal (rudolf@pobox.com).

    This file is part of CRATE.

    CRATE is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CRATE is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CRATE. If not, see <http://www.gnu.org/licenses/>.
===============================================================================

Site-specific Django settings for CRATE web front end.
Put the secret stuff here.

SPECIMEN FILE ONLY - edit to your own requirements.
IT WILL NOT WORK until you've edited it.
"""

import os

raise Exception(
    "Well done - CRATE has found your crate_local_settings.py file at {}. "
    "However, you need to configure it for your institution's set-up, and "
    "remove this line.".format(os.path.abspath(__file__)))


# =============================================================================
# Site URL configuration
# =============================================================================

DJANGO_SITE_ROOT_ABSOLUTE_URL = "http://mymachine.mydomain"  # example for Apache  # noqa
# DJANGO_SITE_ROOT_ABSOLUTE_URL = "http://localhost:8000"  # for the Django dev server  # noqa

FORCE_SCRIPT_NAME = ""
# FORCE_SCRIPT_NAME = "/crate"  # example for CherryPy or Apache non-root hosting  # noqa

# =============================================================================
# Site security
# =============================================================================

# FOR SECURITY:
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'  # CHANGE THIS!  # noqa
# Run crate_generate_new_django_secret_key to generate a new one.

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
# ... when False, note that static files must be served properly


# noinspection PyUnusedLocal
def always_show_toolbar(request):
    return True  # Always show toolbar, for debugging only.

if DEBUG:
    ALLOWED_HOSTS = []
    DEBUG_TOOLBAR_CONFIG = {
        'SHOW_TOOLBAR_CALLBACK': always_show_toolbar,
    }
else:
    ALLOWED_HOSTS = ['*']

# =============================================================================
# Celery configuration
# =============================================================================

# Override BROKER_URL if you want.
# This will allow you to use multiple virtual hosts, to host multiple
# independent instances (in the unlikely event you'd wat to!)
# See
#   http://stackoverflow.com/questions/12209652/multi-celery-projects-with-same-rabbitmq-broker-backend-process  # noqa
# Similarly, override BROKER_URL to improve RabbitMQ security.

# =============================================================================
# Database configuration
# =============================================================================
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases

DATABASES = {
    # -------------------------------------------------------------------------
    # Django database for web site (inc. users, audit).
    # -------------------------------------------------------------------------

    # Quick SQLite example:
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': '/home/myuser/somewhere/crate_db.sqlite3',
    # },

    # Quick MySQL example:
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',
        'PORT': 3306,  # local
        'NAME': 'crate_db',
        'USER': 'someuser',
        'PASSWORD': 'somepassword',
    },

    # -------------------------------------------------------------------------
    # Anonymised research database
    # -------------------------------------------------------------------------
    'research': {

        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        # IT IS CRITICALLY IMPORTANT THAT THIS CONNECTION (i.e. its user's
        # access) IS READ-ONLY FOR THE RESEARCH DATABASES [1] AND HAS NO
        # ACCESS WHATSOEVER TO SECRET DATABASES (like the 'default' or
        # 'secret' databases) [2]. RESEARCHERS ARE GIVEN FULL ABILITY TO
        # EXECUTE SQL VIA THIS CONNECTION, AND CAN DO SO FOR ANY DATABASES
        # THAT THE CONNECTION PERMITS, NOT JUST THE ONE YOU SPECIFY
        # EXPLICITLY.
        #
        # [1] ... so researchers can't alter/delete research data
        # [2] ... so researchers can't see secrets
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',
        'PORT': 3306,  # local
        'NAME': 'anonymous_output',  # will be the default database; use None for no default database  # noqa
        'USER': 'researcher',
        'PASSWORD': 'somepassword',
    },

    # -------------------------------------------------------------------------
    # Secret database for RID/PID mapping
    # -------------------------------------------------------------------------
    'secret': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'NAME': 'anonymous_mapping',
        'USER': 'anonymiser_system',
        'PASSWORD': 'somepassword',
    },

    # -------------------------------------------------------------------------
    # Others, for consent lookup
    # -------------------------------------------------------------------------

    # Optional: 'cpft_iapt'
    # Optional: 'cpft_crs'
    # Optional: 'cpft_rio_rcep'
    # Optional: 'cpft_rio_crate'
    # ... see attributes of PatientLookup in crate_anon/consent/models.py
}

# Database title
RESEARCH_DB_TITLE = "My NHS Trust Research Database"

# Databases/schemas to provide database structure info for, and details on how
# to join within/between them (for the query builder).
# - Note that ALL these databases use the DATABASES['research'] connection
#   specified above.
# - Under SQL Server, "database" and "schema" are different levels of
#   organization. Specify a schema of "dbo" if you are unsure; this is the
#   default.
# - Under MySQL, "database" and "schema" mean the same thing. Here, we'll call
#   this a SCHEMA.
# - The first database/schema is the default selected in the query builder.
# - WITHIN a schema, tables will be autojoined on the trid_field.
# - ACROSS schemas, tables will be autojoined on the rid_field if they are in
#   the same rid_family (a non-False Python value, e.g. integers starting at
#   1), and on mrid_table.mrid_field otherwise.
# - PostgreSQL can only query a single database via a single connection.
RESEARCH_DB_INFO = [
    {
        # Database name:
        # - BLANK, i.e. '', for MySQL.
        # - BLANK, i.e. '', for PostgreSQL.
        # - The database name, for SQL Server.
        'database': '',
        # Schema name:
        # - The database=schema name, for MySQL.
        # - The schema name, for PostgreSQL (usual default: 'public').
        # - The schema name, for SQL Server (usual default: 'dbo').
        'schema': 'dbo',

        'trid_field': 'trid',
        'rid_field': 'brcid',
        'rid_family': 1,
        'mrid_table': 'patients',
        'mrid_field': 'nhshash',

        # For the data finder: is there a standard date field for most patient
        # tables?
        'default_date_field': '',
    },
    # {
    #     'database': 'similar_database',
    #     'schema': 'similar_schema',
    #     'trid_field': 'trid',
    #     'rid_field': 'same_rid',
    #     'rid_family': 1,
    #     'mrid_table': None,
    #     'mrid_field': None,
    #     'default_date_field': '',
    # },
    # {
    #     'database': 'different_database',
    #     'schema': 'different_schema',
    #     'trid_field': 'trid',
    #     'rid_field': 'different_rid',
    #     'rid_family': 2,
    #     'mrid_table': 'hashed_nhs_numbers',
    #     'mrid_field': 'nhshash',
    #     'default_date_field': '',
    # },
]

# For the automatic query generator, we need to know the underlying SQL dialect
# Options are
# - 'mysql' => MySQL
# - 'mssql' => Microsoft SQL Server
RESEARCH_DB_DIALECT = 'mysql'

DISABLE_DJANGO_PYODBC_AZURE_CURSOR_FETCHONE_NEXTSET = True

# Configuration of the secret mapping database (as set during initial
# anonymisation)
SECRET_MAP = {
    # Table within 'secret' mapping database containing PID/RID mapping
    'TABLENAME': "secret_map",
    # PID/RID fieldnames within that table
    'PID_FIELD': "patient_id",
    'RID_FIELD': "brcid",
    'MASTER_PID_FIELD': "nhsnum",
    'MASTER_RID_FIELD': "nhshash",
    'TRID_FIELD': 'trid',
    # Maximum length of the RID fields (containing a hash in a VARCHAR field)
    'MAX_RID_LENGTH': 255,
}

# Which of the databases defined above should be used for lookups?
# Must (a) be a key of PatientLookup.DATABASES_CHOICES in consent/models.py;
#      (b) be defined in DATABASES, above, UNLESS it is 'dummy_clinical'
CLINICAL_LOOKUP_DB = 'dummy_clinical'

# =============================================================================
# Database extra help file
# =============================================================================

# If specified, this must be a string that is an absolute filename of TRUSTED
# HTML that will be included.

DATABASE_HELP_HTML_FILENAME = None

# =============================================================================
# Local file storage (for PDFs etc).
# =============================================================================

# Where should we store the files? Make this directory (and don't let it
# be served by a generic web server that doesn't check permissions).
PRIVATE_FILE_STORAGE_ROOT = '/srv/crate_filestorage'

# Serve files via Django (inefficient but useful for testing) or via Apache
# with mod_xsendfile (or other web server configured for the X-SendFile
# directive)?
XSENDFILE = False

# How big will we accept?
MAX_UPLOAD_SIZE_BYTES = 10 * 1024 * 1024  # 10 Mb

# =============================================================================
# Outgoing e-mail
# =============================================================================
# General settings for sending e-mail from Django
# https://docs.djangoproject.com/en/1.8/ref/settings/#email-backend

#   default backend:
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
#   bugfix for servers that only support TLSv1:
# EMAIL_BACKEND = 'crate_anon.crateweb.core.mail.SmtpEmailBackendTls1'

EMAIL_HOST = 'smtp.somewhere.nhs.uk'
EMAIL_PORT = 587  # usually 25 (plain SMTP) or 587 (STARTTLS)
# ... see https://www.fastmail.com/help/technical/ssltlsstarttls.html
EMAIL_HOST_USER = 'myuser'
EMAIL_HOST_PASSWORD = 'mypassword'
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False

# Who will the e-mails appear to come from?
EMAIL_SENDER = "My NHS Trust Research Database - DO NOT REPLY <noreply@somewhere.nhs.uk>"  # noqa

# During development, we route all consent-related e-mails to the developer.
# Switch SAFETY_CATCH_ON to False for production mode.
SAFETY_CATCH_ON = True
DEVELOPER_EMAIL = "testuser@somewhere.nhs.uk"

VALID_RESEARCHER_EMAIL_DOMAINS = []
# ... if empty, no checks are performed (any address is accepted)

# =============================================================================
# Research Database Manager (RDBM) details
# =============================================================================

RDBM_NAME = "John Doe"
RDBM_TITLE = "Research Database Manager"
RDBM_TELEPHONE = "01223-XXXXXX"
RDBM_EMAIL = "research.database@somewhere.nhs.uk"
RDBM_ADDRESS = ["FREEPOST SOMEWHERE_HOSPITAL RESEARCH DATABASE MANAGER"]  # a list  # noqa

# =============================================================================
# Administrators/managers to be notified of errors
# =============================================================================

# Exceptions get sent to these people.
ADMINS = (
    ('Mr Administrator', 'mr_admin@somewhere.domain'),
)

# Broken links get sent to these people
SEND_BROKEN_LINK_EMAILS = True
MANAGERS = (
    ('Mr Administrator', 'mr_admin@somewhere.domain'),
)

# =============================================================================
# PDF creation
# =============================================================================
# WKHTMLTOPDF_FILENAME: for the pdfkit PDF engine, specify a filename for
# wkhtmltopdf that incorporates any need for an X Server (not the default
# /usr/bin/wkhtmltopdf). See http://stackoverflow.com/questions/9604625/ .
# Basically, you can try
#   WKHTMLTOPDF_FILENAME = ''
# and if it fails, try
#   WKHTMLTOPDF_FILENAME = '/usr/bin/wkhtmltopdf'
# but if that fails, use
#   WKHTMLTOPDF_FILENAME = '/path/to/wkhtmltopdf.sh'
# where wkhtmltopdf.sh is an executable script (chmod a+x ...) containing:
#   #!/bin/bash
#   xvfb-run --auto-servernum --server-args="-screen 0 640x480x16" \
#       /usr/bin/wkhtmltopdf "$@"

# For a recent version, fetch one from http://wkhtmltopdf.org/, e.g.
# v0.12.4 for your OS.
WKHTMLTOPDF_FILENAME = ''
# WKHTMLTOPDF_FILENAME = '/usr/bin/wkhtmltopdf'

PDF_LOGO_ABS_URL = 'http://localhost/crate_logo'
# ... path on local machine, read by wkhtmltopdf
# Examples:
#   [if you're running a web server] 'http://localhost/crate_logo'
#   [Linux root path] file:///home/myuser/myfile.png
#   [Windows root path] file:///c:/path/to/myfile.png

PDF_LOGO_WIDTH = "75%"
# ... must be suitable for an <img> tag, but "150mm" isn't working; "75%" is.
# ... tune this to your logo file (see PDF_LOGO_ABS_URL)

# =============================================================================
# Consent-for-contact settings
# =============================================================================

# For how long may we contact discharged patients without specific permission?
# Use 0 for "not at all".
PERMITTED_TO_CONTACT_DISCHARGED_PATIENTS_FOR_N_DAYS = 3 * 365

# Donation to charity for clinician response (regardless of the decision):
CHARITY_AMOUNT_CLINICIAN_RESPONSE = 1.0  # in local currency, e.g. GBP

# Note that using headers/footers requires a version of wkhtmltopdf built using
# "patched Qt". See above.
# Fetch one from http://wkhtmltopdf.org/, e.g. v0.12.4 for your OS.
PDF_LETTER_HEADER_HTML = ''
# PDF_LETTER_HEADER_HTML = '''
# <!DOCTYPE html>
# <head>
#     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
# </head>
# <html>
#     <body>
#         <div>boo! header</div>
#     </body>
# </html>
# '''

PDF_LETTER_FOOTER_HTML = ''
# http://stackoverflow.com/questions/11948158/wkhtmltopdf-how-to-disable-header-on-the-first-page  # noqa
# PDF_LETTER_FOOTER_HTML = '''
# <!DOCTYPE html>
# <html>
#     <head>
#         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
#         <script>
# function restrict_page_display() {
#     var vars = {},
#         kvp_list = document.location.search.substring(1).split('&'),
#         key_value_pair,
#         debug_element = document.getElementById("debug"),
#         i;
#     for (i = 0; i < kvp_list.length; ++i) {
#         key_value_pair = kvp_list[i].split('=', 2);
#         vars[key_value_pair[0]] = unescape(key_value_pair[1]);
#     }
#     // debug_element.textContent = kvp_list;
#
#     // Turn off footer except on first page
#     if (vars['page'] != 1) {
#         document.getElementById("footer").style.display = 'none';
#     }
# }
#         </script>
#         <style>
# body {
#     color: #005EB8;  /* NHS Blue */
#     font-family: Arial, Helvetica, sans-serif;
#     font-size: small;
#     text-align: right;
# }
#         </style>
#     </head>
#     <!-- <body onload="restrict_page_display()"> -->
#     <body>
#         <div id="footer">
#             CPFT
#             | HQ: Elizabeth House, Fulbourn Hospital, Fulbourn,
#               Cambridge CB21 5EF
#             | www.cpft.nhs.uk
#         </div>
#         <div id="debug"></div>
#     </body>
# </html>
# '''

# =============================================================================
# Local information links
# =============================================================================

CHARITY_URL = "http://www.cpft.nhs.uk/research.htm"
CHARITY_URL_SHORT = "www.cpft.nhs.uk/research.htm"
LEAFLET_URL_CPFTRD_CLINRES_SHORT = "www.cpft.nhs.uk/research.htm > CPFT Research Database"  # noqa
PUBLIC_RESEARCH_URL_SHORT = "www.cpft.nhs.uk/research.htm"

8.3.2. Django secret key

Use this command to generate a new random secret key:

crate_generate_new_django_secret_key

You can use the output for the SECRET_KEY variable in the config file.