438 lines
15 KiB
Python
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': False,
|
|
'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]}'
|