class Development(Base): """Development environment settings. We set ``DEBUG`` to ``True`` by default, configure the server to respond to all hosts. """ ALLOWED_HOSTS = ["*"] AWS_BASE_NAME = values.Value("development") DEBUG = values.BooleanValue(True) CLOUDFRONT_SIGNED_URLS_ACTIVE = values.BooleanValue(False) CACHES = { "default": { "BACKEND": "django.core.cache.backends.dummy.DummyCache" } } # Mail EMAIL_HOST = values.Value("mailcatcher") EMAIL_PORT = values.PositiveIntegerValue(1025) # Logging LOGGING = values.DictValue({ "version": 1, "disable_existing_loggers": False, "formatters": { "gelf": { "()": "logging_gelf.formatters.GELFFormatter", "null_character": True, }, }, "handlers": { "console": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", }, "gelf": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", "formatter": "gelf", }, }, "loggers": { "marsha": { "handlers": ["console"], "level": "DEBUG", "propagate": True, }, # This formatter is here as an example to what is possible to do # with xapi loogers. "xapi": { "handlers": ["gelf"], "level": "INFO", "propagate": True }, }, })
class CeleryMixin: CELERY_ACCEPT_CONTENT = values.ListValue(['application/json']) CELERY_ENABLE_UTC = values.BooleanValue(True) CELERY_IMPORTS = values.ListValue([]) CELERY_INCLUDE = values.ListValue([]) CELERY_TIMEZONE = values.Value('UTC') CELERYBEAT_MAX_LOOP_INTERVAL = values.Value(0) CELERYBEAT_SCHEDULE = {} CELERYBEAT_SCHEDULER = values.Value('celery.beat:PersistentScheduler') CELERYBEAT_SCHEDULE_FILENAME = values.Value('celerybeat-schedule') CELERYBEAT_SYNC_EVERY = values.PositiveIntegerValue(0) BROKER_URL = values.Value(None) # BROKER_TRANSPORT BROKER_TRANSPORT_OPTIONS = {} BROKER_CONNECTION_TIMEOUT = values.FloatValue(4.0) BROKER_CONNECTION_RETRY = values.BooleanValue(True) BROKER_CONNECTION_MAX_RETRIES = values.PositiveIntegerValue(100) BROKER_FAILOVER_STRATEGY = values.Value('round-robin') BROKER_HEARTBEAT = values.FloatValue(120.0) BROKER_LOGIN_METHOD = values.Value('AMQPLAIN') BROKER_POOL_LIMIT = values.PositiveIntegerValue(10) BROKER_USE_SSL = values.BooleanValue(False) # CELERY_CACHE_BACKEND no longer used CELERY_CACHE_BACKEND_OPTIONS = {} CASSANDRA_COLUMN_FAMILY = values.Value(None) CASSANDRA_ENTRY_TTL = values.PositiveIntegerValue(None) CASSANDRA_KEYSPACE = values.Value(None) CASSANDRA_PORT = values.PositiveIntegerValue(9042) CASSANDRA_READ_CONSISTENCY = values.Value(None) CASSANDRA_OPTIONS = {} S3_ACCESS_KEY_ID = values.Value(None) S3_SECRET_ACCESS_KEY = values.Value(None) S3_BUCKET = values.Value(None) S3_BASE_PATH = values.Value(None) S3_ENDPOINT_URL = values.Value(None) S3_REGION = values.Value(None) CELERY_COUCHBASE_BACKEND_SETTINGS = {} CELERY_ARANGODB_BACKEND_SETTINGS = {} CELERY_MONGODB_BACKEND_SETTINGS = {} CELERY_EVENT_QUEUE_EXPIRES = values.FloatValue(60.0) CELERY_EVENT_QUEUE_TTL = values.FloatValue(5.0) CELERY_EVENT_QUEUE_PREFIX = values.Value('celeryev') CELERY_EVENT_SERIALIZER = values.Value('json') CELERY_REDIS_DB = values.Value(None) CELERY_REDIS_HOST = values.Value(None) CELERY_REDIS_MAX_CONNECTIONS = values.PositiveIntegerValue(None) CELERY_REDIS_PASSWORD = values.Value(None) CELERY_REDIS_PORT = values.PositiveIntegerValue(None) CELERY_REDIS_BACKEND_USE_SSL = values.BooleanValue(False) CELERY_RESULT_BACKEND = values.Value(None) CELERY_MAX_CACHED_RESULTS = values.BooleanValue(False) CELERY_MESSAGE_COMPRESSION = values.Value(None) CELERY_RESULT_EXCHANGE = values.Value(None) CELERY_RESULT_EXCHANGE_TYPE = values.Value(None) # CELERY_RESULT_EXPIRES timedelta 1 day. CELERY_RESULT_PERSISTENT = values.BooleanValue(False) CELERY_RESULT_SERIALIZER = values.Value('json') CELERY_RESULT_DBURI = values.Value(None) CELERY_RESULT_ENGINE_OPTIONS = {} # _DB_SHORT_LIVED_SESSIONS CELERY_RESULT_DB_TABLE_NAMES = values.ListValue([]) CELERY_SECURITY_CERTIFICATE = values.Value(None) CELERY_SECURITY_CERT_STORE = values.Value(None) CELERY_SECURITY_KEY = values.Value(None) CELERY_ACKS_LATE = values.BooleanValue(False) CELERY_ACKS_ON_FAILURE_OR_TIMEOUT = values.BooleanValue(True) CELERY_ALWAYS_EAGER = values.BooleanValue(False) CELERY_ANNOTATIONS = None # dict/list CELERY_COMPRESSION = values.Value(None) CELERY_CREATE_MISSING_QUEUES = values.BooleanValue(True) CELERY_DEFAULT_DELIVERY_MODE = values.Value('persistent') # CELERY_DEFAULT_EXCHANGE CELERY_DEFAULT_EXCHANGE_TYPE = values.Value('direct') CELERY_DEFAULT_QUEUE = values.Value('celery') CELERY_DEFAULT_RATE_LIMIT = values.Value(None) # CELERY_DEFAULT_ROUTING_KEY str CELERY_EAGER_PROPAGATES = values.BooleanValue(False) CELERY_IGNORE_RESULT = values.BooleanValue(False) CELERY_PUBLISH_RETRY = values.BooleanValue(True) # CELERY_PUBLISH_RETRY_POLICY CELERY_QUEUES = None CELERY_ROUTES = None CELERY_SEND_SENT_EVENT = values.BooleanValue(False) CELERY_SERIALIZER = values.Value('json') CELERYD_SOFT_TIME_LIMIT = values.PositiveIntegerValue(None) CELERYD_TIME_LIMIT = values.PositiveIntegerValue(None) CELERY_TRACK_STARTED = values.BooleanValue(False) CELERYD_AGENT = values.Value(None) CELERYD_AUTOSCALER = values.Value('celery.worker.autoscale:Autoscaler') CELERYD_CONCURRENCY = values.PositiveIntegerValue(None) CELERYD_CONSUMER = values.Value('celery.worker.consumer:Consumer') CELERY_WORKER_DIRECT = values.BooleanValue(False) CELERY_DISABLE_RATE_LIMITS = values.BooleanValue(False) CELERY_ENABLE_REMOTE_CONTROL = values.BooleanValue(True) CELERYD_HIJACK_ROOT_LOGGER = values.BooleanValue(True) # CELERYD_LOG_COLOR CELERYD_LOG_FORMAT = values.Value( '[%(asctime)s: %(levelname)s/%(processName)s] %(message)s') CELERYD_WORKER_LOST_WAIT = values.FloatValue(10.0) CELERYD_MAX_TASKS_PER_CHILD = values.PositiveIntegerValue(None) CELERYD_POOL = values.Value('prefork') # CELERYD_POOL_PUTLOCKS ? CELERYD_POOL_RESTARTS = values.BooleanValue(False) CELERYD_PREFETCH_MULTIPLIER = values.PositiveIntegerValue(4) CELERYD_REDIRECT_STDOUTS = values.BooleanValue(True) CELERYD_REDIRECT_STDOUTS_LEVEL = values.Value('WARNING') CELERY_SEND_EVENTS = values.BooleanValue(False) CELERYD_STATE_DB = values.Value(None) CELERYD_TASK_LOG_FORMAT = values.Value("""[%(asctime)s: %(levelname)s/%(processName)s] [%(task_name)s(%(task_id)s)] %(message)s""") CELERYD_TIMER = values.Value(None) CELERYD_TIMER_PRECISION = values.FloatValue(1.0)
class Base(Configuration): ######################################################################## # # General django settings # https://docs.djangoproject.com/en/3.1/topics/settings/ # ######################################################################## INSTALLED_APPS = [ 'imagetagger.annotations', 'imagetagger.base', 'imagetagger.images', 'imagetagger.users', 'imagetagger.tools', 'imagetagger.administration', 'django.contrib.admin', 'imagetagger.tagger_messages', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'widget_tweaks', 'friendlytagloader', ] 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', 'django.middleware.locale.LocaleMiddleware', ] 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', 'imagetagger.base.context_processors.base_data', ], }, }, ] ROOT_URLCONF = 'imagetagger.urls' WSGI_APPLICATION = 'imagetagger.wsgi.application' FILE_UPLOAD_HANDLERS = [ "django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler", ] # Password validation # https://docs.djangoproject.com/en/1.10/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', }, ] # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ USE_I18N = True USE_L10N = True USE_TZ = True AUTH_USER_MODEL = 'users.User' PROBLEMS_URL = 'https://github.com/bit-bots/imagetagger/issues' PROBLEMS_TEXT = '' LOGIN_URL = '/user/login/' LOGIN_REDIRECT_URL = '/images/' # Flash Messages # https://docs.djangoproject.com/en/3.1/ref/contrib/messages/ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' MESSAGE_TAGS = { messages.INFO: 'info', messages.ERROR: 'danger', messages.WARNING: 'warning', messages.SUCCESS: 'success', } # Sets the default expire time for new messages in days DEFAULT_EXPIRE_TIME = 7 # Sets the default number of messages per page MESSAGES_PER_PAGE = 10 # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ STATIC_URL = '/static/' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' ######################################################################## # # Computed properties and hooks # ######################################################################## @property def DATABASES(self): """https://docs.djangoproject.com/en/1.10/ref/settings/#databases""" return { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'HOST': self.DB_HOST, 'PORT': self.DB_PORT, 'NAME': self.DB_NAME, 'USER': self.DB_USER, 'PASSWORD': self.DB_PASSWORD } } @classmethod def post_setup(cls): super().post_setup() if cls.SENTRY_REPORTING_ENABLED: try: import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration sentry_sdk.init( dsn=cls.SENTRY_DSN, integrations=[DjangoIntegration()], # If you wish to associate users to errors you may enable sending PII data. send_default_pii=cls.SENTRY_SEND_DEFAULT_PII) except ImportError: raise ImproperlyConfigured( "Could not import sentry although the server is configured to use it" ) ######################################################################## # # User-adaptable settings # ######################################################################## DEBUG = values.BooleanValue(environ_prefix='IT', default=False) SECRET_KEY = values.SecretValue(environ_prefix='IT') DB_HOST = values.Value(environ_prefix='IT', environ_required=True) DB_PORT = values.PositiveIntegerValue(environ_prefix='IT', default=5432) DB_NAME = values.Value(environ_prefix='IT', default='imagetagger') DB_USER = values.Value(environ_prefix='IT', default=DB_NAME) DB_PASSWORD = values.Value(environ_prefix='IT', environ_required=True) ALLOWED_HOSTS = values.ListValue(environ_prefix='IT', environ_required=True) LANGUAGE_CODE = values.Value(environ_prefix='IT', default='en-us') TIME_ZONE = values.Value(environ_prefix='IT', default='Europe/Berlin') STATIC_ROOT = values.Value(environ_prefix='IT', default=path_join(BASE_DIR, 'static')) USE_IMPRINT = values.BooleanValue(environ_prefix='IT', default=False) IMPRINT_NAME = values.Value(environ_prefix='IT') IMPRINT_URL = values.Value(environ_prefix='IT') # the URL where the ImageTagger is hosted e.g. https://imagetagger.bit-bots.de DOWNLOAD_BASE_URL = values.Value(environ_prefix='IT', environ_required=True) TOOLS_ENABLED = values.BooleanValue(environ_prefix='IT', default=True) TOOL_UPLOAD_NOTICE = values.Value(environ_prefix='IT', default='') ENABLE_ZIP_DOWNLOAD = values.BooleanValue(environ_prefix='IT', default=is_in_docker()) USE_NGINX_IMAGE_PROVISION = values.BooleanValue(environ_prefix='IT', default=is_in_docker()) FS_URL = values.Value(environ_prefix='IT', default=path_join(BASE_DIR, 'data')) TMP_FS_URL = values.Value(environ_prefix='IT', default='temp://imagetagger') UPLOAD_NOTICE = values.Value( environ_prefix='IT', default='By uploading images to this tool you accept that ' 'the images get published under creative commons license ' 'and confirm that you have the permission to do so.') EXPORT_SEPARATOR = values.Value(environ_prefix='IT', default='|') # the path to the folder with the imagesets relative to the filesystem root (see FS_URL) IMAGE_PATH = values.Value(environ_prefix='IT', default='images') # the path to use for temporary image files relative to the temp filesystem (see TMP_FS_URL) TMP_IMAGE_PATH = values.Value(environ_prefix='IT', default='images') # the path to the folder with the tools relative to the filesystem root (see FS_URL) TOOLS_PATH = values.Value(environ_prefix='IT', default='tools') # filename extension of accepted imagefiles IMAGE_EXTENSION = values.ListValue(environ_prefix='IT', default=['png', 'jpeg']) ACCOUNT_ACTIVATION_DAYS = values.PositiveIntegerValue(environ_prefix='IT', default=7) SENTRY_REPORTING_ENABLED = values.BooleanValue(environ_prefix='IT', default=False) SENTRY_DSN = values.Value(environ_prefix='IT', environ_required=SENTRY_REPORTING_ENABLED) SENTRY_SEND_DEFAULT_PII = values.BooleanValue(environ_prefix='IT', default=False)
class Base(Configuration): """Base configuration every configuration (aka environment) should inherit from. It depends on an environment variable that SHOULD be defined: - DJANGO_SECRET_KEY You may also want to override default configuration by setting the following environment variables: - DJANGO_DEBUG """ DATA_DIR = values.Value(os.path.join("/", "data")) # Static files (CSS, JavaScript, Images) STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"), ) STATIC_URL = "/static/" MEDIA_URL = "/media/" # Allow to configure location of static/media files for non-Docker installation MEDIA_ROOT = values.Value(os.path.join(str(DATA_DIR), "media")) STATIC_ROOT = values.Value(os.path.join(str(DATA_DIR), "static")) SECRET_KEY = values.SecretValue() DEBUG = values.BooleanValue(False) DATABASES = { "default": { "ENGINE": values.Value( "django.db.backends.postgresql", environ_name="DATABASE_ENGINE", environ_prefix=None, ), "NAME": values.Value("marsha", environ_name="POSTGRES_DB", environ_prefix=None), "USER": values.Value("marsha_user", environ_name="POSTGRES_USER", environ_prefix=None), "PASSWORD": values.Value("pass", environ_name="POSTGRES_PASSWORD", environ_prefix=None), "HOST": values.Value("localhost", environ_name="POSTGRES_HOST", environ_prefix=None), "PORT": values.Value(5432, environ_name="POSTGRES_PORT", environ_prefix=None), } } ALLOWED_HOSTS = [] SITE_ID = 1 SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = "DENY" SECURE_REFERRER_POLICY = "same-origin" SILENCED_SYSTEM_CHECKS = values.ListValue([]) CSRF_TRUSTED_ORIGINS = values.ListValue([]) # Application definition INSTALLED_APPS = [ "django.contrib.admin.apps.SimpleAdminConfig", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.sites", "django.contrib.staticfiles", "django_extensions", "dockerflow.django", "waffle", "rest_framework", "corsheaders", "marsha.core.apps.CoreConfig", "marsha.bbb.apps.BbbConfig", "marsha.websocket.apps.WebsocketConfig", "channels", ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "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", "dockerflow.django.middleware.DockerflowMiddleware", "waffle.middleware.WaffleMiddleware", ] ROOT_URLCONF = "marsha.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", ] }, }] AUTH_USER_MODEL = "core.User" ASGI_APPLICATION = "marsha.asgi.application" # For sentinels: # CHANNEL_LAYERS = { # "default": { # "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer", # "CONFIG": { # "hosts": [{ # "sentinels": [(SENTINEL_HOST, SENTINEL_PORT)], # "master_name": SENTINEL_MASTER, # }], # }, # }, # } CHANNEL_LAYERS = { "default": { "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer", "CONFIG": { "hosts": values.ListValue([("redis", 6379)], environ_name="REDIS_HOST", environ_prefix=None), }, }, } REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework_simplejwt.authentication.JWTTokenUserAuthentication", ), "EXCEPTION_HANDLER": "marsha.core.views.exception_handler", "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": 50, "DEFAULT_THROTTLE_RATES": { "anon": "3/minute", "live_registration": "3/minute", }, } # WAFFLE WAFFLE_CREATE_MISSING_SWITCHES = True # Password validation # https://docs.djangoproject.com/en/2.0/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" }, ] JWT_SIGNING_KEY = values.Value(SECRET_KEY) # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ # Django sets `LANGUAGES` by default with all supported languages. Let's save it to a # different setting before overriding it with the languages active in Marsha. We can use it # for example for the choice of time text tracks languages which should not be limited to # the few languages active in Marsha. # pylint: disable=no-member ALL_LANGUAGES = Configuration.LANGUAGES LANGUAGE_CODE = "en-us" # Careful! Languages should be ordered by priority, as this tuple is used to get # fallback/default languages throughout the app. # Use "en" as default as it is the language that is most likely to be spoken by any visitor # when their preferred language, whatever it is, is unavailable LANGUAGES = [("en", _("english")), ("fr", _("french"))] LANGUAGES_DICT = dict(LANGUAGES) # Internationalization TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True REACT_LOCALES = values.ListValue(["en_US", "es_ES", "fr_FR", "fr_CA"]) DEFAULT_LTI_LAUNCH_PRESENTATION_LOCALE = values.Value("en") VIDEO_RESOLUTIONS = [144, 240, 480, 720, 1080] STORAGE_BACKEND = values.Value("marsha.core.storage.s3") # Logging LOGGING = values.DictValue({ "version": 1, "disable_existing_loggers": False, "handlers": { "console": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", }, }, "loggers": { "marsha": { "handlers": ["console"], "level": "INFO", "propagate": True }, }, }) # AWS AWS_ACCESS_KEY_ID = values.SecretValue() AWS_SECRET_ACCESS_KEY = values.SecretValue() AWS_S3_REGION_NAME = values.Value("eu-west-1") AWS_S3_URL_PROTOCOL = values.Value("https") AWS_BASE_NAME = values.Value() UPDATE_STATE_SHARED_SECRETS = values.ListValue() AWS_UPLOAD_EXPIRATION_DELAY = values.Value(24 * 60 * 60) # 24h AWS_MEDIALIVE_ROLE_ARN = values.SecretValue() AWS_MEDIAPACKAGE_HARVEST_JOB_ARN = values.SecretValue() # BBB BBB_ENABLED = values.BooleanValue(False) BBB_API_ENDPOINT = values.Value() BBB_API_SECRET = values.Value(None) # LIVE_RAW LIVE_RAW_ENABLED = values.BooleanValue(False) # Cloud Front key pair for signed urls CLOUDFRONT_ACCESS_KEY_ID = values.Value(None) CLOUDFRONT_PRIVATE_KEY_PATH = values.Value( os.path.join(BASE_DIR, "..", ".ssh", "cloudfront_private_key")) CLOUDFRONT_SIGNED_URLS_ACTIVE = values.BooleanValue(True) CLOUDFRONT_SIGNED_URLS_VALIDITY = 2 * 60 * 60 # 2 hours CLOUDFRONT_DOMAIN = values.Value(None) BYPASS_LTI_VERIFICATION = values.BooleanValue(False) # Cache APP_DATA_CACHE_DURATION = values.Value(60) # 60 secondes SENTRY_DSN = values.Value(None) # Resource max file size DOCUMENT_SOURCE_MAX_SIZE = values.Value(2**30) # 1GB VIDEO_SOURCE_MAX_SIZE = values.Value(2**30) # 1GB SUBTITLE_SOURCE_MAX_SIZE = values.Value(2**20) # 1MB THUMBNAIL_SOURCE_MAX_SIZE = values.Value(10 * (2**20)) # 10MB SHARED_LIVE_MEDIA_SOURCE_MAX_SIZE = values.Value(300 * (2**20)) # 300MB EXTERNAL_JAVASCRIPT_SCRIPTS = values.ListValue([]) VIDEO_PLAYER = values.Value("videojs") FRONT_UPLOAD_POLL_INTERVAL = values.Value("60") MAINTENANCE_MODE = values.BooleanValue(False) # XMPP Settings LIVE_CHAT_ENABLED = values.BooleanValue(False) XMPP_BOSH_URL = values.Value(None) XMPP_CONVERSE_PERSISTENT_STORE = values.Value("localStorage") XMPP_WEBSOCKET_URL = values.Value(None) XMPP_CONFERENCE_DOMAIN = values.Value(None) XMPP_PRIVATE_ADMIN_JID = values.Value(None) XMPP_PRIVATE_SERVER_PORT = values.Value(5222) XMPP_PRIVATE_SERVER_PASSWORD = values.Value(None) XMPP_JWT_SHARED_SECRET = values.Value(None) XMPP_JWT_ISSUER = values.Value("marsha") XMPP_JWT_AUDIENCE = values.Value("marsha") XMPP_DOMAIN = values.Value(None) # LIVE SETTINGS NB_DAYS_BEFORE_DELETING_LIVE_RECORDINGS = values.Value(14) NB_SECONDS_LIVING_DEV_STACK = values.PositiveIntegerValue(600) LIVE_PLAYLIST_WINDOW_SECONDS = values.PositiveIntegerValue(10) LIVE_SEGMENT_DURATION_SECONDS = values.PositiveIntegerValue(4) LIVE_FRAMERATE_NUMERATOR = values.PositiveIntegerValue(24000) LIVE_FRAMERATE_DENOMINATOR = values.PositiveIntegerValue(1000) LIVE_GOP_SIZE = values.FloatValue(4) # JITSI SETTINGS JITSI_EXTERNAL_API_URL = values.Value( "https://meet.jit.si/external_api.js") JITSI_DOMAIN = values.Value("meet.jit.si") JITSI_CONFIG_OVERWRITE = values.DictValue({}) JITSI_INTERFACE_CONFIG_OVERWRITE = values.DictValue({}) # LIVE PAIRING LIVE_PAIRING_EXPIRATION_SECONDS = 60 # SHARED LIVE MEDIA SETTINGS ALLOWED_SHARED_LIVE_MEDIA_MIME_TYPES = values.ListValue( ["application/pdf"]) # Cors CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False) CORS_ALLOWED_ORIGINS = values.ListValue([]) CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([]) CORS_URLS_REGEX = values.Value(r"^/api/pairing-challenge$") CORS_ALLOW_METHODS = values.ListValue(["POST", "OPTIONS"]) CORS_ALLOW_HEADERS = values.ListValue(list(default_headers)) # Mail EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend") EMAIL_HOST = values.Value(None) EMAIL_HOST_USER = values.Value(None) EMAIL_HOST_PASSWORD = values.Value(None) EMAIL_PORT = values.PositiveIntegerValue(None) EMAIL_USE_TLS = values.BooleanValue(False) EMAIL_FROM = values.Value("*****@*****.**") # REMINDERS SENT for scheduled webinars REMINDER_1, REMINDER_2, REMINDER_3, REMINDER_IS_STARTED, REMINDER_ERROR = ( "REMINDER_1", "REMINDER_2", "REMINDER_3", "REMINDER_IS_STARTED", "REMINDER_ERROR", ) # keys for REMINDERS_STEP ( REMINDER_ELAPSED_LABEL, REGISTER_EXCLUDE_STEP, REMINDER_KEY_REGISTER_BEFORE_S, REMINDER_KEY_STARTS_IN_S, REMINDER_OBJECT_MAIL, ) = ( "REMINDER_ELAPSED_LABEL", "REGISTER_EXCLUDE_STEP", "STARTS_IN_S", "REGISTER_BEFORE_S", "REMINDER_OBJECT_MAIL", ) REMINDERS_STEP = values.DictValue({ REMINDER_1: { REMINDER_ELAPSED_LABEL: _("5 minutes"), REGISTER_EXCLUDE_STEP: [], REMINDER_KEY_STARTS_IN_S: 300, # webinar starts in less than 5 minutes REMINDER_KEY_REGISTER_BEFORE_S: 10800, # three hours before REMINDER_OBJECT_MAIL: _("Live starts in less than 5 minutes"), }, REMINDER_2: { REMINDER_ELAPSED_LABEL: _("3 hours"), REGISTER_EXCLUDE_STEP: [REMINDER_1], # if step REMINDER_1 is done it will cancel this one REMINDER_KEY_REGISTER_BEFORE_S: 86400, # 1 day before in seconds REMINDER_KEY_STARTS_IN_S: 10800, # webinar starts in less than 3 hours REMINDER_OBJECT_MAIL: _("Live starts in less than 3 hours"), }, REMINDER_3: { REMINDER_ELAPSED_LABEL: _("3 days"), REGISTER_EXCLUDE_STEP: [REMINDER_1, REMINDER_2], REMINDER_KEY_REGISTER_BEFORE_S: 30 * 86400, # thirty days before REMINDER_KEY_STARTS_IN_S: 3 * 86400, # webinar starts in less than 3 days REMINDER_OBJECT_MAIL: _("Live starts in less than 3 days"), }, }) REMINDERS_SECRET = values.Value() # pylint: disable=invalid-name @property def AWS_SOURCE_BUCKET_NAME(self): """Source bucket name. If this setting is set in an environment variable we use it. Otherwise the value is computed with the AWS_BASE_NAME value. """ return os.environ.get("DJANGO_AWS_SOURCE_BUCKET_NAME", f"{self.AWS_BASE_NAME}-marsha-source") # pylint: disable=invalid-name @property def AWS_DESTINATION_BUCKET_NAME(self): """Destination bucket name. If this setting is set in an environment variable we use it. Otherwise the value is computed with the AWS_BASE_NAME value. """ return os.environ.get( "DJANGO_AWS_DESTINATION_BUCKET_NAME", f"{self.AWS_BASE_NAME}-marsha-destination", ) # pylint: disable=invalid-name @property def SIMPLE_JWT(self): """Define settings for `djangorestframework_simplejwt`. The JWT_SIGNING_KEY must be evaluated late as the jwt library check for string type. """ return { "ACCESS_TOKEN_LIFETIME": timedelta(days=1), "ALGORITHM": "HS256", "SIGNING_KEY": str(self.JWT_SIGNING_KEY), "USER_ID_CLAIM": "resource_id", "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken", ), } # pylint: disable=invalid-name @property def RELEASE(self): """ Return the release information. Delegate to the module function to enable easier testing. """ return get_release() @classmethod def _get_environment(cls): """Environment in which the application is launched.""" return cls.__name__.lower() # pylint: disable=invalid-name @property def ENVIRONMENT(self): """Environment in which the application is launched.""" return self._get_environment() @classmethod def post_setup(cls): """Post setup configuration. This is the place where you can configure settings that require other settings to be loaded. """ super().post_setup() # The DJANGO_SENTRY_DSN environment variable should be set to activate # sentry for an environment if cls.SENTRY_DSN is not None: sentry_sdk.init( dsn=cls.SENTRY_DSN, environment=cls._get_environment(), release=get_release(), integrations=[DjangoIntegration()], ) with sentry_sdk.configure_scope() as scope: scope.set_extra("application", "backend")
class Base(Configuration): """ This is the base configuration every configuration (aka environnement) should inherit from. It is recommended to configure third-party applications by creating a configuration mixins in ./configurations and compose the Base configuration with those mixins. It depends on an environment variable that SHOULD be defined: * DJANGO_SECRET_KEY You may also want to override default configuration by setting the following environment variables: * DJANGO_SENTRY_DSN * DB_NAME * DB_HOST * DB_PASSWORD * DB_USER """ DEBUG = False # Security ALLOWED_HOSTS = [] SECRET_KEY = values.Value(None) AUTH_USER_MODEL = "ashley.User" # SECURE_PROXY_SSL_HEADER allows to fix the scheme in Django's HttpRequest # object when you application is behind a reverse proxy. # # Keep this SECURE_PROXY_SSL_HEADER configuration only if : # - your Django app is behind a proxy. # - your proxy strips the X-Forwarded-Proto header from all incoming requests # - Your proxy sets the X-Forwarded-Proto header and sends it to Django # # In other cases, you should comment the following line to avoid security issues. SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # Disable Samesite flag in session and csrf cookies, because ashley is meant to # run in an iframe on external websites. # Note : The better solution is to send a flag Samesite=none, because # modern browsers are considering Samesite=Lax by default when the flag is # not specified. CSRF_COOKIE_SAMESITE = "None" SESSION_COOKIE_SAMESITE = "None" # Modern browsers require to have the `secure` attribute on cookies with `Samesite=none` CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True # Privacy SECURE_REFERRER_POLICY = "same-origin" # Application definition ROOT_URLCONF = "urls" WSGI_APPLICATION = "wsgi.application" # Database DATABASES = { "default": { "ENGINE": values.Value( "django.db.backends.postgresql_psycopg2", environ_name="DB_ENGINE", environ_prefix=None, ), "NAME": values.Value("ashley", environ_name="DB_NAME", environ_prefix=None), "USER": values.Value("fun", environ_name="DB_USER", environ_prefix=None), "PASSWORD": values.Value("pass", environ_name="DB_PASSWORD", environ_prefix=None), "HOST": values.Value("localhost", environ_name="DB_HOST", environ_prefix=None), "PORT": values.Value(5432, environ_name="DB_PORT", environ_prefix=None), } } # Static files (CSS, JavaScript, Images) STATIC_URL = "/static/" STATIC_ROOT = os.path.join(DATA_DIR, "static") MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(DATA_DIR, "media") STATICFILES_DIRS = (MACHINA_MAIN_STATIC_DIR, ) # AWS AWS_ACCESS_KEY_ID = values.SecretValue() AWS_LOCATION = values.Value("media/") AWS_S3_CUSTOM_DOMAIN = values.Value() AWS_S3_REGION_NAME = values.Value() AWS_S3_URL_PROTOCOL = values.Value("https") AWS_SECRET_ACCESS_KEY = values.SecretValue() AWS_STORAGE_BUCKET_NAME = values.Value() AWS_QUERYSTRING_AUTH = False DEFAULT_FILE_STORAGE = values.Value( "storages.backends.s3boto3.S3Boto3Storage") # Internationalization TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True # Templates TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [ASHLEY_MAIN_TEMPLATE_DIR, MACHINA_MAIN_TEMPLATE_DIR], "OPTIONS": { "context_processors": [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "django.template.context_processors.csrf", "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.request", "django.template.context_processors.tz", "ashley.context_processors.site_metas", "machina.core.context_processors.metadata", ], "loaders": [ "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ], }, }, ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "dockerflow.django.middleware.DockerflowMiddleware", "ashley.machina_extensions.forum_permission.middleware.ForumPermissionMiddleware", ] AUTHENTICATION_BACKENDS = [ "ashley.auth.backend.LTIBackend", "django.contrib.auth.backends.ModelBackend", ] # Django applications from the highest priority to the lowest INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # Django machina dependencies "mptt", "haystack", "widget_tweaks", # Ashley "ashley", "ashley.machina_extensions.forum", "ashley.machina_extensions.forum_conversation", "ashley.machina_extensions.forum_moderation", "ashley.machina_extensions.forum_permission", "ashley.machina_extensions.forum_search", # Django LTI Toolbox "lti_toolbox", # Third party apps "dockerflow.django", # Django machina "machina", "machina.apps.forum_conversation.forum_attachments", "machina.apps.forum_conversation.forum_polls", "machina.apps.forum_feeds", "machina.apps.forum_member", "machina.apps.forum_tracking", "rest_framework", ] # Languages LANGUAGE_CODE = "en-us" # Cache CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache" }, "machina_attachments": { "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", "LOCATION": values.Value( tempfile.gettempdir(), environ_name="TMP_ATTACHMENT_DIR", environ_prefix=None, ), }, } # Search engine HAYSTACK_CONNECTIONS = { "default": { "ENGINE": "haystack.backends.elasticsearch5_backend.Elasticsearch5SearchEngine", "URL": "{:s}:{!s}".format( values.Value( "elasticsearch", environ_name="ELASTICSEARCH_HOST", environ_prefix=None, ), values.Value(9200, environ_name="ELASTICSEARCH_PORT", environ_prefix=None), ), "INDEX_NAME": values.Value("ashley", environ_name="ELASTICSEARCH_INDEX_NAME", environ_prefix=None), }, } # Machina MACHINA_MARKUP_LANGUAGE = ("ashley.editor.draftjs_renderer", {}) MACHINA_MARKUP_MAX_LENGTH_VALIDATOR = ( "ashley.validators.MarkupNullableMaxLengthValidator") MACHINA_MARKUP_WIDGET = "ashley.editor.widgets.DraftEditor" MACHINA_PROFILE_AVATARS_ENABLED = False MACHINA_USER_DISPLAY_NAME_METHOD = "get_public_username_with_default" MAX_UPLOAD_FILE_MB = values.PositiveIntegerValue(5) IMAGE_TYPE_ALLOWED = values.ListValue(["gif", "jpeg", "jpg", "png", "svg"]) # Sentry SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN") REST_FRAMEWORK = { "ALLOWED_VERSIONS": ("1.0", ), "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework.authentication.SessionAuthentication", ), "DEFAULT_VERSION": "1.0", "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning", } @classmethod def post_setup(cls): """Post setup configuration. This is the place where you can configure settings that require other settings to be loaded. """ super().post_setup() # The SENTRY_DSN setting should be available to activate sentry for an environment if cls.SENTRY_DSN is not None: sentry_sdk.init( dsn=cls.SENTRY_DSN, environment=cls.__name__.lower(), release=get_release(), integrations=[DjangoIntegration()], ) with sentry_sdk.configure_scope() as scope: scope.set_extra("application", "backend")
class Development(Base): """Development environment settings. We set ``DEBUG`` to ``True`` by default, configure the server to respond to all hosts. """ ALLOWED_HOSTS = ["*"] AWS_BASE_NAME = values.Value("development") DEBUG = values.BooleanValue(True) CLOUDFRONT_SIGNED_URLS_ACTIVE = values.BooleanValue(False) CACHES = { "default": { "BACKEND": "django.core.cache.backends.dummy.DummyCache" } } STAT_BACKEND = values.Value("marsha.core.stats.dummy_backend") # Mail EMAIL_HOST = values.Value("mailcatcher") EMAIL_PORT = values.PositiveIntegerValue(1025) # Logging LOGGING = values.DictValue({ "version": 1, "disable_existing_loggers": False, "formatters": { "gelf": { "()": "logging_gelf.formatters.GELFFormatter", "null_character": True, }, }, "handlers": { "console": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", }, "gelf": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", "formatter": "gelf", }, }, "loggers": { "marsha": { "handlers": ["console"], "level": "DEBUG", "propagate": True, }, # This formatter is here as an example to what is possible to do # with xapi loogers. "xapi": { "handlers": ["gelf"], "level": "INFO", "propagate": True }, }, }) @classmethod def setup(cls): # Add settings from the Renater FER SAML testing suite # pylint: disable=import-outside-toplevel from social_edu_federation.testing.settings import saml_fer_settings # pylint: enable=import-outside-toplevel for setting_name, setting_value in saml_fer_settings.items(): setattr(cls, setting_name, values.Value(setting_value)) cls.SOCIAL_AUTH_SAML_FER_IDP_FAKER = values.Value(True) cls.SOCIAL_AUTH_SAML_FER_FEDERATION_SAML_METADATA_URL = values.Value( # Metadata is fetched from inside the docker, hence the 8000 port "http://localhost:8000/account/saml/idp/metadata/") # Call setup afterward super().setup()
class Base(Configuration): """Base configuration every configuration (aka environment) should inherit from. It depends on an environment variable that SHOULD be defined: - DJANGO_SECRET_KEY You may also want to override default configuration by setting the following environment variables: - DJANGO_DEBUG """ DATA_DIR = values.Value(os.path.join("/", "data")) # Static files (CSS, JavaScript, Images) STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"), ) STATIC_URL = "/static/" MEDIA_URL = "/media/" # Allow to configure location of static/media files for non-Docker installation MEDIA_ROOT = values.Value(os.path.join(str(DATA_DIR), "media")) STATIC_ROOT = values.Value(os.path.join(str(DATA_DIR), "static")) SECRET_KEY = values.SecretValue() DEBUG = values.BooleanValue(False) DATABASES = { "default": { "ENGINE": values.Value( "django.db.backends.postgresql", environ_name="DATABASE_ENGINE", environ_prefix=None, ), "NAME": values.Value("marsha", environ_name="POSTGRES_DB", environ_prefix=None), "USER": values.Value("marsha_user", environ_name="POSTGRES_USER", environ_prefix=None), "PASSWORD": values.Value("pass", environ_name="POSTGRES_PASSWORD", environ_prefix=None), "HOST": values.Value("localhost", environ_name="POSTGRES_HOST", environ_prefix=None), "PORT": values.Value(5432, environ_name="POSTGRES_PORT", environ_prefix=None), } } ALLOWED_HOSTS = [] SITE_ID = 1 SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = "DENY" SECURE_REFERRER_POLICY = "same-origin" SILENCED_SYSTEM_CHECKS = values.ListValue([]) CSRF_TRUSTED_ORIGINS = values.ListValue([]) # Application definition INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.sites", "django.contrib.staticfiles", "django_extensions", "dockerflow.django", "waffle", "rest_framework", "corsheaders", "channels", "parler", # django-parler, for translated models "social_django.apps.PythonSocialAuthConfig", # python-social-auth for Django "social_edu_federation.django.apps.PythonSocialEduFedAuthConfig", # Marsha "marsha.account.apps.AccountConfig", "marsha.core.apps.MarshaAdminConfig", "marsha.core.apps.CoreConfig", "marsha.bbb.apps.BbbConfig", "marsha.markdown.apps.MarkdownConfig", "marsha.websocket.apps.WebsocketConfig", ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "dockerflow.django.middleware.DockerflowMiddleware", "waffle.middleware.WaffleMiddleware", "social_django.middleware.SocialAuthExceptionMiddleware", ] ROOT_URLCONF = "marsha.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", "social_django.context_processors.backends", "social_django.context_processors.login_redirect", ] }, }] AUTH_USER_MODEL = "core.User" AUTHENTICATION_BACKENDS = [ "social_edu_federation.backends.saml_fer.FERSAMLAuth", "django.contrib.auth.backends.ModelBackend", ] ASGI_APPLICATION = "marsha.asgi.application" # For sentinels: # CHANNEL_LAYERS = { # "default": { # "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer", # "CONFIG": { # "hosts": [{ # "sentinels": [(SENTINEL_HOST, SENTINEL_PORT)], # "master_name": SENTINEL_MASTER, # }], # }, # }, # } CHANNEL_LAYERS = { "default": { "BACKEND": "marsha.websocket.layers.JsonRedisChannelLayer", "CONFIG": { "hosts": values.ListValue([("redis", 6379)], environ_name="REDIS_HOST", environ_prefix=None), }, }, } REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework_simplejwt.authentication.JWTStatelessUserAuthentication", ), "EXCEPTION_HANDLER": "marsha.core.views.exception_handler", "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": 50, "DEFAULT_THROTTLE_RATES": { "anon": values.Value("3/minute", environ_name="REST_FRAMEWORK_ANON_THROTTLE_RATE"), "live_session": values.Value( "3/minute", environ_name="REST_FRAMEWORK_LIVE_SESSION_THROTTLE_RATE"), }, } # WAFFLE WAFFLE_CREATE_MISSING_SWITCHES = True # User login base Django settings LOGIN_REDIRECT_URL = "site" # redirect to home (React site) LOGIN_URL = "account:login" # Password validation # https://docs.djangoproject.com/en/2.0/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" }, ] JWT_SIGNING_KEY = values.Value(SECRET_KEY) # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ # Django sets `LANGUAGES` by default with all supported languages. Let's save it to a # different setting before overriding it with the languages active in Marsha. We can use it # for example for the choice of time text tracks languages which should not be limited to # the few languages active in Marsha. # pylint: disable=no-member ALL_LANGUAGES = Configuration.LANGUAGES LANGUAGE_CODE = "en-us" # Careful! Languages should be ordered by priority, as this tuple is used to get # fallback/default languages throughout the app. # Use "en" as default as it is the language that is most likely to be spoken by any visitor # when their preferred language, whatever it is, is unavailable LANGUAGES = [("en", _("english")), ("fr", _("french"))] LANGUAGES_DICT = dict(LANGUAGES) LOCALE_PATHS = (os.path.join(BASE_DIR, "../locale"), ) # Internationalization TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True REACT_LOCALES = values.ListValue(["en_US", "es_ES", "fr_FR", "fr_CA"]) DEFAULT_LTI_LAUNCH_PRESENTATION_LOCALE = values.Value("en") PARLER_DEFAULT_LANGUAGE_CODE = LANGUAGES[0][0] PARLER_LANGUAGES = { None: tuple({"code": code} for code in LANGUAGES_DICT.keys()), "default": { "fallbacks": [PARLER_DEFAULT_LANGUAGE_CODE ], # defaults to PARLER_DEFAULT_LANGUAGE_CODE "hide_untranslated": False, }, } # Admin tabs don't show up when only Site `None` is defined PARLER_LANGUAGES[SITE_ID] = PARLER_LANGUAGES[None] VIDEO_RESOLUTIONS = [144, 240, 480, 720, 1080] STORAGE_BACKEND = values.Value("marsha.core.storage.s3") # Logging LOGGING = values.DictValue({ "version": 1, "disable_existing_loggers": False, "handlers": { "console": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", }, }, "loggers": { "marsha": { "handlers": ["console"], "level": "INFO", "propagate": True }, }, }) # AWS AWS_ACCESS_KEY_ID = values.SecretValue() AWS_SECRET_ACCESS_KEY = values.SecretValue() AWS_S3_REGION_NAME = values.Value("eu-west-1") AWS_S3_URL_PROTOCOL = values.Value("https") AWS_BASE_NAME = values.Value() UPDATE_STATE_SHARED_SECRETS = values.ListValue() AWS_UPLOAD_EXPIRATION_DELAY = values.Value(24 * 60 * 60) # 24h AWS_MEDIALIVE_ROLE_ARN = values.SecretValue() AWS_MEDIAPACKAGE_HARVEST_JOB_ARN = values.SecretValue() AWS_MEDIALIVE_INPUT_WAITER_DELAY = values.PositiveIntegerValue(5) AWS_MEDIALIVE_INPUT_WAITER_MAX_ATTEMPTS = values.PositiveIntegerValue(84) # LTI Config LTI_CONFIG_TITLE = values.Value("Marsha") LTI_CONFIG_DESCRIPTION = values.Value( "An LTI first, opensource and extensible Learning Content Management System" ) LTI_CONFIG_ICON = values.Value("marsha_32x32_blue.png") LTI_CONFIG_URL = values.Value() LTI_CONFIG_CONTACT_EMAIL = values.Value() # BBB BBB_ENABLED = values.BooleanValue(False) BBB_API_ENDPOINT = values.Value() BBB_API_SECRET = values.Value(None) # Markdown application MARKDOWN_ENABLED = values.BooleanValue(False) # LIVE_RAW LIVE_RAW_ENABLED = values.BooleanValue(False) # Cloud Front key pair for signed urls CLOUDFRONT_PRIVATE_KEY_PATH = values.Value( os.path.join(BASE_DIR, "..", ".ssh", "cloudfront_private_key")) CLOUDFRONT_SIGNED_URLS_ACTIVE = values.BooleanValue(True) CLOUDFRONT_SIGNED_URLS_VALIDITY = 2 * 60 * 60 # 2 hours CLOUDFRONT_SIGNED_URL_CACHE_DURATION = values.Value(900) # 15 minutes CLOUDFRONT_SIGNED_PUBLIC_KEY_ID = values.Value(None) CLOUDFRONT_DOMAIN = values.Value(None) BYPASS_LTI_VERIFICATION = values.BooleanValue(False) # Cache APP_DATA_CACHE_DURATION = values.Value(60) # 60 secondes VIDEO_ATTENDANCES_CACHE_DURATION = values.Value(300) # 5 minutes SENTRY_DSN = values.Value(None) # Resource max file size DOCUMENT_SOURCE_MAX_SIZE = values.Value(2**30) # 1GB VIDEO_SOURCE_MAX_SIZE = values.Value(2**30) # 1GB SUBTITLE_SOURCE_MAX_SIZE = values.Value(2**20) # 1MB THUMBNAIL_SOURCE_MAX_SIZE = values.Value(10 * (2**20)) # 10MB SHARED_LIVE_MEDIA_SOURCE_MAX_SIZE = values.Value(300 * (2**20)) # 300MB EXTERNAL_JAVASCRIPT_SCRIPTS = values.ListValue([]) VIDEO_PLAYER = values.Value("videojs") FRONT_UPLOAD_POLL_INTERVAL = values.Value("60") MAINTENANCE_MODE = values.BooleanValue(False) # XMPP Settings LIVE_CHAT_ENABLED = values.BooleanValue(False) XMPP_BOSH_URL = values.Value(None) XMPP_CONVERSE_PERSISTENT_STORE = values.Value("localStorage") XMPP_WEBSOCKET_URL = values.Value(None) XMPP_CONFERENCE_DOMAIN = values.Value(None) XMPP_PRIVATE_ADMIN_JID = values.Value(None) XMPP_PRIVATE_SERVER_PORT = values.Value(5222) XMPP_PRIVATE_SERVER_PASSWORD = values.Value(None) XMPP_JWT_SHARED_SECRET = values.Value(None) XMPP_JWT_ISSUER = values.Value("marsha") XMPP_JWT_AUDIENCE = values.Value("marsha") XMPP_DOMAIN = values.Value(None) # LIVE SETTINGS NB_DAYS_BEFORE_DELETING_LIVE_RECORDINGS = values.Value(14) NB_SECONDS_LIVING_DEV_STACK = values.PositiveIntegerValue(600) LIVE_PLAYLIST_WINDOW_SECONDS = values.PositiveIntegerValue(10) LIVE_SEGMENT_DURATION_SECONDS = values.PositiveIntegerValue(4) LIVE_FRAMERATE_NUMERATOR = values.PositiveIntegerValue(24000) LIVE_FRAMERATE_DENOMINATOR = values.PositiveIntegerValue(1000) LIVE_GOP_SIZE = values.FloatValue(4) # JITSI SETTINGS JITSI_EXTERNAL_API_URL = values.Value( "https://meet.jit.si/external_api.js") JITSI_DOMAIN = values.Value("meet.jit.si") JITSI_CONFIG_OVERWRITE = values.DictValue({}) JITSI_INTERFACE_CONFIG_OVERWRITE = values.DictValue({}) JITSI_JWT_APP_ID = values.Value() JITSI_JWT_APP_SECRET = values.Value() JITSI_JWT_TOKEN_EXPIRATION_SECONDS = values.PositiveIntegerValue(600) # LIVE PAIRING LIVE_PAIRING_EXPIRATION_SECONDS = 60 # SHARED LIVE MEDIA SETTINGS ALLOWED_SHARED_LIVE_MEDIA_MIME_TYPES = values.ListValue( ["application/pdf"]) # Cors CORS_ALLOW_ALL_ORIGINS = values.BooleanValue(False) CORS_ALLOWED_ORIGINS = values.ListValue([]) CORS_ALLOWED_ORIGIN_REGEXES = values.ListValue([]) CORS_URLS_REGEX = values.Value(r"^/api/pairing-challenge$") CORS_ALLOW_METHODS = values.ListValue(["POST", "OPTIONS"]) CORS_ALLOW_HEADERS = values.ListValue(list(default_headers)) # Mail EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend") EMAIL_HOST = values.Value(None) EMAIL_HOST_USER = values.Value(None) EMAIL_HOST_PASSWORD = values.Value(None) EMAIL_PORT = values.PositiveIntegerValue(None) EMAIL_USE_TLS = values.BooleanValue(False) EMAIL_FROM = values.Value("*****@*****.**") # REMINDERS SENT for scheduled webinars REMINDER_1, REMINDER_2, REMINDER_3, REMINDER_DATE_UPDATED, REMINDER_ERROR = ( "REMINDER_1", "REMINDER_2", "REMINDER_3", "REMINDER_DATE_UPDATED", "REMINDER_ERROR", ) # keys for REMINDERS_STEP ( REMINDER_ELAPSED_LABEL, REGISTER_EXCLUDE_STEP, REMINDER_KEY_REGISTER_BEFORE_S, REMINDER_KEY_STARTS_IN_S, REMINDER_OBJECT_MAIL, ) = ( "REMINDER_ELAPSED_LABEL", "REGISTER_EXCLUDE_STEP", "STARTS_IN_S", "REGISTER_BEFORE_S", "REMINDER_OBJECT_MAIL", ) REMINDERS_STEP = values.DictValue({ REMINDER_1: { REMINDER_ELAPSED_LABEL: _("5 minutes"), REGISTER_EXCLUDE_STEP: [], REMINDER_KEY_STARTS_IN_S: 300, # webinar starts in less than 5 minutes REMINDER_KEY_REGISTER_BEFORE_S: 10800, # three hours before REMINDER_OBJECT_MAIL: _("Live starts in less than 5 minutes"), }, REMINDER_2: { REMINDER_ELAPSED_LABEL: _("3 hours"), REGISTER_EXCLUDE_STEP: [REMINDER_1], # if step REMINDER_1 is done it will cancel this one REMINDER_KEY_REGISTER_BEFORE_S: 86400, # 1 day before in seconds REMINDER_KEY_STARTS_IN_S: 10800, # webinar starts in less than 3 hours REMINDER_OBJECT_MAIL: _("Live starts in less than 3 hours"), }, REMINDER_3: { REMINDER_ELAPSED_LABEL: _("3 days"), REGISTER_EXCLUDE_STEP: [REMINDER_1, REMINDER_2], REMINDER_KEY_REGISTER_BEFORE_S: 30 * 86400, # thirty days before REMINDER_KEY_STARTS_IN_S: 3 * 86400, # webinar starts in less than 3 days REMINDER_OBJECT_MAIL: _("Live starts in less than 3 days"), }, }) REMINDERS_SECRET = values.Value() # Settings related to statistics in potsie project STAT_BACKEND = values.Value("marsha.core.stats.grafana_xapi_fun_backend") STAT_BACKEND_SETTINGS = values.DictValue({ "api_key": values.Value(environ_name="GRAFANA_XAPI_FUN_API_KEY", environ_prefix=None), "api_endpoint": values.Value(environ_name="GRAFANA_XAPI_FUN_API_ENDPOINT", environ_prefix=None), "api_datasource_id": values.Value(environ_name="GRAFANA_XAPI_FUN_API_DATASOURCE_ID", environ_prefix=None), "api_datastream": values.Value(environ_name="GRAFANA_XAPI_FUN_API_DATASTREAM", environ_prefix=None), }) ATTENDANCE_POINTS = values.Value(20) ATTENDANCE_PUSH_DELAY = values.Value(60) # Python social auth SOCIAL_AUTH_JSONFIELD_ENABLED = True SOCIAL_AUTH_URL_NAMESPACE = "account:social" SOCIAL_AUTH_SAML_FER_IDP_FAKER = False SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG = { "authnRequestsSigned": values.BooleanValue( default=True, environ_name= "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_AUTH_REQUEST_SIGNED", ), "signMetadata": values.BooleanValue( default=True, environ_name="SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_SIGN_METADATA", ), "wantMessagesSigned": values.BooleanValue( default=True, environ_name= "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_WANT_MESSAGES_SIGNED", ), "wantAssertionsSigned": values.BooleanValue( default=True, environ_name= "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_WANT_ASSERTIONS_SIGNED", ), "wantAssertionsEncrypted": values.BooleanValue( default=True, environ_name= "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_WANT_ASSERTIONS_ENCRYPTED", ), "rejectDeprecatedAlgorithm": values.BooleanValue( default=True, environ_name= "SOCIAL_AUTH_SAML_FER_SECURITY_CONFIG_REJECT_DEPRECATED_ALGORITHM", ), } # SOCIAL_AUTH_SAML_FER_SP_ENTITY_ID should be a URL that includes a domain name you own SOCIAL_AUTH_SAML_FER_SP_ENTITY_ID = values.Value() # SOCIAL_AUTH_SAML_FER_SP_PUBLIC_CERT X.509 certificate for the key pair that # your app will use SOCIAL_AUTH_SAML_FER_SP_PUBLIC_CERT = values.Value() # SOCIAL_AUTH_SAML_FER_SP_PRIVATE_KEY The private key to be used by your app SOCIAL_AUTH_SAML_FER_SP_PRIVATE_KEY = values.Value() # Next certificate management, keep empty when next certificate is still not known SOCIAL_AUTH_SAML_FER_SP_NEXT_PUBLIC_CERT = values.Value() SOCIAL_AUTH_SAML_FER_SP_EXTRA = ({ "x509certNew": SOCIAL_AUTH_SAML_FER_SP_NEXT_PUBLIC_CERT, } if SOCIAL_AUTH_SAML_FER_SP_NEXT_PUBLIC_CERT else {}) SOCIAL_AUTH_SAML_FER_ORG_INFO = { # specify values for English at a minimum "en-US": { "name": values.Value( "marsha", environ_name="SOCIAL_AUTH_SAML_FER_ORG_INFO_NAME", ), "displayname": values.Value( "Marsha", environ_name="SOCIAL_AUTH_SAML_FER_ORG_INFO_DISPLAY_NAME", ), "url": values.Value(environ_name="SOCIAL_AUTH_SAML_FER_ORG_INFO_URL", ), } } # SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT technical contact responsible for your app SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT = { "givenName": values.Value( "Marsha dev team", environ_name="SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT_NAME", ), "emailAddress": values.Value( environ_name="SOCIAL_AUTH_SAML_FER_TECHNICAL_CONTACT_EMAIL", ), } # SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT support contact for your app SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT = { "givenName": values.Value( "Marsha dev team", environ_name="SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT_NAME", ), "emailAddress": values.Value( environ_name="SOCIAL_AUTH_SAML_FER_SUPPORT_CONTACT_EMAIL", ), } # SOCIAL_AUTH_SAML_FER_ENABLED_IDPS is not required since the # SAML FER backend is overridden to allow dynamic IdPs. # see social_edu_federation.backends.saml_fer.FERSAMLAuth.get_idp(idp_name) # Custom parameter to define the Renater Federation Metadata SOCIAL_AUTH_SAML_FER_FEDERATION_SAML_METADATA_URL = values.Value( "https://metadata.federation.renater.fr/renater/main/main-idps-renater-metadata.xml" ) SOCIAL_AUTH_SAML_FER_PIPELINE = MARSHA_DEFAULT_AUTH_PIPELINE # pylint: disable=invalid-name @property def AWS_SOURCE_BUCKET_NAME(self): """Source bucket name. If this setting is set in an environment variable we use it. Otherwise the value is computed with the AWS_BASE_NAME value. """ return os.environ.get("DJANGO_AWS_SOURCE_BUCKET_NAME", f"{self.AWS_BASE_NAME}-marsha-source") # pylint: disable=invalid-name @property def AWS_DESTINATION_BUCKET_NAME(self): """Destination bucket name. If this setting is set in an environment variable we use it. Otherwise the value is computed with the AWS_BASE_NAME value. """ return os.environ.get( "DJANGO_AWS_DESTINATION_BUCKET_NAME", f"{self.AWS_BASE_NAME}-marsha-destination", ) # pylint: disable=invalid-name @property def SIMPLE_JWT(self): """Define settings for `djangorestframework_simplejwt`. The JWT_SIGNING_KEY must be evaluated late as the jwt library check for string type. """ return { "ACCESS_TOKEN_LIFETIME": timedelta(days=1), "ALGORITHM": "HS256", "SIGNING_KEY": str(self.JWT_SIGNING_KEY), "USER_ID_CLAIM": "resource_id", "AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken", ), } # pylint: disable=invalid-name @property def RELEASE(self): """ Return the release information. Delegate to the module function to enable easier testing. """ return get_release() @classmethod def _get_environment(cls): """Environment in which the application is launched.""" return cls.__name__.lower() # pylint: disable=invalid-name @property def ENVIRONMENT(self): """Environment in which the application is launched.""" return self._get_environment() @classmethod def post_setup(cls): """Post setup configuration. This is the place where you can configure settings that require other settings to be loaded. """ super().post_setup() # The DJANGO_SENTRY_DSN environment variable should be set to activate # sentry for an environment if cls.SENTRY_DSN is not None: sentry_sdk.init( dsn=cls.SENTRY_DSN, environment=cls._get_environment(), release=get_release(), integrations=[DjangoIntegration()], ) with sentry_sdk.configure_scope() as scope: scope.set_extra("application", "backend")
class Base(Configuration): # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ SECRET_KEY = values.SecretValue() LOG_LEVEL = values.Value(environ_prefix="", default="ERROR") DEBUG = values.BooleanValue(environ_prefix="", default=False) ALLOWED_HOSTS = values.ListValue( environ_prefix="", default=["127.0.0.1", "localhost", "0.0.0.0"]) # Application definition SYSTEM_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # third party apps "rest_framework", "django_extensions", ] PROJECT_APPS = ["movies", "external_services"] INSTALLED_APPS = SYSTEM_APPS + PROJECT_APPS 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", ] ROOT_URLCONF = "totoro_app.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", ], }, }, ] WSGI_APPLICATION = "totoro_app.wsgi.application" # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } # 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", }, ] # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_URL = "/static/" STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "verbose": { "format": "%(levelname)s %(asctime)s %(module)s " "%(process)d %(thread)d %(message)s" } }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", } }, "root": { "level": "INFO", "handlers": ["console"] }, } # django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/ # lazy eval for env based config # https://django-configurations.readthedocs.io/en/stable/patterns/#property-settings @property def REST_FRAMEWORK(self): return { "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.TokenAuthentication", ), "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated", ), "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", } CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": os.getenv("REDIS_URL"), "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient" }, "KEY_PREFIX": "totoro_app", } } CACHE_TTL = values.PositiveIntegerValue(environ_prefix="", default=(0)) # sentry setup ENABLE_SENTRY = values.BooleanValue(environ_prefix="", default=False) SENTRY_PROJECT_ID = values.Value(environ_prefix="", default=None) SENTRY_KEY = values.Value(environ_prefix="", default=None) # https://django-configurations.readthedocs.io/en/stable/patterns/#setup-methods @classmethod def post_setup(cls): super().post_setup() if cls.ENABLE_SENTRY: import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration sentry_sdk.init( dsn= f"https://{cls.SENTRY_KEY}.ingest.sentry.io/{cls.SENTRY_PROJECT_ID}", integrations=[DjangoIntegration()], # If you wish to associate users to errors (assuming you are using # django.contrib.auth) you may enable sending PII data. send_default_pii=True, )
class Settings(Configuration): ALLOWED_HOSTS = values.ListValue(['*']) 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', }, ] AUTH_USER_MODEL = 'telegrambot.User' BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) CACHES = values.CacheURLValue('locmem://') DATABASES = values.DatabaseURLValue('sqlite:///db.sqlite3') # DOTENV = os.path.join(BASE_DIR, '.env') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = values.BooleanValue(True) INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # third party 'rest_framework', # local apps 'telegrambot', ] LANGUAGE_CODE = 'en-us' LOGGING = { "version": 1, "disable_existing_loggers": False, "loggers": { "command_notify": { "level": "DEBUG", "handlers": ['console'], "propagate": False }, "scrappers": { "level": "DEBUG", "handlers": ['console'], "propagate": False } }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "simple" } }, "formatters": { "simple": { "format": "%(asctime)s [%(levelname)s] {%(module)s} %(message)s" } } } MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', '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', ] PREVENT_NOTIFICATION_REPEAT_TIMEOUT = values.PositiveIntegerValue( 5 * 60) # 5 mins ROOT_URLCONF = 'onair.urls' REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, 'URL_FIELD_NAME': 'href', } # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '1)#=9)t+e=374nx2i*p-o$a_b7%zvreb1ghmxo21+iyh&_*+a9' STATIC_ROOT = values.Value(os.path.join(BASE_DIR, '..', 'staticfiles')) STATIC_URL = '/assets/' # http://whitenoise.evans.io/en/stable/ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' TELEGRAM_TOKEN = values.SecretValue() 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', ], }, }, ] TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True WSGI_APPLICATION = 'onair.wsgi.application'