Source code for snakeface.settings

__author__ = "Vanessa Sochat"
__copyright__ = "Copyright 2020-2021, Vanessa Sochat"
__license__ = "MPL 2.0"

import os
import tempfile
import yaml
import sys

from django.core.management.utils import get_random_secret_key
from snakeface.apps.users.utils import get_username
from datetime import datetime
from importlib import import_module

# Build paths inside the project with the base directory
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# The snakeface global conflict contains all settings.
SETTINGS_FILE = os.path.join(BASE_DIR, "settings.yml")
if not os.path.exists(SETTINGS_FILE):
    sys.exit("Global settings file settings.yml is missing in the install directory.")


# Read in the settings file to get settings
[docs]class Settings: """convert a dictionary of settings (from yaml) into a class""" def __init__(self, dictionary): for key, value in dictionary.items(): setattr(self, key, value) setattr(self, "UPDATED_AT", datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")) def __str__(self): return "[snakeface-settings]" def __repr__(self): return self.__str__() def __iter__(self): for key, value in self.__dict__.items(): yield key, value
with open(SETTINGS_FILE, "r") as fd: cfg = Settings(yaml.load(fd.read(), Loader=yaml.FullLoader)) # For each setting, if it's defined in the environment with SNAKEFACE_ prefix, override for key, value in cfg: envar = os.getenv("SNAKEFACE_%s" % key) if envar: setattr(cfg, key, envar) # Secret Key
[docs]def generate_secret_key(filename): """A helper function to write a randomly generated secret key to file""" key = get_random_secret_key() with open(filename, "w") as fd: fd.writelines("SECRET_KEY = '%s'" % key)
# Generate secret key if doesn't exist, and not defined in environment SECRET_KEY = os.environ.get("SECRET_KEY") if not SECRET_KEY: try: from .secret_key import SECRET_KEY except ImportError: SETTINGS_DIR = os.path.abspath(os.path.dirname(__file__)) generate_secret_key(os.path.join(SETTINGS_DIR, "secret_key.py")) from .secret_key import SECRET_KEY # Private only should be a boolean cfg.PRIVATE_ONLY = cfg.PRIVATE_ONLY is not None # Set the domain name DOMAIN_NAME = cfg.DOMAIN_NAME if cfg.DOMAIN_PORT: DOMAIN_NAME = "%s:%s" % (DOMAIN_NAME, cfg.DOMAIN_PORT) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True if os.getenv("DEBUG") != "false" else False # Derive list of plugins enabled from the environment PLUGINS_LOOKUP = { "ldap_auth": False, "pam_auth": False, "saml_auth": False, } PLUGINS_ENABLED = [] using_auth_backend = False for key, enabled in PLUGINS_LOOKUP.items(): plugin_key = "PLUGIN_%s_ENABLED" % key.upper() if hasattr(cfg, plugin_key) and getattr(cfg, plugin_key) is not None: # Don't enable auth backends if we are using a notebook if cfg.NOTEBOOK_ONLY or cfg.NOTEBOOK and "AUTH" in plugin_key: continue if "AUTH" in plugin_key: using_auth_backend = True PLUGINS_ENABLED.append(key) # Does the user want a notebook? Default to this if no auth setup if not hasattr(cfg, "NOTEBOOK"): cfg.NOTEBOOK = True if not using_auth_backend else None # If the working directory isn't defined, set to pwd if not hasattr(cfg, "WORKDIR") or not cfg.WORKDIR: cfg.WORKDIR = os.getcwd() # SECURITY WARNING: App Engine's security features ensure that it is safe to # have ALLOWED_HOSTS = ['*'] when the app is deployed. If you deploy a Django # app not on App Engine, make sure to set an appropriate host here. # See https://docs.djangoproject.com/en/2.1/ref/settings/ ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ "channels", "snakeface.apps.base", "snakeface.apps.api", "snakeface.apps.main", "snakeface.apps.users", "django.contrib.admin", "django.contrib.auth", "django.contrib.humanize", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "django_extensions", "crispy_forms", "social_django", "django_q", "rest_framework", "rest_framework.authtoken", ] CRISPY_TEMPLATE_PACK = "bootstrap4" MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "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", ] # Do we want to enable the cache? if cfg.ENABLE_CACHE: MIDDLEWARE += [ "django.middleware.cache.UpdateCacheMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.cache.FetchFromCacheMiddleware", ] CACHE_MIDDLEWARE_ALIAS = "default" CACHE_MIDDLEWARE_SECONDS = 86400 # one day # If we are using a notebook, use an in memory channel layer if cfg.NOTEBOOK: CHANNEL_LAYERS = {"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}} ROOT_URLCONF = "snakeface.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "snakeface.context_processors.globals", ], }, }, ] TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG WSGI_APPLICATION = "snakeface.wsgi.application" ASGI_APPLICATION = "snakeface.asgi.application" AUTH_USER_MODEL = "users.User" SOCIAL_AUTH_USER_MODEL = "users.User" GRAVATAR_DEFAULT_IMAGE = "retro" # Cache to tmp CACHE_LOCATION = os.path.join(tempfile.gettempdir(), "snakeface-cache") CACHES = { "default": { "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", "LOCATION": CACHE_LOCATION, } } if not os.path.exists(CACHE_LOCATION): os.mkdir(CACHE_LOCATION) # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases # Case 1: we are running locally but want to do migration, etc. (set False to True) if True and os.getenv("APP_ENGINE_HOST") != None: print("Warning: connecting to production database.") # Running in development, but want to access the Google Cloud SQL instance in production. DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "USER": os.getenv("APP_ENGINE_USERNAME"), "PASSWORD": os.getenv("APP_ENGINE_PASSWORD"), "NAME": os.getenv("APP_ENGINE_DATABASE"), "HOST": os.getenv("APP_ENGINE_HOST"), # Set to IP address "PORT": "", # empty string for default. } } # Case 2: we are running on app engine elif os.getenv("APP_ENGINE_CONNECTION_NAME") != None: # Ensure debug is absolutely off TEMPLATES[0]["OPTIONS"]["debug"] = False DEBUG = False # Running on production App Engine, so connect to Google Cloud SQL using # the unix socket at /cloudsql/<your-cloudsql-connection string> DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "HOST": "/cloudsql/%s" % os.getenv("APP_ENGINE_CONNECTION_NAME"), "USER": os.getenv("APP_ENGINE_USERNAME"), "PASSWORD": os.getenv("APP_ENGINE_PASSWORD"), "NAME": os.getenv("APP_ENGINE_DATABASE"), } } # Case 3: Database local development uses DATABASE_* variables elif os.getenv("DATABASE_HOST") is not None: # Make sure to export all of these in your .env file DATABASES = { "default": { "ENGINE": os.environ.get("DATABASE_ENGINE", "django.db.backends.mysql"), "HOST": os.environ.get("DATABASE_HOST"), "USER": os.environ.get("DATABASE_USER"), "PASSWORD": os.environ.get("DATABASE_PASSWORD"), "NAME": os.environ.get("DATABASE_NAME"), } } else: # Use sqlite when testing locally DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa: 501 }, { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", # noqa: 501 }, { "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", # noqa: 501 }, { "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", # noqa: 501 }, ] # Django Q # workers defaults to multiprocessing CPU count, can be set if neede # This can be sped up running with another database Q_CLUSTER = { "name": "snakecluster", "timeout": 90, "retry": 120, "queue_limit": 50, "bulk": 10, "orm": "default", } # Internationalization # https://docs.djangoproject.com/en/2.1/topics/i18n/ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # TODO: we probably want to put these in one spot relative to user home # https://docs.djangoproject.com/en/2.1/howto/static-files/ STATIC_ROOT = "static" STATIC_URL = "/static/" MEDIA_ROOT = "data" MEDIA_URL = "/data/" # Rate Limiting VIEW_RATE_LIMIT = "1000/1d" # The rate limit for each view, django-ratelimit, "50 per day per ipaddress) VIEW_RATE_LIMIT_BLOCK = ( True # Given that someone goes over, are they blocked for the period? ) # On any admin or plugin login redirect to standard social-auth entry point for agreement to terms LOGIN_REDIRECT_URL = "/login" LOGIN_URL = "/login" # If we are using a notebook, grab the user that started cfg.USERNAME = None if cfg.NOTEBOOK or cfg.NOTEBOOK_ONLY: cfg.USERNAME = get_username() AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"] ## PLUGINS ##################################################################### # Apply any plugin settings for plugin in PLUGINS_ENABLED: plugin_module = "snakeface.plugins." + plugin plugin = import_module(plugin_module) # Add the plugin to INSTALLED APPS INSTALLED_APPS.append(plugin_module) # Add AUTHENTICATION_BACKENDS if defined, for authentication plugins if hasattr(plugin, "AUTHENTICATION_BACKENDS"): AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS + plugin.AUTHENTICATION_BACKENDS ) # Add custom context processors, if defines for plugin if hasattr(plugin, "CONTEXT_PROCESSORS"): for context_processor in plugin.CONTEXT_PROCESSORS: TEMPLATES[0]["OPTIONS"]["context_processors"].append(context_processor)