conference-website/conference/settings.py

438 lines
15 KiB
Python

from typing import List
import os
import pathlib
import sys
from dotenv import load_dotenv
import dj_database_url
from sentry_sdk.integrations.django import DjangoIntegration
import sentry_sdk
# Look into current and parents dirs until we find a .env
load_dotenv()
BASE_DIR = pathlib.Path(__file__).absolute().parent.parent
TESTING = sys.argv[1:2] == ['test']
SECRET_KEY = os.getenv(
'SECRET_KEY',
'django-insecure-@!bx83z!nudfc3uvfmm+)n0a+ms1zz&z9jivwwj$&70&d48h2t',
)
DEBUG = bool(os.getenv('DEBUG', False))
ALLOWED_HOSTS: List[str] = os.getenv('ALLOWED_HOSTS', 'conference.local').split(',')
trusted_origins = os.getenv('CSRF_TRUSTED_ORIGINS', '')
CSRF_TRUSTED_ORIGINS: List[str] = trusted_origins.split(',') if trusted_origins else []
BLENDER_ID = {
# MUST end in a slash:
'BASE_URL': os.getenv('BLENDER_ID_BASE_URL', 'http://id.local:8000/'),
'OAUTH_CLIENT': os.getenv('BLENDER_ID_OAUTH_CLIENT', 'BLENDER-CONFERENCE-DEV'),
'OAUTH_SECRET': os.getenv('BLENDER_ID_OAUTH_SECRET', 'TEST-SECRET'),
}
# Allow to set return.is_secure() to True when running behind a trusted proxy such as Heroku.
SECURE_PROXY_SSL_HEADER = (
('HTTP_X_FORWARDED_PROTO', 'https') if os.getenv('FORCE_SECURE_PROXY_SSL_HEADER') else None
)
# See README.md for info
SYSTEM_USER_ID = os.getenv('SYSTEM_USER_ID')
# Application definition
INSTALLED_APPS = [
# Register an optional 'overrides' app to override templates and static (useful to fully
# customize the look of the website). The app will be registered only if HAS_OVERRIDES_APP
*(['overrides'] if os.getenv('HAS_OVERRIDES_APP') else ()),
'conference_main',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'django.contrib.flatpages',
'pipeline',
'loginas',
'crispy_forms',
'tinymce',
'blender_id_oauth_client',
'micawber.contrib.mcdjango',
'sorl.thumbnail',
'qr_code',
'background_task',
'emails',
'tickets',
'django_countries',
]
SITE_ID = int(os.getenv('SITE_ID', 1))
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# Enable WhiteNoise middleware only if USE_WHITENOISE is set
*(['whitenoise.middleware.WhiteNoiseMiddleware'] if os.getenv('USE_WHITENOISE') else ()),
'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',
]
ROOT_URLCONF = 'conference.urls'
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
MEDIA_URL = os.getenv('MEDIA_URL', '/media/')
STATIC_ROOT = os.getenv('STATIC_ROOT', BASE_DIR / 'public/static')
MEDIA_ROOT = os.getenv('MEDIA_ROOT', BASE_DIR / 'public/media')
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / 'assets_shared' / 'src' / 'templates',
BASE_DIR / 'templates',
],
'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',
'loginas.context_processors.impersonated_session_status',
'conference_main.context_processors.edition_and_site_settings',
'conference_main.context_processors.main_menu_editions',
],
# PyPugJS:
'loaders': [
(
'pypugjs.ext.django.Loader',
(
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
),
)
],
'builtins': ['pypugjs.ext.django.templatetags'],
},
}
]
TINYMCE_JS_URL = STATIC_URL + 'conference_main/tinymce/tinymce.min.js'
TINYMCE_DEFAULT_CONFIG = {
'plugins': "table,spellchecker,paste,searchreplace,visualblocks,image,code,link,autoresize",
'theme': "silver",
'cleanup_on_startup': True,
'custom_undo_redo_levels': 10,
}
CRISPY_TEMPLATE_PACK = 'bootstrap4'
WSGI_APPLICATION = 'conference.wsgi.application'
DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite:///{}'.format(BASE_DIR / 'db.sqlite3'))
CONN_MAX_AGE = int(os.getenv('CONN_MAX_AGE', 0))
DATABASES = {
'default': dj_database_url.config(default=DATABASE_URL),
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
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'},
]
# Blender ID login with Blender ID OAuth client
LOGIN_URL = '/oauth/login'
LOGOUT_URL = '/oauth/logout'
LOGIN_REDIRECT_URL = '/'
# Set to empty string to remain on Blender ID after logging out:
LOGOUT_REDIRECT_URL = '/'
# Email configuration, by default uses console output
EMAIL_BACKEND = os.getenv('EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend')
DEFAULT_REPLY_TO_EMAIL = os.getenv(
'DEFAULT_REPLY_TO_EMAIL', 'Blender Conference <conference@blender.org>'
)
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'Blender Conference <conference@blender.org>')
if os.environ.get('EMAIL_HOST') is not None:
EMAIL_HOST = os.getenv('EMAIL_HOST')
if os.environ.get('EMAIL_PORT') is not None:
EMAIL_PORT = os.getenv('EMAIL_PORT')
if os.environ.get('EMAIL_HOST_USER') is not None:
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
if os.environ.get('EMAIL_HOST_PASSWORD') is not None:
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = os.getenv('TIME_ZONE', 'Europe/Amsterdam')
USE_I18N = True
USE_L10N = True
USE_TZ = True
PIPELINE = {
'PIPELINE_ENABLED': not DEBUG or TESTING,
'JS_COMPRESSOR': 'customjsmin.JSMinCompressor',
# Consider https://github.com/mysociety/django-pipeline-csscompressor
'CSS_COMPRESSOR': 'pipeline.compressors.NoopCompressor',
'JAVASCRIPT': {
'vendor': {
'source_filenames': (
'conference_main/scripts/vendor/jquery*.js',
'conference_main/scripts/vendor/popper*.js',
'conference_main/scripts/vendor/bootstrap*.js',
'conference_main/scripts/vendor/js.cookie*.js',
),
'output_filename': 'js/vendors.js',
},
'tutti': {
'source_filenames': ('assets_shared/src/scripts/tutti/*.js',),
'output_filename': 'js/tutti.js',
},
'main': {
'source_filenames': ('conference_main/scripts/main.js',),
'output_filename': 'js/main.js',
},
'js_cookie': {
'source_filenames': (
'conference_main/scripts/vendor/js.cookie*.js',
'conference_main/scripts/tinymce_image_upload_handler.js',
),
'output_filename': 'js/js.cookie.js',
},
'dropzone': {
'source_filenames': ('conference_main/dropzone/dropzone.js',),
'output_filename': 'js/dropzone.js',
},
'slideshow': {
'source_filenames': ('conference_main/scripts/slideshow.js',),
'output_filename': 'js/slideshow.js',
},
'panel': {
'source_filenames': ('conference_main/scripts/panel.js',),
'output_filename': 'js/panel.js',
},
'ticket': {
'source_filenames': (
# cdn.jsdelivr.net/npm/dinero.js@1.9.1/build/umd/dinero.polyfilled.min.js
'tickets/scripts/vendor/dinero-1.9.1.polyfilled.min.js',
'tickets/scripts/util.js',
'tickets/scripts/ticket.js',
),
'output_filename': 'js/ticket.js',
},
'ticket-checkout': {
'source_filenames': (
'tickets/scripts/vendor/dinero-1.9.1.polyfilled.min.js',
# unpkg.com/apollo-client-browser@1.9.0
'tickets/scripts/vendor/apollo-client-browser-1.9.0.min.js',
'tickets/scripts/util.js',
'tickets/scripts/typed_queries.js',
'tickets/scripts/checkout.js',
'tickets/scripts/address_validation_rules.js',
),
'output_filename': 'js/ticket-checkout.js',
},
'ticket-detail': {
'source_filenames': ('tickets/scripts/detail.js',),
'output_filename': 'js/ticket-detail.js',
},
'festival-finals': {
'source_filenames': ('conference_main/scripts/festival-finals.js',),
'output_filename': 'js/festival-finals.js',
},
'web-assets': {
'source_filenames': ('tutti/10_navbar.js',),
'output_filename': 'js/web-assets.js',
'extra_context': {'async': True, 'defer': True},
},
'chartjs': {
'source_filenames': (
'tickets/scripts/vendor/chartjs-4.4.0.min.js',
'tickets/scripts/vendor/luxon.min.js',
'tickets/scripts/vendor/chartjs-adapter-luxon.min.js',
),
'output_filename': 'js/chartjs.js',
},
},
'STYLESHEETS': {
'main': {
'source_filenames': ('conference_main/styles/main.sass',),
'output_filename': 'css/main.css',
'extra_context': {'media': 'screen,projection'},
},
'fullscreen': {
'source_filenames': ('conference_main/styles/fullscreen.sass',),
'output_filename': 'css/fullscreen.css',
'extra_context': {'media': 'screen,projection'},
},
'dropzone': {
'source_filenames': ('conference_main/dropzone/dropzone.css',),
'output_filename': 'css/dropzone.css',
'extra_context': {'media': 'screen,projection'},
},
'bcon20la': {
'source_filenames': ('conference_main/styles/bcon20la/bcon20la.scss',),
'output_filename': 'css/bcon20la.css',
'extra_context': {'media': 'screen,projection'},
},
'bcon20': {
'source_filenames': ('conference_main/styles/bcon20/bcon20.scss',),
'output_filename': 'css/bcon20.css',
'extra_context': {'media': 'screen,projection'},
},
'tickets': {
'source_filenames': ('tickets/styles/tickets.sass',),
'output_filename': 'css/tickets.css',
'extra_context': {'media': 'screen,projection'},
},
},
'COMPILERS': ('libsasscompiler.LibSassCompiler',),
'DISABLE_WRAPPER': True,
}
STATICFILES_DIRS = [
str(BASE_DIR / 'assets_shared'),
str(BASE_DIR / 'assets_shared/src/scripts/'),
]
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'pipeline.finders.PipelineFinder',
)
STORAGES = {
'default': {
'BACKEND': os.getenv('STORAGE_DEFAULT', 'django.core.files.storage.FileSystemStorage'),
'OPTIONS': {
'location': MEDIA_ROOT,
},
},
'staticfiles': {
'BACKEND': os.getenv('STORAGE_STATICFILES', 'pipeline.storage.PipelineManifestStorage'),
},
}
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.getenv('AWS_S3_REGION_NAME')
AWS_S3_ENDPOINT_URL = os.getenv('AWS_S3_ENDPOINT_URL')
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_LOCATION = os.getenv('AWS_LOCATION', '')
AWS_DEFAULT_ACL = 'public-read'
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'default': {'format': '%(asctime)-15s %(levelname)8s %(name)s %(message)s'},
'verbose': {
'format': '%(asctime)-15s %(levelname)8s %(name)s %(process)d %(thread)d %(message)s'
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'default', # Set to 'verbose' in production
'stream': 'ext://sys.stderr',
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'include_html': True,
},
},
'loggers': {
'conference': {'level': 'DEBUG'},
'conference_main': {'level': 'DEBUG'},
'django': {'level': 'INFO'},
# 'sentry.errors': {'level': 'DEBUG', 'handlers': ['console'], 'propagate': False},
'oauthlib': {'level': 'INFO'},
'oauth2_provider': {'level': 'DEBUG'},
},
'root': {
'level': 'WARNING',
'handlers': ['console'],
},
}
SENTRY_DSN = os.getenv('SENTRY_DSN')
if SENTRY_DSN:
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[DjangoIntegration()],
# Set traces_sample_rate to 1.0 to capture 100%
# of transactions for performance monitoring.
# We recommend adjusting this value in production,
traces_sample_rate=1.0,
# If you wish to associate users to errors (assuming you are using
# django.contrib.auth) you may enable sending PII data.
send_default_pii=False,
# By default, the SDK will try to use the SENTRY_RELEASE
# environment variable, or infer a git commit
# SHA as release, however you may want to set
# something more human-readable.
# release="myapp@1.0.0",
)
if TESTING:
# Disable logging output when running tests
LOGGING = {
'version': 1,
'loggers': {
'': {
'level': 'CRITICAL',
},
},
}
SALEOR_API_URL = os.getenv('SALEOR_API_URL', 'https://staging.shop.blender.org/graphql/')
# A token with MANAGE_ORDERS permissions for retrieving order invoices
SALEOR_APP_TOKEN = ''
SALEOR_WEBHOOK_SECRET = ''
STRIPE_API_KEY = os.getenv('STRIPE_API_KEY')
STRIPE_ENDPOINT_SECRET = os.getenv('STRIPE_ENDPOINT_SECRET')
ACTIVE_PAYMENT_BACKEND = os.getenv('ACTIVE_PAYMENT_METHOD', 'stripe')
# An indicator for the maximum amount of tickets that can be sold
TICKET_SALES_CAP = int(os.getenv('TICKET_SALES_CAP', 760))
# Backgroun tasks settings
MAX_ATTEMPTS = 3
if os.environ.get('ADMINS') is not None:
# Expects the following format:
# ADMINS='J Doe: jane@example.com, John Dee: john@example.com'
ADMINS = [[_.strip() for _ in adm.split(':')] for adm in os.environ.get('ADMINS').split(',')]
MAIL_SUBJECT_PREFIX = '[Conference]'
SERVER_EMAIL = f'django@{ALLOWED_HOSTS[0]}'