calc/settings.py
"""
Django settings for calc project.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import dj_database_url
import dj_email_url
from dotenv import load_dotenv
from typing import Tuple, Any, Dict # NOQA
from .settings_utils import (load_cups_from_vcap_services,
load_redis_url_from_vcap_services,
is_running_tests)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DOTENV_PATH = os.path.join(BASE_DIR, '.env')
if os.path.exists(DOTENV_PATH):
load_dotenv(DOTENV_PATH)
load_cups_from_vcap_services()
load_redis_url_from_vcap_services('calc-redis32')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = 'DEBUG' in os.environ
DEBUG_HTTPS = 'DEBUG_HTTPS' in os.environ and not is_running_tests()
HIDE_DEBUG_UI = 'HIDE_DEBUG_UI' in os.environ
SLACKBOT_WEBHOOK_URL = os.environ.get('SLACKBOT_WEBHOOK_URL', '')
if is_running_tests():
HIDE_DEBUG_UI = True
SLACKBOT_WEBHOOK_URL = ''
if DEBUG:
os.environ.setdefault(
'SECRET_KEY',
'I am an insecure secret key intended ONLY for dev/testing.'
)
os.environ.setdefault(
'EMAIL_URL',
os.environ.get('DEFAULT_DEBUG_EMAIL_URL', 'console:')
)
if 'REDIS_URL' not in os.environ:
# Only set a default REDIS_TEST_URL if REDIS_URL is not
# explicitly defined either.
os.environ.setdefault('REDIS_TEST_URL', 'redis://localhost:6379/1')
os.environ.setdefault('REDIS_URL', 'redis://localhost:6379/0')
os.environ.setdefault('DEFAULT_FROM_EMAIL', 'noreply@localhost')
os.environ.setdefault('SERVER_EMAIL', 'system@localhost')
if 'EMAIL_URL' not in os.environ:
raise Exception('Please define the EMAIL_URL environment variable!')
SEND_TRANSACTIONAL_EMAILS = os.environ['EMAIL_URL'] == 'dummy:'
email_config = dj_email_url.config()
# Sets a number of settings values, as described at
# https://github.com/migonzalvar/dj-email-url
vars().update(email_config)
DEFAULT_FROM_EMAIL = os.environ['DEFAULT_FROM_EMAIL']
SERVER_EMAIL = os.environ['SERVER_EMAIL']
HELP_EMAIL = os.environ.get('HELP_EMAIL', DEFAULT_FROM_EMAIL)
GA_TRACKING_ID = os.environ.get('GA_TRACKING_ID', '')
NON_PROD_INSTANCE_NAME = os.environ.get('NON_PROD_INSTANCE_NAME', '')
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'calc/templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'calc.context_processors.show_debug_ui',
'calc.context_processors.google_analytics_tracking_id',
'calc.context_processors.help_email',
'calc.context_processors.non_prod_instance_name',
'calc.context_processors.sample_users',
'frontend.context_processors.is_safe_mode_enabled',
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
'django.template.context_processors.request',
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.request",
],
},
}]
ALLOWED_HOSTS = ['*']
BASE_GITHUB_URL = 'https://github.com/18F/calc'
DATA_CAPTURE_APP_CONFIG = 'DefaultDataCaptureApp'
# When IS_RQ_SCHEDULER is in the env,
# instead use the special DataCaptureSchedulerApp since this process
# is being used as the scheduler instance.
if 'IS_RQ_SCHEDULER' in os.environ:
DATA_CAPTURE_APP_CONFIG = 'DataCaptureSchedulerApp'
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.humanize',
'django.contrib.sites',
'django.contrib.postgres',
'whitenoise.runserver_nostatic',
'django.contrib.staticfiles',
'debug_toolbar',
'django_rq',
'session_security',
'data_explorer',
'contracts.apps.DefaultContractsApp',
'data_capture.apps.{}'.format(DATA_CAPTURE_APP_CONFIG),
'api',
'rest_framework',
'corsheaders',
'uaa_client',
'user_account',
'styleguide',
'meta',
'frontend',
'slackbot.apps.SlackbotConfig',
'uswds_forms',
'admin_reorder',
) # type: Tuple[str, ...]
SITE_ID = 1
if DEBUG:
STATICFILES_STORAGE = 'frontend.crotchety.CrotchetyStaticFilesStorage'
WHITENOISE_MIDDLEWARE = 'frontend.crotchety.CrotchetyWhiteNoiseMiddleware'
else:
STATICFILES_STORAGE = ('whitenoise.storage.'
'CompressedManifestStaticFilesStorage')
WHITENOISE_MIDDLEWARE = 'whitenoise.middleware.WhiteNoiseMiddleware'
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'calc.middleware.ComplianceMiddleware',
WHITENOISE_MIDDLEWARE,
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'uaa_client.middleware.UaaRefreshMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'session_security.middleware.SessionSecurityMiddleware',
# DjDT needs to be at the end of the middleware stack or else it can
# cause issues with other middlewares' process_view methods
# when the ProfilingPanel is enabled
# http://django-debug-toolbar.readthedocs.io/en/stable/panels.html#profiling
'calc.middleware.DebugOnlyDebugToolbarMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'admin_reorder.middleware.ModelAdminReorder',
)
AUTHENTICATION_BACKENDS = (
'uaa_client.authentication.UaaBackend',
)
ROOT_URLCONF = 'calc.urls'
WSGI_APPLICATION = 'calc.wsgi.application'
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# django cors headers
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = r'^/api/.*$' # only allow CORS for /api/ routes
CORS_ALLOW_METHODS = ('GET', 'OPTIONS',) # only allow read-only methods
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = [
# os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'docs', 'static')
]
RQ_QUEUES = {
'default': {
'URL': os.environ['REDIS_URL'],
}
}
if is_running_tests():
RQ_QUEUES['default']['URL'] = os.environ['REDIS_TEST_URL']
PAGINATION = 200
REST_FRAMEWORK = {
'COERCE_DECIMAL_TO_STRING': False,
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema'
}
LOGGING: Dict[str, Any] = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] "
"%(message)s",
'datefmt': "%d/%b/%Y %H:%M:%S"
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'logs/calc.log'),
'formatter': 'verbose'
},
'contracts_file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'logs/load_data.log'),
'formatter': 'verbose'
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'verbose'
},
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'propagate': True,
'level': 'INFO',
},
'uaa_client': {
'handlers': ['console', 'file'],
'propagate': True,
'level': 'INFO',
},
'calc': {
'handlers': ['console', 'file'],
'propagate': True,
'level': 'INFO',
},
'contracts': {
'handlers': ['console', 'contracts_file'],
'propagate': True,
'level': 'INFO',
},
'rq.worker': {
'handlers': ['console'],
'level': 'INFO',
},
'rq_scheduler': {
'handlers': ['console'],
'level': 'INFO',
},
'slackbot': {
'handlers': ['console'],
'level': 'INFO',
},
},
}
DEBUG_LOG_SQL = 'DEBUG_LOG_SQL' in os.environ
if DEBUG_LOG_SQL:
LOGGING['handlers']['debug_console'] = {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose'
}
LOGGING['loggers']['django.db.backends'] = {
'handlers': ['debug_console'],
'level': 'DEBUG',
}
DATABASES = {}
DATABASES['default'] = dj_database_url.config()
POSTGRES_VERSION = '9.5.4'
SECURE_SSL_REDIRECT = not DEBUG
if DEBUG and DEBUG_HTTPS:
SECURE_SSL_REDIRECT = True
if 'FORCE_DISABLE_SECURE_SSL_REDIRECT' in os.environ:
SECURE_SSL_REDIRECT = False
SESSION_COOKIE_SECURE = CSRF_COOKIE_SECURE = SECURE_SSL_REDIRECT
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
CSRF_COOKIE_HTTPONLY = True
# Amazon ELBs pass on X-Forwarded-Proto.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Amazon also sets X-Forwarded-Host.
USE_X_FORWARDED_HOST = True
SECRET_KEY = os.environ['SECRET_KEY']
ENABLE_SEO_INDEXING = 'ENABLE_SEO_INDEXING' in os.environ
SECURITY_HEADERS_ON_ERROR_ONLY = 'SECURITY_HEADERS_ON_ERROR_ONLY' in os.environ
DATA_CAPTURE_SCHEDULES = (
'data_capture.schedules.region_10.Region10PriceList',
'data_capture.schedules.s70.Schedule70PriceList',
'data_capture.schedules.s03fac.Schedule03FACPriceList',
'data_capture.schedules.s736.Schedule736PriceList',
'data_capture.schedules.region_3.Region3PriceList',
'data_capture.schedules.mas_consolidated.MASConsolidatedPriceList'
) # type: Tuple[str, ...]
if DEBUG and not HIDE_DEBUG_UI:
DATA_CAPTURE_SCHEDULES += (
'data_capture.schedules.fake_schedule.FakeSchedulePriceList',
)
UAA_AUTH_URL = 'https://login.fr.cloud.gov/oauth/authorize'
UAA_TOKEN_URL = 'https://uaa.fr.cloud.gov/oauth/token'
UAA_CLIENT_ID = os.environ.get('UAA_CLIENT_ID', 'calc-dev')
UAA_LOGOUT_URL = 'https://login.fr.cloud.gov/logout.do'
UAA_CLIENT_SECRET = os.environ.get('UAA_CLIENT_SECRET')
LOGIN_URL = 'uaa_client:login'
LOGIN_REDIRECT_URL = '/'
# We *always* want to send a Cache-Control header downstream, especially
# in the event where we've got a reverse proxy with aggressive caching
# defaults like Amazon CloudFront in front of us.
#
# For now we're just going to tell any downstream caches to never cache
# any dynamic content we give them, to ensure that stale content never
# gets served to end-users.
CACHE_MIDDLEWARE_SECONDS = 0
# seconds
SESSION_SECURITY_WARN_AFTER = 1500
# seconds
SESSION_SECURITY_EXPIRE_AFTER = 1800
SESSION_SECURITY_PASSIVE_URL_NAMES = ['ignore']
SESSION_EXPIRE_AFTER_LAST_ACTIVITY = True
if not UAA_CLIENT_SECRET:
if DEBUG:
# We'll be using the Fake UAA Provider.
UAA_CLIENT_SECRET = 'fake-uaa-provider-client-secret'
if not is_running_tests():
UAA_AUTH_URL = UAA_TOKEN_URL = 'fake:'
UAA_LOGOUT_URL = '/logout'
DEBUG_TOOLBAR_PATCH_SETTINGS = False
DEBUG_TOOLBAR_DISABLE_FOR_URL_PREFIXES = [
'/styleguide/fullpage-example/',
]
DEBUG_TOOLBAR_CONFIG = {
'DISABLE_PANELS': set([
'debug_toolbar.panels.redirects.RedirectsPanel',
'debug_toolbar.panels.profiling.ProfilingPanel',
]),
'SHOW_TOOLBAR_CALLBACK': 'calc.middleware.show_toolbar',
}
TEMPLATE_CONTEXT_PROCESSORS = [
'django.core.context_processors.request',
'django.contrib.auth.context_processors.auth',
]
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.versions.VersionsPanel',
'debug_toolbar.panels.profiling.ProfilingPanel',
'debug_toolbar.panels.timer.TimerPanel',
'debug_toolbar.panels.settings.SettingsPanel',
'debug_toolbar.panels.headers.HeadersPanel',
'debug_toolbar.panels.request.RequestPanel',
'debug_toolbar.panels.sql.SQLPanel',
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
'debug_toolbar.panels.templates.TemplatesPanel',
'debug_toolbar.panels.cache.CachePanel',
'debug_toolbar.panels.signals.SignalsPanel',
'debug_toolbar.panels.logging.LoggingPanel',
'debug_toolbar.panels.redirects.RedirectsPanel',
'data_capture.panels.ScheduledJobsPanel',
]
PRICE_LIST_ANALYSIS_FINDERS = [
'data_capture.analysis.finders.GteEduAndExpFinder',
]
if DEBUG:
INSTALLED_APPS += (
'django.contrib.admindocs',
)
ADMIN_REORDER = (
# Use django-modeladmin reorder to rearrange/rename the apps and models
# https://pypi.org/project/django-modeladmin-reorder/
{'app': 'data_capture', 'label': 'User-submitted pricing data', 'models': (
{
'model': 'data_capture.SubmittedPriceListRow',
'label': 'Mute or unmute submitted price list rows'
},
{
'model': 'data_capture.UnreviewedPriceList',
'label': 'Approve or reject unreviewed price lists'
},
{
'model': 'data_capture.ApprovedPriceList',
'label': 'Retire approved price lists'
},
{
'model': 'data_capture.RetiredPriceList',
'label': 'Edit and re-approve retired price lists'
},
{
'model': 'data_capture.RejectedPriceList',
'label': 'Approve rejected price lists'
},
{
'model': 'data_capture.AttemptedPriceListSubmission',
'label': 'Replay uncompleted price list submission attempts'
},
)},
{'app': 'contracts', 'label': 'Contracting Metadata', 'models': (
{'model': 'contracts.ScheduleMetadata', 'label': 'Available schedules'},
)},
{'app': 'auth', 'label': 'Authentication and authorization', 'models': (
'auth.User',
{'model': 'auth.Group', 'label': 'User groups'},
)},
{'app': 'sites', 'label': 'Available site settings', 'models': (
{'model': 'sites.Site', 'label': 'Site URLs'},
)},
)