team-3/src/c3nav/settings.py
2019-12-25 12:37:20 +01:00

475 lines
16 KiB
Python

# c3nav settings, mostly taken from the pretix project
import configparser
import os
import string
import sys
from contextlib import suppress
import sass
from django.contrib.messages import constants as messages
from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _
config = configparser.RawConfigParser()
if 'C3NAV_CONFIG' in os.environ:
open(os.environ.get('C3NAV_CONFIG'), encoding='utf-8')
config.read(['/etc/c3nav/c3nav.cfg', os.path.expanduser('~/.c3nav.cfg'), os.environ.get('C3NAV_CONFIG', 'c3nav.cfg')],
encoding='utf-8')
INSTANCE_NAME = config.get('c3nav', 'name', fallback='')
SENTRY_DSN = config.get('sentry', 'dsn', fallback=None)
with suppress(ImportError):
if (SENTRY_DSN):
import sentry_sdk
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[CeleryIntegration(), DjangoIntegration()]
)
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DATA_DIR = config.get('c3nav', 'datadir', fallback=os.environ.get('DATA_DIR', 'data'))
LOG_DIR = config.get('c3nav', 'logdir', fallback=os.path.join(DATA_DIR, 'logs'))
MEDIA_ROOT = os.path.join(DATA_DIR, 'media')
SOURCES_ROOT = os.path.join(DATA_DIR, 'sources')
MAP_ROOT = os.path.join(DATA_DIR, 'map')
RENDER_ROOT = os.path.join(DATA_DIR, 'render')
TILES_ROOT = os.path.join(DATA_DIR, 'tiles')
CACHE_ROOT = os.path.join(DATA_DIR, 'cache')
STATS_ROOT = os.path.join(DATA_DIR, 'stats')
if not os.path.exists(DATA_DIR):
os.mkdir(DATA_DIR)
if not os.path.exists(LOG_DIR):
os.mkdir(LOG_DIR)
if not os.path.exists(MEDIA_ROOT):
os.mkdir(MEDIA_ROOT)
if not os.path.exists(SOURCES_ROOT):
os.mkdir(SOURCES_ROOT)
if not os.path.exists(MAP_ROOT):
os.mkdir(MAP_ROOT)
if not os.path.exists(RENDER_ROOT):
os.mkdir(RENDER_ROOT)
if not os.path.exists(TILES_ROOT):
os.mkdir(TILES_ROOT)
if not os.path.exists(CACHE_ROOT):
os.mkdir(CACHE_ROOT)
if not os.path.exists(STATS_ROOT):
os.mkdir(STATS_ROOT)
PUBLIC_EDITOR = config.getboolean('c3nav', 'editor', fallback=True)
PUBLIC_BASE_MAPDATA = config.getboolean('c3nav', 'public_base_mapdata', fallback=False)
AUTO_PROCESS_UPDATES = config.getboolean('c3nav', 'auto_process_updates', fallback=True)
RANDOM_LOCATION_GROUPS = config.get('c3nav', 'random_location_groups', fallback=None)
if RANDOM_LOCATION_GROUPS:
RANDOM_LOCATION_GROUPS = tuple(int(i) for i in RANDOM_LOCATION_GROUPS.split(','))
if config.has_option('django', 'secret'):
SECRET_KEY = config.get('django', 'secret')
else:
SECRET_FILE = os.path.join(DATA_DIR, '.secret')
if os.path.exists(SECRET_FILE):
with open(SECRET_FILE, 'r') as f:
SECRET_KEY = f.read().strip()
else:
SECRET_KEY = get_random_string(50, string.printable)
with open(SECRET_FILE, 'w') as f:
os.chmod(SECRET_FILE, 0o600)
os.chown(SECRET_FILE, os.getuid(), os.getgid())
f.write(SECRET_KEY)
if config.has_option('c3nav', 'tile_secret'):
SECRET_TILE_KEY = config.get('c3nav', 'tile_secret')
else:
SECRET_TILE_FILE = os.path.join(DATA_DIR, '.tile_secret')
if os.path.exists(SECRET_TILE_FILE):
with open(SECRET_TILE_FILE, 'r') as f:
SECRET_TILE_KEY = f.read().strip()
else:
SECRET_TILE_KEY = get_random_string(50, string.printable)
with open(SECRET_TILE_FILE, 'w') as f:
os.chmod(SECRET_TILE_FILE, 0o600)
os.chown(SECRET_TILE_FILE, os.getuid(), os.getgid())
f.write(SECRET_TILE_KEY)
# Adjustable settings
debug_fallback = "runserver" in sys.argv
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback)
RENDER_SCALE = float(config.get('c3nav', 'render_scale', fallback=20.0))
IMAGE_RENDERER = config.get('c3nav', 'image_renderer', fallback='svg')
SVG_RENDERER = config.get('c3nav', 'svg_renderer', fallback='rsvg-convert')
CACHE_TILES = config.getboolean('c3nav', 'cache_tiles', fallback=not DEBUG)
CACHE_RESOLUTION = config.getint('c3nav', 'cache_resolution', fallback=4)
IMPRINT_ADDRESS = config.get('c3nav', 'imprint_address', fallback=None)
IMPRINT_PATRONS = config.get('c3nav', 'imprint_patrons', fallback=None)
IMPRINT_TEAM = config.get('c3nav', 'imprint_team', fallback=None)
IMPRINT_HOSTING = config.get('c3nav', 'imprint_hosting', fallback=None)
INITIAL_LEVEL = config.get('c3nav', 'initial_level', fallback=None)
INITIAL_BOUNDS = config.get('c3nav', 'initial_bounds', fallback='').split(' ')
GRID_ROWS = config.get('c3nav', 'grid_rows', fallback=None)
GRID_COLS = config.get('c3nav', 'grid_cols', fallback=None)
if len(INITIAL_BOUNDS) == 4:
try:
INITIAL_BOUNDS = tuple(float(i) for i in INITIAL_BOUNDS)
except ValueError:
INITIAL_BOUNDS = None
else:
INITIAL_BOUNDS = None
db_backend = config.get('database', 'backend', fallback='sqlite3')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.' + db_backend,
'NAME': config.get('database', 'name', fallback=os.path.join(DATA_DIR, 'db.sqlite3')),
'USER': config.get('database', 'user', fallback=''),
'PASSWORD': config.get('database', 'password', fallback=''),
'HOST': config.get('database', 'host', fallback=''),
'PORT': config.get('database', 'port', fallback=''),
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120
}
}
STATIC_URL = config.get('urls', 'static', fallback='/static/')
ALLOWED_HOSTS = [n for n in config.get('django', 'hosts', fallback='').split(',') if n]
LANGUAGE_CODE = config.get('locale', 'default', fallback='en')
TIME_ZONE = config.get('locale', 'timezone', fallback='UTC')
MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = config.get('mail', 'from', fallback='c3nav@localhost')
EMAIL_HOST = config.get('mail', 'host', fallback='localhost')
EMAIL_PORT = config.getint('mail', 'port', fallback=25)
EMAIL_HOST_USER = config.get('mail', 'user', fallback='')
EMAIL_HOST_PASSWORD = config.get('mail', 'password', fallback='')
EMAIL_USE_TLS = config.getboolean('mail', 'tls', fallback=False)
EMAIL_USE_SSL = config.getboolean('mail', 'ssl', fallback=False)
EMAIL_SUBJECT_PREFIX = ('[c3nav-%s] ' % INSTANCE_NAME) if INSTANCE_NAME else '[c3nav]'
ADMINS = [('Admin', n) for n in config.get('mail', 'admins', fallback='').split(",") if n]
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
HAS_REAL_CACHE = False
SESSION_ENGINE = "django.contrib.sessions.backends.db"
HAS_MEMCACHED = config.has_option('memcached', 'location')
if HAS_MEMCACHED:
HAS_REAL_CACHE = True
CACHES['default'] = {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': config.get('memcached', 'location'),
}
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
HAS_REDIS = config.has_option('redis', 'location')
if HAS_REDIS:
HAS_REAL_CACHE = True
CACHES['redis'] = {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": config.get('redis', 'location'),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
if not HAS_MEMCACHED:
CACHES['default'] = CACHES['redis']
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
else:
SESSION_CACHE_ALIAS = "redis"
HAS_CELERY = config.has_option('celery', 'broker')
if HAS_CELERY:
BROKER_URL = config.get('celery', 'broker')
CELERY_RESULT_BACKEND = config.get('celery', 'backend')
CELERY_SEND_TASK_ERROR_EMAILS = bool(ADMINS)
else:
CELERY_ALWAYS_EAGER = True
CELERY_TASK_SERIALIZER = 'json'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_RESULT_SERIALIZER = 'json'
TILE_CACHE_SERVER = config.get('c3nav', 'tile_cache_server', fallback=None)
# Internal settings
STATIC_ROOT = os.path.join(os.path.dirname(__file__), 'static.dist')
SESSION_COOKIE_NAME = 'c3nav_session'
SESSION_COOKIE_DOMAIN = config.get('c3nav', 'session_cookie_domain', fallback=None)
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = not DEBUG
LANGUAGE_COOKIE_NAME = 'c3nav_language'
CSRF_COOKIE_NAME = 'c3nav_csrftoken'
CSRF_COOKIE_SECURE = not DEBUG
TILE_ACCESS_COOKIE_NAME = 'c3nav_tile_access'
TILE_ACCESS_COOKIE_DOMAIN = config.get('c3nav', 'tile_access_cookie_domain', fallback=None)
TILE_ACCESS_COOKIE_HTTPONLY = True
TILE_ACCESS_COOKIE_SECURE = not DEBUG
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'compressor',
'bootstrap3',
'c3nav.api',
'rest_framework',
'c3nav.mapdata',
'c3nav.routing',
'c3nav.site',
'c3nav.control',
'c3nav.editor',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'c3nav.mapdata.middleware.NoLanguageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'c3nav.mapdata.middleware.UserDataMiddleware',
'c3nav.site.middleware.MobileclientMiddleware',
'c3nav.control.middleware.UserPermissionsMiddleware',
'c3nav.api.middleware.JsonRequestBodyMiddleware',
]
with suppress(ImportError):
import debug_toolbar # noqa
INSTALLED_APPS.append('debug_toolbar')
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
with suppress(ImportError):
import htmlmin # noqa
MIDDLEWARE += [
'htmlmin.middleware.HtmlMinifyMiddleware',
'htmlmin.middleware.MarkRequestMiddleware',
]
with suppress(ImportError):
import django_extensions # noqa
INSTALLED_APPS.append('django_extensions')
# Security settings
X_FRAME_OPTIONS = 'DENY'
# URL settings
ROOT_URLCONF = 'c3nav.urls'
WSGI_APPLICATION = 'c3nav.wsgi.application'
USE_I18N = True
USE_L10N = True
USE_TZ = True
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
)
}
LOCALE_PATHS = (
os.path.join(os.path.dirname(__file__), 'locale'),
)
LANGUAGES = [
('en', _('English')),
('de', _('German')),
]
template_loaders = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
if not DEBUG:
template_loaders = (
('django.template.loaders.cached.Loader', template_loaders),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
'c3nav.site.context_processors.logos',
'c3nav.site.context_processors.colors',
'c3nav.site.context_processors.user_data_json',
],
'loaders': template_loaders
},
},
]
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder',
'c3nav.site.finders.LogoFinder',
)
BOOTSTRAP3 = {
'success_css_class': '',
}
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'c3nav/static'),
]
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'django_libsass.SassCompiler'),
)
COMPRESS_ENABLED = COMPRESS_OFFLINE = not debug_fallback
COMPRESS_CSS_FILTERS = (
'compressor.filters.css_default.CssAbsoluteFilter',
'compressor.filters.cssmin.CSSCompressorFilter',
)
HEADER_LOGO = config.get('c3nav', 'header_logo', fallback=None)
FAVICON = config.get('c3nav', 'favicon', fallback=None)
FAVICON_PACKAGE = config.get('c3nav', 'favicon_package', fallback=None)
PRIMARY_COLOR = config.get('c3nav', 'primary_color', fallback='')
HEADER_BACKGROUND_COLOR = config.get('c3nav', 'header_background_color', fallback='')
HEADER_TEXT_COLOR = config.get('c3nav', 'header_text_color', fallback='')
HEADER_TEXT_HOVER_COLOR = config.get('c3nav', 'header_text_hover_color', fallback='')
SAFARI_MASK_ICON_COLOR = config.get('c3nav', 'safari_mask_icon_color', fallback=PRIMARY_COLOR)
MSAPPLICATION_TILE_COLOR = config.get('c3nav', 'msapplication_tile_color', fallback='')
WIFI_SSIDS = [n for n in config.get('c3nav', 'wifi_ssids', fallback='').split(',') if n]
USER_REGISTRATION = config.getboolean('c3nav', 'user_registration', fallback=True)
def return_sass_color(color):
if not color:
return lambda: color
if not color.startswith('#') or len(color) != 7 or any((i not in '0123456789abcdef') for i in color[1:]):
raise ValueError('custom color is not a hex color!')
return lambda: sass.SassColor(int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16), 1)
LIBSASS_CUSTOM_FUNCTIONS = {
'primary_color': return_sass_color(PRIMARY_COLOR),
'header_background_color': return_sass_color(HEADER_BACKGROUND_COLOR),
'header_text_color': return_sass_color(HEADER_TEXT_COLOR),
'header_text_hover_color': return_sass_color(HEADER_TEXT_HOVER_COLOR),
}
INTERNAL_IPS = ('127.0.0.1', '::1')
MESSAGE_TAGS = {
messages.INFO: 'alert-info',
messages.ERROR: 'alert-danger',
messages.WARNING: 'alert-warning',
messages.SUCCESS: 'alert-success',
}
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
loglevel = 'DEBUG' if DEBUG else 'INFO'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '%(levelname)s %(asctime)s %(name)s %(module)s %(message)s'
},
},
'handlers': {
'console': {
'level': loglevel,
'class': 'logging.StreamHandler',
'formatter': 'default'
},
'file': {
'level': loglevel,
'class': 'logging.FileHandler',
'filename': os.path.join(LOG_DIR, 'c3nav.log'),
'formatter': 'default'
}
},
'loggers': {
'': {
'handlers': ['file', 'console'],
'level': loglevel,
'propagate': True,
},
'django.request': {
'handlers': ['file', 'console'],
'level': loglevel,
'propagate': True,
},
'django.security': {
'handlers': ['file', 'console'],
'level': loglevel,
'propagate': True,
},
'django.db.backends': {
'handlers': ['file', 'console'],
'level': 'INFO', # Do not output all the queries
'propagate': True,
},
'shapely.geos': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propagate': True,
},
},
}
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]