1#!/usr/bin/env python3
   2
   3"""Define the baseline settings available to all projects.
   4
   5All settings can be overridden by projects, which can also introduce new settings.
   6Every setting should be commented where first used.
   7"""
   8
   9import re
  10import os
  11import sys
  12import time
  13import runpy
  14import socket
  15import importlib
  16from datetime import datetime
  17from datetime import timedelta
  18from typing import Optional
  19from typing import Union
  20
  21try:
  22    import dotenv
  23except ImportError:
  24    dotenv = None
  25
  26# these modules are not allowed to import this module
  27<<<hidden due to potential security issue>>>
  28from chart.common.path import Path
  29from chart.common.env import get_env
  30
  31try:
  32    # Tell Django to use oracledb instead of cx_Oracle if available
  33    import oracledb
  34    # sanity test is needed to avoid crash while building docker framework image
  35    # as for a few steps we're operating with oracledb v1.21
  36
  37    # Enable thick client mode. Without this we will be unable to access any columns using
  38    # national datatypes NCHAR, NVARCHAR2, and NCLOB because the EUM databases were created
  39    # using the Oracle specific UTC8 character set instead of the standard UTF-8.
  40    # This cannot be changed without a major rebuild of the database.
  41    # A future release of oracledb may lift the restriction but as of v1.3.1 there are
  42    # no specific plans to implement this.
  43    if hasattr(oracledb, 'init_oracle_client'):
  44        try:
  45            oracledb.init_oracle_client()
  46        except oracledb.DatabaseError:
  47            pass
  48
  49    # oracledb.version = "8.3.0"
  50    sys.modules['cx_Oracle'] = oracledb
  51except ImportError:
  52    pass
  53
  54
  55# Section: Initialisation
  56# -----------------------
  57
  58def print_debug(*message):
  59    """Show early debug messages if $CHART_DEBUG_SETTINGS is set."""
  60    if not get_env('CHART_DEBUG_SETTINGS', bool):
  61        return
  62
  63    print(' '.join(str(s) for s in message))
  64
  65
  66print_debug('Processing core settings {path}'.format(path=__file__))
  67
  68# Core code home directory
  69CORE_HOME_DIR = Path(__file__).absolute().parent
  70
  71# Top level core dir
  72CORE_ROOT_DIR = CORE_HOME_DIR.parent
  73
  74# Configure Django settings system to use this file
  75DJANGO_SETTINGS_MODULE = 'chart.settings'
  76os.environ['DJANGO_SETTINGS_MODULE'] = DJANGO_SETTINGS_MODULE
  77
  78# Location of project settings module
  79PROJECT_SETTINGS = os.environ.get('CHART_SETTINGS_MODULE', '')
  80assert len(PROJECT_SETTINGS) > 0, \
  81    '$CHART_SETTINGS_MODULE not set. Check that project.init_*() has been called'
  82
  83# Top level project code directory
  84# if PROJECT_SETTINGS != '':
  85# I think this is only needed so doctest can run over the file
  86# Normally this will only be included by an actual project
  87# project_settings_spec = importlib.util.find_spec(PROJECT_SETTINGS)
  88    # if project_settings_spec is None:
  89        # raise ValueError('Cannot import {settings} from ${env}'.format(
  90            # settings=PROJECT_SETTINGS, env=ENV_PROJECT_SETTINGS))
  91# PROJECT_HOME_DIR = Path(project_settings_spec.origin).parent
  92# else:
  93    # PROJECT_HOME_DIR = CORE_HOME_DIR
  94
  95# Find location of project from $CHART_SETTINGS_MODULE
  96if PROJECT_SETTINGS != 'dummy':
  97    # Allow CHART_SETTINGS_MODULE to be set to "dummy" to allow some automated core tests
  98    # to complete when run without a project present
  99    PROJECT_HOME_DIR = Path(importlib.util.find_spec(PROJECT_SETTINGS).origin).parent
 100
 101else:
 102    PROJECT_HOME_DIR = CORE_HOME_DIR
 103
 104# Root directory of the project
 105PROJECT_ROOT_DIR = PROJECT_HOME_DIR.parent
 106
 107# Application name used in the website
 108# It would be better to set this to None, forcing projects to re-define it, however that
 109# causes framework automated tests to fail since they process
 110APPNAME = None
 111
 112# description: Long description of the application
 113# datatype: str
 114PROJECT_DESCRIPTION = None
 115
 116# Where to write stack trace files on unhandled exceptions
 117STACK_TRACE_FILENAME = Path('trace.dump')
 118
 119# Directory to contain consolidated static files.
 120STATIC_ROOT = Path('~/tmp/static').expand()
 121
 122# Base URL for reading static resources
 123STATIC_URL = '/static/'
 124
 125# Local machine name
 126HOSTNAME = socket.gethostname()
 127
 128
 129# Section: Project directories
 130# ----------------------------
 131
 132# Directory to hold all notification statefiles
 133STATEFILE_DIR = None
 134
 135# Paths for activity XML files
 136ACTIVITY_DIRS = [PROJECT_HOME_DIR.joinpath('activities')]
 137
 138# Locations of timeseries table definition files
 139TS_TABLE_DIRS = [PROJECT_HOME_DIR.joinpath('db', 'ts')]
 140
 141# description: Location of fixed non-satellite specific event packet xmls
 142EV_PACKET_DIR = PROJECT_HOME_DIR.joinpath('db', 'packets', 'ev')
 143
 144# Location of sid specific limts, packets, time-series definition files,
 145# Currently only applicable to CHART-EPSSG
 146SID_SPECIFIC_DIR = None
 147
 148# Location of system table definition files
 149SYS_TABLE_DIRS = [CORE_HOME_DIR.joinpath('db', 'system')]
 150
 151# Directory tree for storing zipped working directories containing reports.
 152ARCHIVE_DIR = Path('~/tmp/archive').expand()
 153
 154# Location of named calibration XML files (not per-satellite)
 155CALIBRATION_DIR = PROJECT_HOME_DIR.joinpath('db', 'named_cal')
 156
 157# description: Location of limits XML files (not per-satellite)
 158LIMITS_DIR = PROJECT_HOME_DIR.joinpath('db', 'limits')
 159
 160# Directory where we place Oracle plsql functions (not per-satellite)
 161ORACLE_FUNC_DIR = PROJECT_HOME_DIR.joinpath('db', 'oracle')
 162
 163# Base directory for the worker and jobcontrol to create working directories under
 164WORK_AREA = Path('~/tmp/work').expand()
 165
 166# The timeseries XML files are compiled into this python script.
 167# The `ingester` process must be able to write to this file.
 168RDR_PY_FILENAME = PROJECT_HOME_DIR.joinpath('db', 'rdr.py')
 169RDR_ASYNC_PY_FILENAME = PROJECT_HOME_DIR.joinpath('db', 'rdr_async.py')
 170
 171# Files and subdirectories within the SID_SPECIFIC_DIR for PUS-based projects
 172# using satellite-specific databases
 173TS_TABLE_SUBDIR = Path('ts')
 174TC_PACKET_SUBDIR = Path('packets/tc')
 175TM_PACKET_SUBDIR = Path('packets/tm')
 176TM_CRITERIA_FILENAME = Path('packets/tm_criteria.xml')
 177CALIBRATION_SUBDIR = Path('named_cal')
 178CHOICES_SUBDIR = Path('choices')
 179LIMITS_SUBDIR = Path('limits')
 180PARAM1_PARAM2_INFO_FILENAME = Path('param1_param2_info.xml')
 181SRDB_VERSION_INFO_FILENAME = Path('srdb_version.xml')
 182PARAM_SPIDS_INFO_FILENAME = Path('param_spids_info.xml')
 183
 184# Location of core tools directory for launcher script
 185# This is not really used as the launcher hard codes its own settings to
 186# avoid importing proper settings so early.
 187TOOLS_DIRS = [
 188    {'name': 'Core tools', 'dir': CORE_HOME_DIR.joinpath('cmd')},
 189]
 190
 191# For each Widget directory a tuple of (name, dir, show_in_gallery)
 192WIDGET_DIRS = [
 193    {'name': 'Core widgets',   'dir': CORE_HOME_DIR.joinpath('widgets'), 'show-in-gallery': True},
 194    {'name': 'Digest widgets', 'dir': CORE_HOME_DIR.joinpath('widgets/digest'), 'show-in-gallery': False},
 195]
 196
 197# Location of scheduler XML files
 198SCHEDULE_DIR = PROJECT_HOME_DIR.joinpath('schedule')
 199
 200# Location of event class definitions file
 201EVENT_CLASSES_FILENAME = PROJECT_HOME_DIR.joinpath('events', 'event_classes.xml')
 202
 203# Project report themes
 204THEME_DIR = PROJECT_HOME_DIR.joinpath('themes')
 205
 206# List of known satellites
 207SATELLITES = PROJECT_HOME_DIR.joinpath('db', 'satellites.xml')
 208
 209# For projects using the generic SID_XML data source ID
 210# link the SID configuration file
 211SOURCES = PROJECT_HOME_DIR.joinpath('db', 'sources.xml')
 212
 213# Location of report template XML files
 214REPORT_TEMPLATE_DIR = PROJECT_HOME_DIR.joinpath('reports')
 215
 216# Module holding algorithm constants
 217CONSTANTS_FILE = None
 218
 219# Location of Relax-NG compact schema files
 220SCHEMA_DIR = CORE_HOME_DIR.joinpath('schemas')
 221
 222<<<hidden due to potential security issue>>>
 223<<<hidden due to potential security issue>>>
 224KEYRING_FILE = None
 225
 226<<<hidden due to potential security issue>>>
 227<<<hidden due to potential security issue>>>
 228<<<hidden due to potential security issue>>>
 229GPG_KEYRING_FILE = None
 230
 231# Optional file defining email distribution lists
 232ROLES_FILE = None
 233
 234# For external report publish function
 235REPORT_PUBLISH_HOST = None
 236REPORT_PUBLISH_USER = None
 237REPORT_PUBLISH_DIR = None
 238REPORT_PUBLISH_REMOTE_DB = None
 239REPORT_PUBLISH_DB_SYNC_CMD = None
 240
 241# Report group file location
 242REPORT_GROUP_FILE = None
 243
 244# Location for the auto-generated Sphinx documentation output
 245SPHINX_DIR = None
 246
 247
 248# Section: URLs
 249# ------------
 250
 251# Base URL (without trailing slash) for the CHART website.
 252# Used by automated tests and to create links in reports.
 253CHART_WEB = None  # 'https://chart/eps'
 254
 255# Base URL for external access website
 256CHART_EXTERNAL_WEB = None  # 'https://chartext.eumetsat.int/eps'
 257
 258# Base URL to browse core project source files
 259# Trailing slash should be omitted. Leave None to use built in viewer.
 260# datatype: url
 261CORE_BROWSE_URL = None
 262
 263# URL to browse project source code files. If None we mostly use the built-in
 264# file and directory browser. Other values are only useful in unusual cases such as Sphinx
 265# documentation.
 266# Trailing slash should be omitted.
 267PROJECT_BROWSE_URL = None
 268
 269# Link to a project wiki. It can be referred to as {WIKI_URL} from the <url> element
 270# of most .xml files.
 271PROJECT_HOMEPAGE_URL = 'http://tctrac/projects/example/wiki'
 272
 273# Daily digest uses this
 274TIMELINE_URL = None
 275
 276# Link to CDAT cross-project plotting tool
 277CDAT_URL = 'https://chart/cdat'
 278
 279#Exculde spid_param information for hybrid missions
 280NON_SPID_PROJECTS = 'S3'
 281
 282
 283# Section: Plot tool configuration
 284# --------------------------------
 285
 286# Filters for the plotting tool data tree
 287PLOT_TABLE_FILTERS = []
 288
 289# Do we show the bar chart side-by-side and overlay options in the plot tool?
 290PLOT_INCLUDE_BARCHART = False
 291
 292# Do we show the Daily Total check box in the plot tool?
 293PLOT_INCLUDE_DAILY_TOTAL = False
 294
 295# Include the View Details button in the plot tool info popup
 296PLOT_VIEW_DETAILS = False
 297
 298# Default sensing start time for plot tool
 299PLOT_DEFAULT_SENSING_STOP = datetime.utcnow().replace(second=0, minute=0)
 300
 301# Default sensing stop time for plot tool
 302PLOT_DEFAULT_SENSING_START = PLOT_DEFAULT_SENSING_STOP - timedelta(days=2)
 303
 304# Format of the plot legend entries.
 305# Available placeholders: sid, name, field, unit, description
 306PLOT_LEGEND_TEMPLATE = '{name} {field} {unit}'
 307
 308# Initial SCID for plot and event viewer
 309DEFAULT_SCID = None
 310
 311# Location of CHART master web site to handle proxied requests
 312# from external point of presence
 313WEB_PROXY = None
 314
 315# Location of common static files like the geolocation background
 316COMMON_WEB_DIR = CORE_HOME_DIR.joinpath('web')
 317
 318# With Auto sampling selected, threshold where we try to use stats
 319# instead of all-points subsampled. Timeseries tables
 320PLOT_MAX_AUTO_FITWIDTH_POINTS_TS = 200000
 321
 322# As PLOT_MAX_AUTO_FITWIDTH_POINTS_TS but key-value stores
 323PLOT_MAX_AUTO_FITWIDTH_POINTS_KV = 5000
 324
 325# Record if stats include standard deviations
 326STATS_HAVE_STDDEVS = True
 327
 328# True to use EPS project geolocator, None to disable geolocation,
 329GEOLOC_CONSTRUCTOR = None
 330
 331# Rescale Y-Axis when plotting single parameter with choices
 332# only set true for projects where CHOICE calibrations are not sequential with
 333# constant incrementation. Set True in S3 project
 334CHOICE_YAXIS_RESCALING = False
 335
 336# Add a prefix string to plot labels because otherwise in some circumstances
 337# (S3 plots) flot renders the tick labels at the same x-position as the axis
 338# label, overwriting each other
 339PLOT_LABEL_PREFIX = ''
 340
 341# True for projects that use a numerical region id code in stats tables,
 342# False for projects using a string region name
 343STATS_USE_NUMERICAL_REGIONS = True
 344
 345# Control whether the special button in the plot tool is used
 346# to show EPS-style shading of ECL events, or JCS/MTG (?) style OOL
 347# display, or hidden
 348PLOT_AUX_FUNCTION: Optional[Union['ECL', 'OOL']] = None
 349
 350# Enable restricting visibility of table groups in the plot tool and event viewer
 351# by user
 352TABLE_SECURITY_RESTRICTIONS = False
 353
 354
 355# Section: Job control
 356# --------------------
 357
 358# Ignore any files older than this when looking for files to ingest
 359FILE_SCAN_CUTOFF = timedelta(hours=72)
 360
 361# Sleep duration for scheduler
 362SCHEDULER_SLEEP = timedelta(seconds=5 * 60)
 363
 364# Sleep duration for worker in seconds
 365WORKER_SLEEP = 15 * 60
 366
 367# Build job chains by finding derived jobs
 368WORKER_JOB_CHAIN = 'chart.backend.worker_job_chains'
 369
 370# Job priorities tuned for EPS project
 371WORKER_JOB_PRIORITY = 'chart.backend.worker_next_jobs'
 372
 373# Expect an ORBITS field in the JOBS table.
 374# This is a relic from the EPS project which was written to rely in this.
 375# New projects shouldn't need it
 376ORBIT_IN_JOBS_TABLE = False
 377
 378# Use the older GEN_TIME field in AP tables instead of the newer GEN_NUM int
 379USE_GEN_TIME = False
 380
 381# CHART-EPS events include a "gen_method" column but others don't
 382USE_GEN_METHOD = False
 383
 384# Units of time for storing and displaying event start and stop times
 385EVENT_TIMESTAMP_ACCURACY = 'us'
 386
 387# Exclude activities from execution. These are excluded from the workers only.
 388# Schedulers and job chains will still create jobs as normal
 389EXCLUDE_ACTIVITIES = ()
 390
 391# Force the dispatcher to run algorithms as subprocesses even if being run
 392# interactively. The user must ensure all prerequisites are available since virtualenvs
 393# cannot be added automatically.
 394DISPATCHER_SUBPROCESS = False
 395
 396# Dispatcher will terminate any process taking too long.
 397DISPATCHER_TIMEOUT = timedelta(minutes=90)
 398
 399# Tell the generic SF00 ingester to operate in replace mode by default
 400REINGEST = False
 401
 402# Interval to retain delete jobs and work directories
 403PURGE_INTERVAL = timedelta(days=90)
 404
 405# Number of daily log files to retain after log rotation
 406LOG_ROTATE_COUNT = 60
 407
 408
 409# Section: Emails
 410# ---------------
 411
 412# ADMINS receive notification emails for a) Django errors, b) Algorithm fails and
 413# c) daemon status changes. Emails are only sent when DEBUG=false.
 414ADMINS = []
 415
 416# SYSADMINS only noticiations of daemon status changes.
 417SYSADMINS = None
 418
 419# Send emails for 404 errors
 420SEND_BROKEN_LINK_EMAILS = False
 421
 422# List of people who receive emails when the server responds with a 404 error.
 423# This requires additional middleware to be enabled.
 424# A better alternative is to monitor the web logfiles
 425MANAGERS = []
 426
 427# SMTP server to send internal CHART emails to
 428EMAIL_HOST = 'localhost'
 429
 430# SMTP port to send internal CHART emails
 431EMAIL_PORT = 25
 432
 433# Sender subject prefix to be used in emails send by CHART
 434# Used by pre-Role and Role-base systems
 435EMAIL_SUBJECT_PREFIX = ''
 436
 437# End of email sign-off message
 438# Used by pre-Role system only
 439EMAIL_MESSAGE_SUFFIX = '\nbest regards, CHART'
 440
 441# Django setting, can be used to redirect emails to console
 442EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
 443# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
 444
 445# Template for a name to appear in the 'to:' field of any emails sent out
 446# The {0} is expanded to the name of the module sending the message
 447# This is used only in the pre-Roles email code
 448EMAIL_NAME = 'CHART-{0}'
 449
 450# Template for a 'sender' field to appear in any emails sent out
 451# Used by pre-Role system only
 452EMAIL_ADDRESS = 'chart-{0}@eumetsat.int'
 453
 454# For the new Roles-based emails use a fixed string as the sender / from address
 455# This should replace all references to EMAIL_NAME and EMAIL_ADDRESS
 456# as it's simpler to have a single value in local_settings.py that just sets the from details
 457# without another layer of templating
 458EMAIL_SENDER = 'CHART (no reply) <chart-no-reply@eumetsat.int>'
 459
 460# Don't send out emails for events older than this
 461EMAIL_CUTOFF = timedelta(days=3)
 462
 463
 464# Section: Django templates
 465# -------------------------
 466
 467# If false, Django will send an email alert for web server errors.
 468# Note this does not affect log output; see LOG_LEVEL for that
 469DEBUG = True
 470
 471# Normally we use special exception handling in the API
 472DEBUG_API = False
 473
 474<<<hidden due to potential security issue>>>
 475# Should only be enabled via environment variable
 476<<<hidden due to potential security issue>>>
 477
 478# Tell Django to log emails to console (not sure if works)
 479# EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
 480
 481# If set to an integer, use this value for the PROJECT column of the
 482# JOBS table.
 483# Workers can optionally be configured to only read from a single PROJECT id
 484# Considerable overlap between this and the CATEGORY functionality, which
 485# could be merged into a single column
 486DATABASE_PROJECT_ID = None
 487
 488# If True then the job ID of the parent job will be written into the PARENT column
 489# of the JOBS table for derived jobs
 490DATABASE_JOBS_TABLE_PARENT = True
 491
 492# If True then the JOBS table includes Log File Analysis (error/warn/info counts)
 493# columns
 494DATABASE_JOBS_TABLE_LFA = True
 495
 496
 497# Section: Website
 498# ----------------
 499
 500# Django can retain and reuse db connections for this many seconds.
 501CONN_MAX_AGE = 3600
 502
 503# description Django in-memory cache is used for the Events and Plot start pages
 504# datatype dict
 505# CACHES = {
 506    # 'default': {
 507        # 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
 508        # 'LOCATION': 'unique-snowflake'
 509    # }
 510# }
 511# CACHE_BACKEND = 'locmem://'
 512# SESSION_ENGINE = "django.contrib.sessions.backends.cache"
 513
 514# Port number to use the application web interface
 515PORT = 10000
 516
 517# String that will be prefixed to all URLs.
 518# Use '^' for no prefix, otherwise '^PREFIX/'
 519PREFIX = ''
 520
 521# MEDIA_URL = PREFIX[1:]  # uploads/'
 522
 523# Every instance of every project needs a unique SECRET_KEY. Do not check secret keys
 524# into public source code repositories. Do not share secret keys between projects or instances of
 525# projects.
 526# Instead generate unique keys using a method below, then add them to the various
 527<<<hidden due to potential security issue>>>
 528# Keys can be generated with various tools i.e.
 529# https://www.miniwebtool.com/django-secret-key-generator/
 530# or
 531# > python3
 532# >>> from django.utils.crypto import get_random_string
 533# >>> chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
 534# >>> print(get_random_string(50, chars))
 535# If not specified then a new temporary key will be generated on each startup, which means all
 536# logged in users will be invalidated.
 537# See charteps settings.py file for the command to finally instantiate SECRET_KEY properly
 538
 539# Read the instance specific secret key
 540<<<hidden due to potential security issue>>>
 541
 542# Helper modules that can manipulate each HTTP response
 543# django 2.0 style - sending emails on errors is currently broken
 544MIDDLEWARE = [
 545    'django.middleware.security.SecurityMiddleware',
 546    'django.contrib.sessions.middleware.SessionMiddleware',
 547    'django.middleware.common.CommonMiddleware',
 548    # 'django.middleware.csrf.CsrfViewMiddleware',
 549    # 'chart.web.context.EmailAlerts',
 550    'django.contrib.auth.middleware.AuthenticationMiddleware',
 551    'django.contrib.messages.middleware.MessageMiddleware',
 552    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 553    # 'chart.web.lockdown.LockdownMiddleWare',  # add this to individual projects to get lockdown
 554    ]
 555
 556# MIDDLEWARE_CLASSES = (
 557    # 'django.middleware.gzip.GZipMiddleware',
 558    # 'chart.web.django-unslashed.middleware.RemoveSlashMiddleware',
 559    # 'unslashed.middleware.RemoveSlashMiddleware',
 560    # 'chart.web.context.EmailAlerts',
 561    # 'django.middleware.common.CommonMiddleware',
 562    # 'django.contrib.sessions.middleware.SessionMiddleware',
 563    # 'django.contrib.auth.middleware.AuthenticationMiddleware',
 564    # 'django.middleware.doc.XViewMiddleware',
 565    # )
 566
 567# Our authentication system tries to make an ssh connection against
 568# a TCE server to before looking in the Django users table
 569AUTHENTICATION_HOST = 'concorde'
 570
 571# Text shown in the log page before user enters their info, and repeated if login fails
 572AUTHENTICATION_PROMPT = 'Log in using your TCE credentials'
 573
 574# Django applications available. Any directories containing Django url.py files must be included
 575INSTALLED_APPS = ['django.contrib.auth',
 576                  'django.contrib.contenttypes',
 577                  'django.contrib.sessions',
 578                  'django.contrib.sites',
 579                  'django.contrib.staticfiles',
 580                  'django.contrib.humanize',
 581                  'django.contrib.messages',
 582                  'chart.alg',
 583                  'chart.api2',
 584                  'chart.backend',
 585                  'chart.db',
 586                  'chart.events',
 587                  'chart.plotviewer',
 588                  'chart.reports',
 589                  'chart.jobviewer',
 590                  'chart.reportviewer',
 591                  'chart.eventviewer2',
 592                  'chart.schemas',
 593                  'chart.timeconv',
 594                  'chart.web',
 595                  'chart.widgets',
 596                  ]
 597# 'django.contrib.admin',
 598
 599# Standard Django list of web template locations
 600TEMPLATES = [{
 601    'BACKEND': 'django.template.backends.django.DjangoTemplates',
 602    'DIRS': [CORE_HOME_DIR.joinpath('alg', 'templates'),
 603             CORE_HOME_DIR.joinpath('api', 'templates'),
 604             CORE_HOME_DIR.joinpath('api2', 'templates'),
 605             CORE_HOME_DIR.joinpath('backend', 'templates'),
 606             CORE_HOME_DIR.joinpath('db', 'templates'),
 607             CORE_HOME_DIR.joinpath('events', 'templates'),
 608             CORE_HOME_DIR.joinpath('eventviewer2', 'templates'),
 609             CORE_HOME_DIR.joinpath('jobviewer', 'templates'),
 610             CORE_HOME_DIR.joinpath('plotviewer', 'templates'),
 611             CORE_HOME_DIR.joinpath('reports', 'templates'),
 612             CORE_HOME_DIR.joinpath('reportviewer', 'templates'),
 613             CORE_HOME_DIR.joinpath('widgets', 'templates'),
 614             CORE_HOME_DIR.joinpath('timeconv', 'templates'),
 615             CORE_HOME_DIR.joinpath('web', 'templates'),
 616             CORE_HOME_DIR.joinpath('browse', 'templates'),
 617             CORE_HOME_DIR.joinpath('sids', 'templates')],
 618    'OPTIONS': {
 619        # 'loaders': ('django.template.loaders.filesystem.Loader',
 620                    # 'django.template.loaders.app_directories.Loader'),
 621        'debug': False,
 622        'string_if_invalid': 'MISSING TEMPLATE PARAMETER',
 623        'context_processors': (
 624            'django.contrib.auth.context_processors.auth',
 625            'django.template.context_processors.debug',
 626            'django.template.context_processors.i18n',
 627            'django.template.context_processors.media',
 628            'django.template.context_processors.static',
 629            'django.contrib.messages.context_processors.messages',
 630            'chart.web.context.add_settings'),
 631        'loaders': [
 632            ('django.template.loaders.cached.Loader', [
 633                'django.template.loaders.filesystem.Loader',
 634                'django.template.loaders.app_directories.Loader',
 635            ]),
 636        ],
 637        # proper django 1.9 only
 638        'builtins': ['django.templatetags.static'],
 639        },
 640    }]
 641
 642# TEMPLATES[0]['OPTIONS']['builtins'] = ['django.templatetags.static']
 643
 644# How to check user credentials (not really used as we mostly replace the
 645# built in system in web/views.py
 646AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
 647    # 'chart.web.auth.Auth',
 648
 649# accelerate most web page rendering by caching the content of
 650# the Django session and other tables
 651SESSION_ENGINE = 'django.contrib.sessions.backends.db'
 652# SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
 653# SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
 654
 655# description unknown
 656# AUTH_PROFILE_MODULE = "db.UserProfile"
 657
 658# Local time zone for this installation
 659TIME_ZONE = 'Europe/Berlin'
 660
 661# Language code for this installation
 662LANGUAGE_CODE = 'en-gb'
 663
 664# Django site unique ID
 665SITE_ID = 1
 666
 667# If False Django will make some optimizations so as not to load the internationalization machinery
 668USE_I18N = False
 669
 670# Datetime default text format
 671DATETIME_FORMAT = 'Y-m-d H:i:s'
 672
 673# Time default text format
 674TIME_FORMAT = 'H:i:s'
 675
 676# Required by Django 1.5 otherwise the website only works in debug mode
 677ALLOWED_HOSTS = ['*']
 678
 679# Location of top level urls.py
 680# ROOT_URLCONF = 'chart.urls'
 681ROOT_URLCONF = None
 682
 683# Classic style CSS
 684# Note sure if used - this should be in a CSS file
 685STYLE = {
 686    'menu_button': {
 687        'top_colour': '#85ADFF',
 688        'bottom_colour': '#85ADFF',
 689        'hover_colour': '#789CE6'},
 690    'menu_label': {
 691        'colour': '#FFF',
 692        # 'text_shadow': ''},
 693        'text_shadow': ('rgba(128,128,128,0.5) -1px 0, rgba(128,128,128,0.3) 0 -1px, '
 694                        'rgba(192,192,192,0.5) 0 1px, rgba(128,128,128,0.3) -1px -1px')},
 695    'title': {
 696        'colour': '#85ADFF'},
 697    'heading': {
 698        'colour': '#85ADFF'},
 699    'table_heading': {
 700        'colour': '#000000',
 701        'background_colour': '#a0a0ff'},
 702    'table': {
 703        'border': '1px solid #bbbbbb'},
 704    'table1': {
 705        'background_colour': '#ddf'},
 706    'link': {
 707        'colour': 'green'},
 708     }
 709
 710# Not sure if this works but it is supposed to make Django handle trailing slashes better
 711APPEND_SLASH = True
 712
 713# Tell the job viewer to retrieve files over an ssh link
 714<<<hidden due to potential security issue>>>
 715# example: ssh://epsope
 716WORK_DIR_SERVER = None
 717
 718# Log security messages (login attempts) to syslog, as a security requirement
 719LOG_TO_SYSLOG_PREFIX = None
 720
 721# The lockdown features are intended for the restricted EPP external access systems
 722# If required login is set, the only page a user can see initially is the login screen and
 723# the rest of the site will be available after successful login
 724# Lockdown options require the lockdown middleware
 725LOCKDOWN_LOGIN_REQUIRED = False
 726
 727# Optionally remove the login buttons. This is intended for systems
 728# that use Apache Digest authentication
 729LOCKDOWN_LOGIN_ALLOWED = True
 730
 731# On the EPP we switch off some pages that external users don't need. This
 732# might get used in future to restrict visibility of some reports, tables and parameters too
 733LOCKDOWN_RESTRICTED_ACCESS = False
 734
 735# Enable to show the "TC View" control to select event ordering in the event viewer
 736EVENTVIEWER_TIME_FILTER_OPTIONS: bool = False
 737
 738# Control whether the multitable feature of the event viewer is available.
 739# For now it requires a PUS project although a future extension will drop this restriction.
 740EVENTVIEWER_MULTITABLE_SUPPORT = False
 741
 742# maximum number of events for table. Note this can be increased to 500000 for projects
 743# which have implemented db performance improvements
 744EVENTVIEWER_MAX_TABLE_EVENTS = 250000
 745
 746
 747# Section: Reports
 748# ----------------
 749
 750# Mapping from 'auto' appearance for Graph widgets with a single data series
 751DEFAULT_SINGLE_DATAPOINT_APPEARANCE = 'dynrange'
 752
 753# Mapping from 'auto' appearance for Graph widgets with multiple data series
 754DEFAULT_MULTIPLE_DATAPOINT_APPEARANCE = 'dynrange'
 755
 756# The PDF converter program doesn't work on AIX so we ssh into a Linux host
 757# to run it. Set to None for local (wkhtmltopdf should be on PATH)
 758PDF_CONVERTER_HOST = None  # 'concorde'
 759
 760# Parameters for using the wkhtmltopdf HTML to PDF converter
 761# With the newest wkhtmltopdf we need--enable-local-file-access
 762# otherwise all images are missing.
 763# Also with latest, --print-media-type, --footer* are ignored
 764PDF_CONVERTER_COMMAND = ('wkhtmltopdf',
 765                         '--enable-local-file-access',
 766                         # '--print-media-type',  # using media type makes the tool use
 767                         # high resolution (zoomed) versions of report graphs. However
 768                         # this might be making the PDFs too large to send by email, and for
 769                         # some graphs causes tick labels to be clipped.
 770                         # '--page-width', '1000px',
 771                         # '--page-height', '600px',
 772                         # '--zoom', '1.5',
 773                         # '--load-error-handling ignore',
 774                         '--footer-right', '"Page [page] of [toPage]"',
 775                         '--footer-spacing', '5',
 776                         '--footer-font-size', '8',
 777                         '-q')
 778
 779# Standard category used by the scheduler, worker and system job status web pages
 780DEFAULT_CATEGORY = 'SCHEDULER'
 781
 782# Name of theme to use unless another is specified at runtime
 783DEFAULT_THEME = 'default'
 784
 785# SSH servers which we can publish reports to
 786REPORT_SERVERS = None
 787
 788# Font size for graph axis label
 789GRAPH_FONT_SIZE = 8
 790
 791# Font size for graph axis label for zoomed graphs
 792GRAPH_FONT_SIZE_ZOOM = 8
 793
 794# Font size for the image title inside Graph and other plot widgets
 795# Zero means disabled (title is usually still visible in the HTML text below plot)
 796GRAPH_TITLE_FONTSIZE = 0
 797
 798
 799# Section: Database
 800# -----------------
 801
 802# List of available timeseries database connections
 803DATABASES = {}
 804
 805# Default database connection for this project
 806DB_NAME = None
 807
 808# Allow overriding the database connection name for servers
 809DB_NAME_WEB = None
 810
 811# Name of database connection to use for tests which specify that they want a test connection
 812DB_NAME_TEST = None
 813
 814# Redirect all TS table writes to CSV files
 815FAKE_WRITES = False
 816
 817# Echo all raw SQL queries as log messages
 818SHOW_QUERIES = False
 819
 820# Allow certain tables to be accessed from a different database connection
 821# A dictionary of table names against DATABASES entries
 822DB_SPLIT_TABLES = {}
 823
 824# The set of automatic auxiliary fields that each table gets in the database
 825# options: EPS, PUS
 826TABLE_PATTERN = 'PUS'
 827
 828# The length of the TDEVTMHeader is usually 16 or 20 dependant on padding being enabled
 829TDEV_TM_HEADER_LENGTH = 16
 830
 831# For PUS ingestion give a special offset before the TDEV variable header
 832PUS_EXTENDED_BUFFER_OFFSET = 3
 833
 834# Ingest raw hex dumps into PUS tables
 835PUS_INGEST_RAW_HEX = False
 836
 837# Exclude GS IDs from CCSDS ingestion
 838PUS_TM_EXCLUDED_GS_IDS = None
 839
 840# Remove duplicate rows
 841PUS_DROP_DUPLICATES = None
 842
 843# For PUS ingestion only allow packets to be withheld
 844RESTRICTED_TC_PACKETS = []
 845
 846# PUS ingestion rapid file NIS Header other_data_buffer offset
 847NIS_OTHER_DATA_BUFF_LEN = 0
 848
 849# TM table contains RECEPTION_TIME field
 850TM_RECEPTION_TIME = False
 851# (this is still optional? obsolete? ?)
 852
 853# TM table contains TDEV_GS_ID field
 854TM_PACKET_TDEV_GS_ID = False
 855
 856# In PUS-based projects we try to normalise each parameter so it appears in the
 857# same type of array in each packet where it appears, i.e. if a particular parameter exists inside
 858# a 1-d array in any packet, then we place all instances of that packet in a 1-d array, even in
 859# packets where it would normally be scalar
 860# TBD: This is currently done at ingestion time, as a hack to reduce development effort.
 861# This algorithm should be moved to packet display code.
 862PUS_REDUCE_DIMENSIONALITIES = False
 863
 864# Tell the API /ts function to show empty variable list arrays for PUS tables
 865PUS_SHOW_EMPTY_ARRAYS = False
 866
 867# Source-ID class for the project
 868SID_CLASS = None
 869
 870# description: Maximum length of field names.
 871# Please note that ORACLE has a limitation to 26 characters (it would be 30, but 4 characters
 872# are reserved by CHART for Stats tables), while POSTGRESQL has a limit at 59 (longer is possible
 873# if modifying POSTGRESQL standard config.
 874# datatype: int
 875MAXIMUM_FIELD_NAME_LENGTH = 26
 876
 877# Set up a mapping between source environments and  filename fragments to SID objects
 878INGEST_GS_ENV_MAP = {}
 879
 880
 881# Section: Ingestion
 882# ------------------
 883
 884# Location of the SRDB (SCOS2000 configuration) parameters and packets database.
 885# This setting is also used by automated tests to determine whether to run generic PUS tests
 886SRDB_DIR = None
 887
 888# CCSDS header types as defined in Project SRDB MIBs
 889# in SRDB/current/tcp.dat file
 890# Note this should be moved to an xml file in application db directory and be created
 891# by the SRDB tool
 892# Standard CCSDS header (very standard TC/TM?)
 893CCSDS_STDXCCSD = 'STDXCCSD'
 894
 895# PUS Header - no CCSDS packet?
 896CCSDS_STDXPUSS = 'STDXPUSS'
 897
 898# Packet with no header
 899CCSDS_NO_HDR = 'NO_HDR'
 900
 901# Allow PUS packets to be filtered by TDEV Ground Station ID value,
 902# excluding any in this sequence
 903# PUS_TM_EXCLUDED_GS_IDS : Optional[Sequence[int]] = None
 904PUS_TM_EXCLUDED_GS_IDS = None
 905
 906# In application code, drop packets with duplicate primary key fields before ingesting
 907# them
 908# datatype: Optional[Sequence[str]]
 909PUS_DROP_DUPLICATES = None
 910
 911# CCSDS_HEADER_TYPES = {None: 'STDXCCSD', 'NOT_CCSDS': 'STDXPUSS', 'NO_HEADER': 'NO_HDR'}
 912
 913# For Polar Orbiting programs only, CCSDS OPS data fields are applicable
 914# This controls ingestion of some additional fields in the PUS ingestion
 915# This is a quick hack and should be replaced soon, e.g. by testing if the required fields are
 916# defined in TC_STORE.xml
 917OPS_DATA_APPLICABLE = False
 918
 919# For ops data ops angle conversion factor - satellite specific values for Polar Orbiting programs
 920OPS_ANGLE_FACTOR = 0.0
 921
 922# TDEV TC Variable header, OPS Data offset in Rapid file TDEVTCVarHeader. Although OPS Data
 923# is not applicable to all projects this offset does seem to be applicable to all programs
 924TDEVTC_OPS_DATA_OFFSET = 237
 925
 926# TDEV TC Variable header, MCR data offset in Rapid file TDEVTCVarHeader
 927# This appears to be dependant on the version of SCOS MCS the project is using
 928# For S3 abs_ops_orbit is NOT defined in the record structure so add only 8 bytes to MCR offset
 929# for S6/EPSSG abs_ops_orbit is defined in the record structure so add 12 bytes to MCR offset, 
 930# this is used as default
 931TDEVTC_MRC_DATA_OFFSET = TDEVTC_OPS_DATA_OFFSET + 12
 932
 933# Set to true to include the VARIABLE_DATA column when writing PUS TC packets to the database.
 934# Normally this is fine but the CHART-S3 project uses an Oracle database configured with a maximum
 935# length of 4000 characters for this column, which many packets exceed, so is currently disabled
 936# for that project. All other projects should use the default value of true
 937INC_TC_VARIABLE_DATA = True
 938
 939
 940# Section: Testing
 941# ----------------
 942
 943# Temp dir to use in unit tests. If not set, a subdirectory of
 944# /tmp will be used and deleted after the test ends. If TEST_DIR is specified, the
 945# unit test will not delete it's directory at the end.
 946TEST_DIR = None
 947
 948
 949# Section: Server
 950# ---------------
 951
 952# Type of web worker for the gunicorn server to use.
 953# 'sync'|'gevent'|'eventlet'|'tornado'
 954GUNICORN_WEB_WORKER_CLASS = 'sync'
 955
 956# Number of gunicorn worker processes
 957GUNICORN_WEB_WORKER_COUNT = 8
 958
 959
 960# Section: Logging
 961# ----------------
 962
 963# Activate logging to a single file, if set.
 964SINGLE_LOG_FILE = None
 965
 966# Activate logging to a daily rotating log files, if not None
 967ROTATING_LOG_FILE = None
 968
 969# This is always a rotating log file if used. It is only used if rotating log file is non-null
 970USER_LOG_FILE = 'user.log'
 971
 972# Set to 'NONE' to disable colourised text. Set to the name of a theme from
 973# common/log.py to select that theme.
 974COLOUR_THEME = 'blessings'
 975
 976# Here is an example of a project custom theme
 977# from fabulous import color as col
 978# COLOUR_THEME= {
 979    # None: '{asctime} {levelname} {msg}'.format(
 980        # asctime=col.bg256('#202020', col.fg256('#909090', '{r.asctime}')),
 981        # levelname=col.green('{r.levelname}'),
 982        # msg=col.bold(col.yellow('{r.msg}'))),
 983    # 'ERROR': '{asctime} {levelname} {msg}'.format(
 984        # asctime=col.bg256('#400000', col.fg256('#909090', '{r.asctime}')),
 985        # levelname=col.red('{r.levelname}'),
 986        # msg=col.red('{r.msg}'))}
 987
 988# Debug log file and colour initialisation
 989DEBUG_LOG = False
 990
 991# Max log level
 992# 'DEBUG'|'INFO'|'WARNING'|'ERROR'|'CRITICAL'
 993LOG_LEVEL = 'DEBUG'
 994
 995# Send log messages to stderr instead of stdout when writing to terminals
 996LOG_TO_STDERR = False
 997
 998# This value is only used for the docker compose script to pass the CHART_PROXY_LOG_DIR
 999# configuration value to the purge.py script
1000PROXY_LOG_DIR = None
1001
1002
1003# Section: Configuration
1004# ----------------------
1005
1006# The following magic environment variables are used in various places in the code
1007# outside of this section:
1008# - CHART_DEBUG_LAUNCHER: Debug launcher and virtualenv (used in project launcher scripts)
1009# - CHART_DEBUG_SETTINGS: Debug environment variables
1010# - CHART_AUTORELOAD: Internal, used by serve.py to avoid double printing messages
1011# - CHART_DISPATCHER: Internal, used by algorithms to detect they are being run by the dispatcher
1012
1013def include(module=None, path=None, exit_on_failure=False):
1014    """Import another module allowing it to change global variables of this module."""
1015    if path:
1016        print_debug('Settings including path {path}'.format(path=path))
1017
1018        try:
1019            globals().update(runpy.run_path(str(path), init_globals=globals()))
1020
1021        except FileNotFoundError:
1022            if exit_on_failure:
1023                raise
1024
1025    else:
1026        print_debug('Settings including module {module}'.format(module=module))
1027
1028        try:
1029            globals().update(runpy.run_module(str(module), init_globals=globals()))
1030
1031        except ImportError:
1032            if exit_on_failure:
1033                raise
1034        print_debug('Done')
1035
1036
1037# Project wide environment variable to change the environment variable
1038# prefix from CHART_ to something else
1039ENV_PREFIX_ENV = None
1040
1041# Declare environment variables which can be used to override settings
1042# datatypes are passed the env.get_env() function
1043# Declare these before importing project settings so projects can add custom
1044# environment variable configurations
1045ENV_OVERRIDES = [
1046    {'env': '{ENV_PREFIX}ARCHIVE_DIR', 'setting': 'ARCHIVE_DIR', 'datatype': Path},
1047    {'env': '{ENV_PREFIX}COLOUR', 'setting': 'COLOUR_THEME', 'datatype': str},
1048    {'env': '{ENV_PREFIX}DB', 'setting': 'DB_NAME', 'datatype': str},
1049    {'env': '{ENV_PREFIX}DB_WEB', 'setting': 'DB_NAME_WEB', 'datatype': str},
1050    {'env': '{ENV_PREFIX}DEBUG', 'setting': ('DEBUG', 'TEMPLATE_DEBUG'), 'datatype': bool},
1051    {'env': '{ENV_PREFIX}DEBUG_LOG', 'setting': 'DEBUG_LOG', 'datatype': bool},
1052    {'env': '{ENV_PREFIX}DEBUG_API', 'setting': 'DEBUG_API', 'datatype': bool},
1053    {'env': '{ENV_PREFIX}DISPATCHER_SUBPROCESS',
1054     'setting': 'DISPATCHER_SUBPROCESS',
1055     'datatype': bool,
1056     'description': 'Tell the dispatcher module to use subprocess instead of running inline'},
1057    {'env': '{ENV_PREFIX}FAKE_WRITES', 'setting': 'FAKE_WRITES', 'datatype': bool},
1058    {'env': '{ENV_PREFIX}LOCAL_EVENTS', 'setting': 'LOCAL_EVENTS', 'datatype': Path},
1059    {'env': '{ENV_PREFIX}LOG_FILE', 'setting': 'SINGLE_LOG_FILE', 'datatype': Path},
1060    {'env': '{ENV_PREFIX}LOG_TO_STDERR', 'setting': 'LOG_TO_STDERR', 'datatype': bool},
1061    {'env': '{ENV_PREFIX}PORT', 'setting': 'PORT', 'datatype': int},
1062    {'env': '{ENV_PREFIX}PREFIX', 'setting': 'PREFIX', 'datatype': str},
1063    {'env': '{ENV_PREFIX}REINGEST', 'setting': 'REINGEST', 'datatype': bool},
1064    {'env': '{ENV_PREFIX}ROTATING_LOG_FILE', 'setting': 'ROTATING_LOG_FILE', 'datatype': Path},
1065    {'env': '{ENV_PREFIX}SHOW_QUERIES', 'setting': 'SHOW_QUERIES', 'datatype': bool},
1066<<<hidden due to potential security issue>>>
1067    {'env': '{ENV_PREFIX}DB_TEST', 'setting': 'DB_NAME_TEST', 'datatype': str},
1068    {'env': '{ENV_PREFIX}TEST_DIR', 'setting': 'TEST_DIR', 'datatype': Path},
1069    {'env': '{ENV_PREFIX}WEB', 'setting': 'CHART_WEB', 'datatype': str},
1070    {'env': '{ENV_PREFIX}WEB_PROXY', 'setting': 'WEB_PROXY', 'datatype': str},
1071    {'env': '{ENV_PREFIX}WORK_DIR', 'setting': 'WORK_DIR', 'datatype': Path},
1072    {'env': '{ENV_PREFIX}STATIC_ROOT', 'setting': 'STATIC_ROOT', 'datatype': Path},
1073    {'env': '{ENV_PREFIX}PROJECT_ID', 'setting': 'DATABASE_PROJECT_ID', 'datatype': int},
1074    {'env': '{ENV_PREFIX}HOMEPAGE', 'setting': 'PROJECT_HOMEPAGE_URL', 'datatype': str},
1075    {'env': '{ENV_PREFIX}APPNAME', 'setting': 'APPNAME', 'datatype': str},
1076    {'env': '{ENV_PREFIX}STATEFILE_DIR', 'setting': 'STATEFILE_DIR', 'datatype': Path},
1077    {'env': '{ENV_PREFIX}WORK_AREA', 'setting': 'WORK_AREA', 'datatype': Path},
1078    {'env': '{ENV_PREFIX}ARCHIVE_DIR', 'setting': 'ARCHIVE_DIR', 'datatype': Path},
1079    {'env': '{ENV_PREFIX}ROLES_FILE', 'setting': 'ROLES_FILE', 'datatype': Path},
1080    {'env': '{ENV_PREFIX}EMAIL_SENDER', 'setting': 'EMAIL_SENDER', 'datatype': str},
1081    {'env': '{ENV_PREFIX}DISPATCHER_TIMEOUT', 'setting': 'DISPATCHER_TIMEOUT', 'datatype': int},
1082    {'env': '{ENV_PREFIX}PROXY_LOG_DIR', 'setting': 'PROXY_LOG_DIR', 'datatype': Path},
1083    {'env': '{ENV_PREFIX}LOCKDOWN_LOGIN_ALLOWED',
1084     'setting': 'LOCKDOWN_LOGIN_ALLOWED',
1085     'datatype': bool},
1086    {'env': '{ENV_PREFIX}LOCKDOWN_LOGIN_REQUIRED',
1087     'setting': 'LOCKDOWN_LOGIN_REQUIRED',
1088     'datatype': bool},
1089    {'env': '{ENV_PREFIX}LOCKDOWN_RESTRICTED_ACCESS',
1090     'setting': 'LOCKDOWN_RESTRICTED_ACCESS',
1091     'datatype': bool},
1092    {'env': '{ENV_PREFIX}AUTHENTICATION_HOST', 'setting': 'AUTHENTICATION_HOST', 'datatype': str,
1093     'allow_none': True},
1094    {'env': '{ENV_PREFIX}AUTHENTICATION_PROMPT',
1095     'setting': 'AUTHENTICATION_PROMPT',
1096     'datatype': str},
1097    {'env': '{ENV_PREFIX}EMAIL_HOST', 'setting': 'EMAIL_HOST', 'datatype': str},
1098    {'env': '{ENV_PREFIX}DEFAULT_TABLESPACE',
1099     'setting': 'DEFAULT_TABLESPACE',
1100     'datatype': str,
1101     'allow_none': True},
1102    {'env': '{ENV_PREFIX}FILE_SCAN_CUTOFF',
1103     'setting': 'FILE_SCAN_CUTOFF',
1104     'datatype': timedelta,
1105     'allow_none': False},
1106    {'env': '{ENV_PREFIX}EVENTVIEWER_MAX_TABLE_EVENTS',
1107     'setting': 'EVENTVIEWER_MAX_TABLE_EVENTS',
1108     'datatype': int,
1109     'allow_none': False},
1110    {'env': '{ENV_PREFIX}CDAT_URL', 'setting': 'CDAT_URL'},
1111]
1112# Pull in project settings
1113include(module=PROJECT_SETTINGS)
1114
1115# Read the local settings file
1116LOCAL_SETTINGS = PROJECT_SETTINGS.replace('project_settings', 'local_settings')
1117include(module=LOCAL_SETTINGS)
1118
1119# Read the user settings file
1120USER_SETTINGS = Path(get_env('CHART_USER_SETTINGS',
1121                             str,
1122                             '~/.{proj}.settings'.format(
1123                                 proj=PROJECT_SETTINGS.partition('.')[0]))).expand()
1124include(path=USER_SETTINGS)
1125
1126OBSOLETE_SETTINGS = (
1127    'WORK_DIR',  # Remove, it's a subdirectory of WORK_AREA
1128    'PROJ_HOME_DIR',  # change to PROJECT_HOME_DIR
1129    'PROJ_ROOT_DIR',  # change to PROJECT_ROOT_DIR
1130    'PROJ_TOOLS_DIR',  # add to TOOLS_DIRS
1131    'PROJECT_TOOLS_DIR',  # add to TOOLS_DIRS
1132    'PROJ_WIDGET_DIR',  # projects add to WIDGET_DIRS
1133    'ACTIVITY_DIR',  # Add to ACTIVITY_DIRS instead
1134    # 'PROJ_WIKI_URL',  # projects change to PROJECT_HOMEPAGE_URL  # eps has hundreds of references
1135    # to this one so leave it
1136    'PROJ_BROWSE_URL',  # change to PROJECT_BROWSE_URL
1137)
1138if not get_env('CHART_DISABLE_OBSOLETE_SETTINGS_CHECK', bool):
1139    for obsolete_setting in OBSOLETE_SETTINGS:
1140        if obsolete_setting in globals():
1141            raise ValueError('Obsolete setting {o} found in project settings'.format(
1142                o=obsolete_setting))
1143
1144# Read the local.env file into our environment
1145if dotenv is not None:
1146    local_env_file = PROJECT_HOME_DIR.joinpath('local.env')
1147    if local_env_file.exists():
1148        dotenv.load_dotenv(local_env_file)
1149
1150
1151if ENV_PREFIX_ENV is None:
1152    ENV_PREFIX = 'CHART_'
1153
1154else:
1155    ENV_PREFIX = get_env(ENV_PREFIX_ENV, str, 'CHART_')
1156
1157# Apply settings changes from environment variables
1158for override in ENV_OVERRIDES:
1159    envname = override['env'].format(ENV_PREFIX=ENV_PREFIX)
1160    setting = override['setting']
1161    datatype = override.get('datatype', str)
1162    allow_none = override.get('allow_none', False)
1163    if envname in os.environ:
1164        print_debug(('Using ${env} to change setting {set} to {val}'.format(
1165            env=envname, set=setting, val=get_env(envname, datatype))))
1166
1167        if isinstance(setting, str):
1168            # single env, single setting
1169            # setattr(settings[setting] = get_env(envname, datatype)
1170            globals()[setting] = get_env(envname, datatype, allow_none=allow_none)
1171
1172        else:
1173            # single env, multiple settings
1174            for s in setting:
1175                # if debug:
1176                    # print('Setting {k} to {v} type {t}'.format(
1177                        # k=s, v=get_env(envname, datatype), t=type(get_env(envname, datatype))))
1178                globals()[s] = get_env(envname, datatype)
1179
1180# Apply changes to DATABASES structure from environment variables
1181# CHART_DATABASES_mje-ingester_DESCRIPTION="My dev db"
1182DATABASES_PREFIX = ENV_PREFIX + 'DATABASES_'
1183for dbname_param, value in os.environ.items():
1184    if dbname_param.startswith(DATABASES_PREFIX):
1185        dbname, _, param = dbname_param[len(DATABASES_PREFIX):].partition('_')
1186        if dbname not in DATABASES:
1187            DATABASES[dbname] = {}
1188
1189<<<hidden due to potential security issue>>>
1190<<<hidden due to potential security issue>>>
1191
1192        else:
1193            DATABASES[dbname][param] = value
1194
1195# Read environment variables specifying a INGEST_GS_ENV_MAP
1196INGEST_GS_ENV_MAP_PREFIX = ENV_PREFIX + 'INGEST_GS_MAP_'
1197for gs_scid, sid in os.environ.items():
1198    if gs_scid.startswith(INGEST_GS_ENV_MAP_PREFIX):
1199        gs, scid = gs_scid[len(INGEST_GS_ENV_MAP_PREFIX):].split('_')
1200        if gs not in INGEST_GS_ENV_MAP:
1201            INGEST_GS_ENV_MAP[gs] = {}
1202
1203        if sid == 'None':
1204            INGEST_GS_ENV_MAP[gs][scid] = None
1205
1206        else:
1207            INGEST_GS_ENV_MAP[gs][scid] = sid
1208
1209# Must be unique per project otherwise different poject logins conflict with each other
1210# if hosted on the same domain
1211# Set this after pulling in project and local settins because we set APPAME to null in core settings
1212SESSION_COOKIE_NAME = re.sub('[ ()-]', '', APPNAME if APPNAME is not None else '').lower()
1213
1214# STATIC_URL is the base address the browser uses to access static files.
1215# Must come after PREFIX has been finalised
1216# It is possible some odd project will want to customise this but we can fix that when it happens
1217# if PREFIX == '^':
1218    # STATIC_URL = '/static/'
1219
1220# else:
1221    # STATIC_URL = PREFIX.replace('^', '/') + 'static/'
1222
1223# Set this after applying environment overrides as they can change PREFIX
1224STATIC_URL = '/' + PREFIX + 'static/'
1225
1226# SERVER_EMAIL is required by Django and should be a plain address.
1227# Must come after EMAIL_ADDRESS is finalised
1228# This is not used by our application code.
1229# SERVER_EMAIL = EMAIL_ADDRESS.format('webserver')
1230
1231# MONITOR_EMAIL is used by chart.common.emails.sendmail and can be a tuple of
1232# (name,address) or a string of "name<address>"
1233# Must come after EMAIL_ADDRESS is finalised
1234# MONITOR_EMAIL = (EMAIL_NAME.format('supervisor'),
1235# EMAIL_ADDRESS.format('supervisor'))
1236
1237# Base URL for online plot tool. Used by 'live view' links in reports.
1238# Must be set after CHART_WEB is finalised
1239PLOT_URL = CHART_WEB + '/plots' if CHART_WEB is not None else None
1240
1241# LOGGING_CONFIG = None
1242# LOGGING_CONFIG = 'chart.common.log.init_log'
1243
1244os.environ['TZ'] = 'UTC'
1245time.tzset()
1246
1247
1248def set_db_name(name):
1249    """Allow default database connection to be changed after module load.
1250
1251    `name` may either be a connection name or a comma separated list of items
1252    where each item is either:
1253
1254    - the primary connection name
1255
1256    or
1257
1258    - a table override in the form table=connection
1259
1260    E.g.:
1261
1262    charteps serve --db argus-reporter,EVENTS=chartdev
1263
1264    will set the primary database to argus-reporter but use chartdev for the EVENTS
1265    table.
1266    """
1267    # global DB_NAME
1268    # print('set_db_name ' + name)
1269
1270    def set_primary_db(name):
1271        """Set primary database."""
1272        if name not in DATABASES:
1273            raise ValueError('Database {db} not available. Select from:\n{choices}'.format(
1274                    db=name, choices='\n'.join(sorted(DATABASES.keys()))))
1275
1276        # This is the actual structure used by the Django multi-db support
1277        DATABASES['default'] = DATABASES[name]
1278
1279        # dispatcher needs this...
1280        DATABASES['default']['DB_NAME'] = name
1281
1282        if DATABASES['default']['ENGINE'].rpartition('.')[1] == 'postgresql_psycopg2' or\
1283           DATABASES['default']['ENGINE'] == 'django.db.backends.oracle':
1284            # postgres and recent Oracle versions needs this with Django v4 onwards as they
1285<<<hidden due to potential security issue>>>
1286            # We can't do it in our engine code because Django gets there first
1287<<<hidden due to potential security issue>>>
1288            # No idea why this test is suddenly needed
1289<<<hidden due to potential security issue>>>
1290<<<hidden due to potential security issue>>>
1291<<<hidden due to potential security issue>>>
1292
1293        # make sure subprocesses get the same db
1294        os.environ['{ENV_PREFIX}DB'.format(ENV_PREFIX=ENV_PREFIX)] = name
1295
1296    for part in name.split(','):
1297        # Decode the input into a series of tokens.
1298        if '=' not in part:
1299            # Tokens can be simple a connection name to set the primary connection
1300            set_primary_db(part)
1301
1302        else:
1303            # Tokens can also have the form table=name to change the connection for a single
1304            # table
1305            table, _, conn = part.partition('=')
1306
1307            # allow '%' as an additional wildcard char because it avoids shell expansion
1308            if '%' in table:
1309                table = table.replace('%', '*')
1310
1311            # We do wildcard expansion here. It would be neater in the common.tables
1312            # module, and using TableInfo not strings, but this feature is called during early
1313            # initialisation so we avoid pulling in too many imports.
1314            if '*' in table:
1315                for ts_dir in TS_TABLE_DIRS:
1316                    for table_name in ts_dir.glob(table.upper() + '.xml'):
1317                        DB_SPLIT_TABLES[table_name.stem.upper()] = conn
1318
1319            else:
1320                DB_SPLIT_TABLES[table.upper()] = conn
1321
1322
1323# Assign all the DB related variables that Django needs (must be after final setting of DB_NAME)
1324if DB_NAME is not None:
1325    # from chart.db.name import set_db_name
1326    set_db_name(DB_NAME)
1327
1328# this just helps the error message if client code tries to read a setting that is not found
1329# __name__ = 'settings'