class Development(databases.Databases, common.Common): """Settings for development.""" CACHES = values.DictValue({ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } }) MIDDLEWARE = common.Common.MIDDLEWARE + [ 'debug_toolbar.middleware.DebugToolbarMiddleware' ] INSTALLED_APPS = (common.Common.INSTALLED_APPS + ('debug_toolbar', )) EMAIL_BACKEND = values.Value( 'django.core.mail.backends.console.EmailBackend') @property def INTERNAL_IPS(self): # noqa """Return a tuple of IP addresses, as strings. Detect a Vagrant box by looking at the hostname. Return the gateway IP address on a Vagrant box, because this is the IP address the request will originate from. """ if 'vagrant' in socket.gethostname(): addr = [ line.split()[1] for line in subprocess.check_output( ['netstat', '-rn']).splitlines() if line.startswith('0.0.0.0') ][0] # noqa else: addr = '127.0.0.1' return (addr, )
class Prod(Base): """Production settings (override default values with environment vars""" SECRET_KEY = values.SecretValue() DEBUG = False SASS_OUTPUT_STYLE = 'compressed' # Set like this: DJANGO_ADMINS=Foo,[email protected];Bar,[email protected] ADMINS = values.SingleNestedTupleValue() # Set like this: DJANGO_ALLOWED_HOSTS=foo.com,bar.net ALLOWED_HOSTS = values.ListValue([ '127.0.0.1', 'localhost', 'oldp.local', 'de.oldp.local' ]) # Set like this: DJANGO_LANGUAGES_DOMAINS="{'de.foo.com':'de','fr.foo.com':'fr'}" LANGUAGES_DOMAINS = values.DictValue({ 'localhost:8000': 'en', 'oldp.local:8000': 'en', 'de.oldp.local:8000': 'de', '127.0.0.1:8000': 'de', })
class HerokuPostmark(Heroku): SECRET_URLS = values.DictValue({ "admin": "admin", "postmark_inbound": "postmark_inbound", "postmark_bounce": "postmark_bounce" }) FOI_EMAIL_TEMPLATE = values.Value('request+{secret}@{domain}') FOI_EMAIL_DOMAIN = values.Value('inbound.postmarkapp.com') SERVER_EMAIL = values.Value(os_env('POSTMARK_INBOUND_ADDRESS')) DEFAULT_FROM_EMAIL = values.Value(os_env('POSTMARK_INBOUND_ADDRESS')) # Official Notification Mail goes through # the normal Django SMTP Backend EMAIL_HOST = os_env('POSTMARK_SMTP_SERVER') EMAIL_PORT = values.IntegerValue(2525) EMAIL_HOST_USER = os_env('POSTMARK_API_KEY') EMAIL_HOST_PASSWORD = os_env('POSTMARK_API_KEY') EMAIL_USE_TLS = values.BooleanValue(True) # SMTP settings for sending FoI mail FOI_EMAIL_FIXED_FROM_ADDRESS = values.BooleanValue(False) FOI_EMAIL_HOST_FROM = os_env('POSTMARK_INBOUND_ADDRESS') FOI_EMAIL_HOST_USER = os_env('POSTMARK_API_KEY') FOI_EMAIL_HOST_PASSWORD = os_env('POSTMARK_API_KEY') FOI_EMAIL_HOST = os_env('POSTMARK_SMTP_SERVER') FOI_EMAIL_PORT = values.IntegerValue(2525) FOI_EMAIL_USE_TLS = values.BooleanValue(True)
class Dev(Base): u"""Configuração para Desenvolvimento.""" DEBUG = True DATABASES = values.DatabaseURLValue( 'postgres://postgres@localhost/postgres') SECRET_KEY = values.Value( '(ec65w1as3rno#!g#e*4wji!m*6#$=6v5d-bs--%jax(7o_y6$') JWT_AUTH = values.DictValue({ 'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=1), 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(hours=1), 'JWT_ALLOW_REFRESH': True, 'JWT_AUTH_HEADER_PREFIX': 'Bearer', }) @classmethod def pre_setup(cls): u"""Executado antes da Configuração.""" super(Base, cls).pre_setup() @classmethod def setup(cls): u"""Executado depois da Configuração.""" super(Base, cls).setup() logging.info('Configurações de desenvolvimento carregadas: %s', cls) @classmethod def post_setup(cls): u"""Executado depois da Configuração.""" super(Base, cls).post_setup()
class Development(Base): """Development environment settings. We set ``DEBUG`` to ``True`` by default, configure the server to respond to all hosts, and use a local sqlite database by default. """ ALLOWED_HOSTS = ["*"] AWS_SOURCE_BUCKET_NAME = values.Value("development-marsha-source") DEBUG = values.BooleanValue(True) CLOUDFRONT_SIGNED_URLS_ACTIVE = values.BooleanValue(False) CACHES = { "default": { "BACKEND": "django.core.cache.backends.dummy.DummyCache" } } LOGGING = values.DictValue({ "version": 1, "disable_existing_loggers": False, "handlers": { "console": { "class": "logging.StreamHandler", "stream": "ext://sys.stdout", } }, "loggers": { "marsha": { "handlers": ["console"], "level": "DEBUG", "propagate": True } }, })
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 Base(Configuration): ACTIONNETWORK_API_KEYS = values.DictValue({"main": ""}, environ_prefix=None) # stl_dsa/config/settings.py - 2 = stl_dsa/ ROOT_DIR = environ.Path(__file__) - 2 APPS_DIR = ROOT_DIR.path("stl_dsa") BASE_DIR = ROOT_DIR if READ_DOT_ENV_FILE := env.bool("DJANGO_READ_DOT_ENV_FILE", default=False): env.read_env(str(ROOT_DIR.path(".env")))
class EmptyDatabases(object): """Empty databases settings, used to force to overwrite them.""" DATABASES = values.DictValue({ 'default': { 'ENGINE': '', 'NAME': '', 'USER': '', 'PASSWORD': '', 'HOST': '', }, })
class Dev(Base): """Base settings for development.""" DEBUG = True # Required overrides SITE_URL = values.URLValue('http://*****:*****@example.com', 'password': '******' }) # Storage AWS_S3_REGION_NAME = '' AWS_STORAGE_BUCKET_NAME = values.Value('django') AWS_S3_ENDPOINT_URL = values.Value('http://minio:9000') AWS_ACCESS_KEY_ID = values.Value('djangos3') AWS_SECRET_ACCESS_KEY = values.Value('djangos3') AWS_S3_SECURE_URLS = values.BooleanValue(True) @property def AWS_S3_CUSTOM_DOMAIN(self): return values.Value(self.URL.netloc) # Core @property def FRONTEND_URL(self): return values.URLValue(self.SITE_URL) @property def INSTALLED_APPS(self): return super().INSTALLED_APPS + [ 'debug_toolbar', ] # Security CORS_ORIGIN_ALLOW_ALL = True SESSION_COOKIE_SECURE = False CSRF_COOKIE_SECURE = False AWS_AUTO_CREATE_BUCKET = True # Email EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # Services CELERY_BROKER_URL = values.Value('redis://redis', environ_name='CELERY_BROKER_URL') DATABASES = values.DatabaseURLValue( 'postgis://*****:*****@db:5432/django')
class PostgreSQLDatabases(object): """Settings for local PostgreSQL databases.""" DATABASES = values.DictValue({ 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'hopper', 'USER': '******', 'PASSWORD': '******', 'HOST': 'localhost', 'CONN_MAX_AGE': None, }, })
class Production(Base): """Production environment settings You must define the DJANGO_ALLOWED_HOSTS environment variable in Production configuration (and derived configurations): DJANGO_ALLOWED_HOSTS="foo.com,foo.fr" """ # Security ALLOWED_HOSTS = values.ListValue(None) CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True SESSION_COOKIE_SECURE = True # System check reference: # https://docs.djangoproject.com/en/2.2/ref/checks/#security SILENCED_SYSTEM_CHECKS = values.ListValue([ # Allow the X_FRAME_OPTIONS to be set to "SAMEORIGIN" "security.W019" ]) # The X_FRAME_OPTIONS value should be set to "SAMEORIGIN" to display # DjangoCMS frontend admin frames. Dockerflow raises a system check security # warning with this setting, one should add "security.W019" to the # SILENCED_SYSTEM_CHECKS setting (see above). X_FRAME_OPTIONS = "SAMEORIGIN" # For static files in production, we want to use a backend that includes a hash in # the filename, that is calculated from the file content, so that browsers always # get the updated version of each file. STATICFILES_STORAGE = ( "django.contrib.staticfiles.storage.ManifestStaticFilesStorage") # For more details about CMS_CACHE_DURATION, see : # http://docs.django-cms.org/en/latest/reference/configuration.html#cms-cache-durations CMS_CACHE_DURATIONS = values.DictValue({ "menus": 3600, "content": 86400, "permissions": 86400 }) # By default, Django CMS sends cached responses with a # Cache-control: max-age value that reflects the server cache TTL # (CMS_CACHE_DURATIONS["content"]) # # The thing is : we can invalidate a server side cache entry, but we cannot # invalidate our client browser cache entries. That's why we want to set a # long TTL on the server side, but a much lower TTL on the browser cache. # # This setting allows to define a maximum value for the max-age header # returned by Django CMS views. MAX_BROWSER_CACHE_TTL = 600
class Development(databases.Databases, common.Common): """Settings for development.""" CACHES = values.DictValue({ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } }) DEVSERVER_ARGS = values.ListValue([]) @property def DEVSERVER_DEFAULT_ADDR(self): """Return the default address to bind devserver to.""" if 'vagrant' in socket.gethostname(): addr = '0.0.0.0' else: addr = '127.0.0.1' return addr DEVSERVER_MODULES = values.ListValue([ 'devserver.modules.sql.SQLRealTimeModule', 'devserver.modules.sql.SQLSummaryModule', 'devserver.modules.profile.ProfileSummaryModule', ]) DEVSERVER_TRUNCATE_SQL = values.BooleanValue(True) EMAIL_BACKEND = values.Value( 'django.core.mail.backends.console.EmailBackend') # devserver must be ahead of django.contrib.staticfiles INSTALLED_APPS = ('devserver', ) + common.Common.INSTALLED_APPS + ( 'debug_toolbar', ) @property def INTERNAL_IPS(self): """Return a tuple of IP addresses, as strings. Detect a Vagrant box by looking at the hostname. Return the gateway IP address on a Vagrant box, because this is the IP address the request will originate from. """ if 'vagrant' in socket.gethostname(): addr = [ line.split()[1] for line in subprocess.check_output( ['netstat', '-rn']).splitlines() if line.startswith('0.0.0.0') ][0] # noqa else: addr = '127.0.0.1' return (addr, )
class DevMixin: DEBUG = values.BooleanValue(True) CACHES = values.DictValue( environ_prefix='', default={ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } }) EMAIL_BACKEND = 'eml_email_backend.EmailBackend' @property def EMAIL_FILE_PATH(self): return join(super().BASE_DIR, 'tmp') THUMBNAIL_DEBUG = True
class Production(Base): """Production environment settings You must define the DJANGO_ALLOWED_HOSTS and DJANGO_SECRET_KEY environment variables in Production configuration (and derived configurations): DJANGO_ALLOWED_HOSTS="foo.com,foo.fr" DJANGO_SECRET_KEY="your-secret-key" """ # Security SECRET_KEY = values.SecretValue() ALLOWED_HOSTS = values.ListValue(None) CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True SESSION_COOKIE_SECURE = True # For static files in production, we want to use a backend that includes a hash in # the filename, that is calculated from the file content, so that browsers always # get the updated version of each file. STATICFILES_STORAGE = ( "django.contrib.staticfiles.storage.ManifestStaticFilesStorage") # For more details about CMS_CACHE_DURATION, see : # http://docs.django-cms.org/en/latest/reference/configuration.html#cms-cache-durations CMS_CACHE_DURATIONS = values.DictValue({ "menus": 3600, "content": 86400, "permissions": 86400 }) # By default, Django CMS sends cached responses with a # Cache-control: max-age value that reflects the server cache TTL # (CMS_CACHE_DURATIONS["content"]) # # The thing is : we can invalidate a server side cache entry, but we cannot # invalidate our client browser cache entries. That's why we want to set a # long TTL on the server side, but a much lower TTL on the browser cache. # # This setting allows to define a maximum value for the max-age header # returned by Django CMS views. MAX_BROWSER_CACHE_TTL = 600
class Production(Common): # This ensures that Django will be able to detect a secure connection # properly on Heroku. # SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # INSTALLED_APPS INSTALLED_APPS = Common.INSTALLED_APPS # END INSTALLED_APPS # SECRET KEY SECRET_KEY = values.SecretValue() # END SECRET KEY # sentry RAVEN_CONFIG = values.DictValue() INSTALLED_APPS += ('raven.contrib.django.raven_compat', ) # end sentry # django-secure # INSTALLED_APPS += ("djangosecure", ) # set this to 60 seconds and then to 518400 when you can prove it works # SECURE_HSTS_SECONDS = 60 # SECURE_HSTS_INCLUDE_SUBDOMAINS = values.BooleanValue(True) # SECURE_FRAME_DENY = values.BooleanValue(True) # SECURE_CONTENT_TYPE_NOSNIFF = values.BooleanValue(True) # SECURE_BROWSER_XSS_FILTER = values.BooleanValue(True) # SESSION_COOKIE_SECURE = values.BooleanValue(False) # SESSION_COOKIE_HTTPONLY = values.BooleanValue(True) # SECURE_SSL_REDIRECT = values.BooleanValue(True) # end django-secure # SITE CONFIGURATION # Hosts/domain names that are valid for this site # See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts ALLOWED_HOSTS = ["*"] # END SITE CONFIGURATION INSTALLED_APPS += ("gunicorn", )
class Test(Base): DEBUG = False TEMPLATE_DEBUG = True def _fake_convert_pdf(self, infile, outpath): _, filename = os.path.split(infile) name, ext = filename.rsplit('.', 1) output = os.path.join(outpath, '%s.pdf' % name) args = ['cp', infile, output] return args, output @property def FROIDE_CONFIG(self): config = dict(super(Test, self).FROIDE_CONFIG) config.update( dict(doc_conversion_call_func=self._fake_convert_pdf, default_law=10000, greetings=[ rec(u"Dear ((?:Mr\.?|Ms\.?) .*),?"), rec(u'Sehr geehrter? ((Herr|Frau) .*),?') ], closings=[ rec(u"Sincerely yours,?"), rec(u'Mit freundlichen Grüßen') ], public_body_officials_public=False)) return config @property def MEDIA_ROOT(self): return os.path.abspath( os.path.join(super(Test, self).PROJECT_ROOT, "tests", "testdata")) MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' CACHES = values.CacheURLValue('locmem://') TEST_SELENIUM_DRIVER = values.Value('phantomjs') USE_X_ACCEL_REDIRECT = True SECRET_URLS = values.DictValue({ "admin": "admin", "postmark_inbound": "postmark_inbound", "postmark_bounce": "postmark_bounce" }) EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' DEFAULT_FROM_EMAIL = '*****@*****.**' FOI_EMAIL_DOMAIN = 'fragdenstaat.de' @property def HAYSTACK_CONNECTIONS(self): return { 'default': { 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'PATH': os.path.join( super(Test, self).PROJECT_ROOT, 'tests/froide_test_whoosh_db'), }, } CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend' CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" CELERY_ALWAYS_EAGER = True CELERY_EAGER_PROPAGATES_EXCEPTIONS = True MIDDLEWARE_CLASSES = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ]
class Base(Configuration): DEBUG = values.BooleanValue(True) TEMPLATE_DEBUG = values.BooleanValue(DEBUG) DATABASES = values.DatabaseURLValue('sqlite:///dev.db') CONN_MAX_AGE = None INSTALLED_APPS = values.ListValue([ 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', 'django_comments', 'django.contrib.flatpages', 'django.contrib.sitemaps', # external 'haystack', 'djcelery', 'taggit', 'floppyforms', 'overextends', 'tastypie', 'tastypie_swagger', 'storages', 'compressor', # local 'froide.foirequest', 'froide.foirequestfollower', 'froide.frontpage', 'froide.publicbody', 'froide.account', 'froide.redaction', 'froide.foisite', 'froide.helper', ]) CACHES = values.CacheURLValue('dummy://') # ############# Site Configuration ######### # Make this unique, and don't share it with anybody. SECRET_KEY = 'make_me_unique!!' SITE_NAME = values.Value('Froide') SITE_EMAIL = values.Value('*****@*****.**') SITE_URL = values.Value('http://*****:*****@example.com'), ) MANAGERS = ADMINS INTERNAL_IPS = values.TupleValue(('127.0.0.1', )) # ############## PATHS ############### PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) LOCALE_PATHS = values.TupleValue( (os.path.abspath(os.path.join(PROJECT_ROOT, '..', "locale")), )) GEOIP_PATH = None # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/media/" MEDIA_ROOT = os.path.abspath(os.path.join(PROJECT_ROOT, "..", "files")) # Sub path in MEDIA_ROOT that will hold FOI attachments FOI_MEDIA_PATH = values.Value('foi') # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/home/media/media.lawrence.com/static/" STATIC_ROOT = os.path.abspath(os.path.join(PROJECT_ROOT, "..", "public")) # Additional locations of static files STATICFILES_DIRS = (os.path.join(PROJECT_ROOT, "static"), ) COMPRESS_ENABLED = values.BooleanValue(False) COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter'] COMPRESS_CSS_FILTERS = [ 'compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.cssmin.CSSMinFilter' ] COMPRESS_PARSER = 'compressor.parser.HtmlParser' # Additional locations of template files TEMPLATE_DIRS = (os.path.join(PROJECT_ROOT, "templates"), ) # ########## URLs ################# ROOT_URLCONF = values.Value('froide.urls') # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" MEDIA_URL = values.Value('/files/') # URL prefix for static files. # Example: "http://media.lawrence.com/static/" # URL that handles the static files like app media. # Example: "http://media.lawrence.com" STATIC_URL = values.Value('/static/') USE_X_ACCEL_REDIRECT = values.BooleanValue(False) X_ACCEL_REDIRECT_PREFIX = values.Value('/protected') # ## URLs that can be translated to a secret value SECRET_URLS = values.DictValue({"admin": "admin"}) # ######## Backends, Finders, Processors, Classes #### AUTH_USER_MODEL = values.Value('account.User') CUSTOM_AUTH_USER_MODEL_DB = values.Value('') # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.FileSystemFinder', 'compressor.finders.CompressorFinder', ) AUTHENTICATION_BACKENDS = [ "froide.helper.auth.EmailBackend", "django.contrib.auth.backends.ModelBackend", ] TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.core.context_processors.static', 'django.core.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'froide.helper.context_processors.froide', 'froide.helper.context_processors.site_settings') # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = [ 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ] MIDDLEWARE_CLASSES = [ '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', ] # ######### I18N and L10N ################## # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # On Unix systems, a value of None will cause Django to use the same # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = values.Value('Europe/Berlin') USE_TZ = values.BooleanValue(True) # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = values.Value('en-us') LANGUAGES = ( ('en', gettext('English')), ('es', gettext('Spanish')), ('fi-fi', gettext('Finnish (Finland)')), ('de', gettext('German')), ('da-dk', gettext('Danish (Denmark)')), ('it', gettext('Italian')), ('pt', gettext('Portuguese')), ('sv-se', gettext('Swedish (Sweden)')), ('sv-fi', gettext('Swedish (Finland)')), ('zh-cn', gettext('Chinese (Simplified)')), ('zh-hk', gettext('Chinese (Traditional, Hong Kong)')), ) # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = values.BooleanValue(True) # If you set this to False, Django will not format dates, numbers and # calendars according to the current locale USE_L10N = values.BooleanValue(True) DATE_FORMAT = values.Value("d. F Y") SHORT_DATE_FORMAT = values.Value("d.m.Y") DATE_INPUT_FORMATS = values.TupleValue(("%d.%m.%Y", )) SHORT_DATETIME_FORMAT = values.Value("d.m.Y H:i") DATETIME_INPUT_FORMATS = values.TupleValue(("%d.%m.%Y %H:%M", )) TIME_FORMAT = values.Value("H:i") TIME_INPUT_FORMATS = values.TupleValue(("%H:%M", )) HOLIDAYS = [ (1, 1), # New Year's Day (12, 25), # Christmas (12, 26) # Second day of Christmas ] # Weekends are non-working days HOLIDAYS_WEEKENDS = True # Calculates other holidays based on easter sunday HOLIDAYS_FOR_EASTER = (0, -2, 1, 39, 50, 60) # ######## Logging ########## # A sample logging configuration. LOGGING = { 'version': 1, 'disable_existing_loggers': True, 'root': { 'level': 'WARNING', 'handlers': [], }, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' }, }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' }, 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', } }, 'loggers': { 'froide': { 'handlers': ['console'], 'propagate': True, 'level': 'DEBUG', }, 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, 'django.db.backends': { 'level': 'ERROR', 'handlers': ['console'], 'propagate': False, } } } # ######## Security ########### CSRF_COOKIE_SECURE = False CSRF_COOKIE_HTTPONLY = True CSRF_FAILURE_VIEW = values.Value('froide.account.views.csrf_failure') # Change this # ALLOWED_HOSTS = () SESSION_COOKIE_AGE = values.IntegerValue(3628800) # six weeks SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SECURE = False # ######## Celery ############# CELERY_RESULT_BACKEND = values.Value( 'djcelery.backends.database:DatabaseBackend') CELERYBEAT_SCHEDULER = values.Value( "djcelery.schedulers.DatabaseScheduler") CELERY_ALWAYS_EAGER = values.BooleanValue(True) CELERY_ROUTES = { 'froide.foirequest.tasks.fetch_mail': { "queue": "emailfetch" }, } CELERY_TIMEZONE = TIME_ZONE # ######## Haystack ########### HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.simple_backend.SimpleEngine', } } # ######### Tastypie ######### TASTYPIE_SWAGGER_API_MODULE = values.Value('froide.urls.v1_api') # ######### Froide settings ######## FROIDE_THEME = None FROIDE_CONFIG = dict( create_new_publicbody=True, publicbody_empty=True, user_can_hide_web=True, public_body_officials_public=True, public_body_officials_email_public=False, request_public_after_due_days=14, payment_possible=True, currency="Euro", default_law=1, search_engine_query= "http://www.google.de/search?as_q=%(query)s&as_epq=&as_oq=&as_eq=&hl=en&lr=&cr=&as_ft=i&as_filetype=&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=%(domain)s&as_rights=&safe=images", greetings=[rec(u"Dear (?:Mr\.?|Ms\.? .*?)")], closings=[rec(u"Sincerely yours,?")], public_body_boosts={}, dryrun=False, request_throttle= None, # Set to [(15, 7 * 24 * 60 * 60),] for 15 requests in 7 days dryrun_domain="testmail.example.com", allow_pseudonym=False, doc_conversion_binary=None, # replace with libreoffice instance doc_conversion_call_func=None, # see settings_test for use ) # ###### Email ############## # Django settings EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_SUBJECT_PREFIX = values.Value('[Froide] ') SERVER_EMAIL = values.Value('*****@*****.**') DEFAULT_FROM_EMAIL = values.Value('*****@*****.**') # Official Notification Mail goes through # the normal Django SMTP Backend EMAIL_HOST = values.Value("") EMAIL_PORT = values.IntegerValue(587) EMAIL_HOST_USER = values.Value("") EMAIL_HOST_PASSWORD = values.Value("") EMAIL_USE_TLS = values.BooleanValue(True) # Froide special case settings # IMAP settings for fetching mail FOI_EMAIL_PORT_IMAP = values.IntegerValue(993) FOI_EMAIL_HOST_IMAP = values.Value("imap.example.com") FOI_EMAIL_ACCOUNT_NAME = values.Value("*****@*****.**") FOI_EMAIL_ACCOUNT_PASSWORD = values.Value("") FOI_EMAIL_USE_SSL = values.BooleanValue(True) # SMTP settings for sending FoI mail FOI_EMAIL_HOST_USER = values.Value(FOI_EMAIL_ACCOUNT_NAME) FOI_EMAIL_HOST_FROM = values.Value(FOI_EMAIL_HOST_USER) FOI_EMAIL_HOST_PASSWORD = values.Value(FOI_EMAIL_ACCOUNT_PASSWORD) FOI_EMAIL_HOST = values.Value("smtp.example.com") FOI_EMAIL_PORT = values.IntegerValue(537) FOI_EMAIL_USE_TLS = values.BooleanValue(True) # The FoI Mail can use a different account FOI_EMAIL_DOMAIN = values.Value("example.com") FOI_EMAIL_TEMPLATE = None # Example: # FOI_EMAIL_TEMPLATE = lambda user_name, secret: "{username}.{secret}@{domain}" % (user_name, secret, FOI_EMAIL_DOMAIN) # Is the message you can send from fixed # or can you send from any address you like? FOI_EMAIL_FIXED_FROM_ADDRESS = values.BooleanValue(True)
class BaseSettings(Configuration): """ Django settings for qcat project. For more information on this file, see https://docs.djangoproject.com/en/dev/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/dev/ref/settings/ """ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = join(dirname(dirname(dirname(dirname(__file__))))) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = values.BooleanValue(default=False) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ # Application definition INSTALLED_APPS = ( 'django.contrib.contenttypes', 'grappelli.dashboard', 'grappelli', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.sitemaps', 'django.contrib.staticfiles', 'django.contrib.humanize', 'compressor', 'cookielaw', 'corsheaders', 'django_extensions', 'django_filters', 'easy_thumbnails', 'easy_thumbnails.optimize', 'floppyforms', 'imagekit', 'maintenancemode', 'rest_framework', 'rest_framework_swagger', 'sekizai', 'wkhtmltopdf', # Custom apps 'accounts', 'api', 'approaches', 'configuration', 'qcat', 'questionnaire', 'notifications', 'sample', 'samplemulti', 'samplemodule', 'search', 'summary', 'technologies', 'unccd', 'watershed', 'wocat', 'cca', 'cbp', ) MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.middleware.locale.LocaleMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'maintenancemode.middleware.MaintenanceModeMiddleware', 'qcat.middleware.ProfilerMiddleware', ) ROOT_URLCONF = 'qcat.urls' WSGI_APPLICATION = 'qcat.wsgi.application' # Internationalization # https://docs.djangoproject.com/en/dev/topics/i18n/ LANGUAGE_CODE = 'en' LOCALE_PATHS = ( join(BASE_DIR, 'locale'), ) # The first language is the default language. # Important: If you add languages here, make sure to add a placeholder of # the infographic in src/assets/img/infographics (make a copy of the English # version) LANGUAGES = ( ('en', _('English')), ('fr', _('French')), ('es', _('Spanish')), ('ru', _('Russian')), ('zh', _('Chinese')), ('km', _('Khmer')), ('lo', _('Lao')), ('ar', _('Arabic')), ('pt', _('Portuguese')), ('af', _('Afrikaans')), ('th', _('Thai')), ('mn', _('Mongolian')), ) # languages with extraordinarily long words that need 'forced' line breaks # to remain consistent in the box-layout. WORD_WRAP_LANGUAGES = [ 'km', 'lo', 'th', ] TIME_ZONE = 'Europe/Zurich' USE_I18N = True USE_L10N = True USE_TZ = True BASE_URL = values.Value(environ_prefix='', default='https://qcat.wocat.net') # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/dev/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = join(BASE_DIR, '..', 'static') STATICFILES_DIRS = ( join(BASE_DIR, 'static'), ) STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'compressor.finders.CompressorFinder', ) MEDIA_URL = '/upload/' MEDIA_ROOT = join(BASE_DIR, '..', 'upload') UPLOAD_VALID_FILES = { 'image': ( ('image/jpeg', 'jpg'), ('image/png', 'png'), ('image/gif', 'gif'), ), 'document': ( ('application/pdf', 'pdf'), ) } UPLOAD_MAX_FILE_SIZE = 3145728 # 3 MB UPLOAD_IMAGE_THUMBNAIL_FORMATS = ( ('default', (640, 480)), ('small', (1024, 768)), ('medium', (1440, 1080)), # 'large' is the original uploaded image. ) THUMBNAIL_ALIASES = { 'summary': { 'screen': { 'header_image': { 'size': (1542, 767), 'upscale': True, 'crop': True, 'target': (50, 50), }, 'half_height': { 'size': (640, 640), 'upscale': True, 'crop': True, }, 'map': { 'size': (560, 0) }, 'flow_chart': { 'size': (900, 0), 'upscale': False }, 'flow_chart_half_height': { 'size': (640, 640), 'upscale': True, } }, 'print': { 'header_image': { 'size': (6168, 3068), 'crop': True, 'upscale': True, }, 'half_height': { 'size': (2560, 2560), 'upscale': True, 'crop': True, }, 'map': { 'size': (2240, 0) }, 'flow_chart': { 'size': (3600, 0), 'upscale': False }, 'flow_chart_half_height': { 'size': (2560, 2560), 'upscale': True, } } } } SUMMARY_PDF_PATH = join(MEDIA_ROOT, 'summary-pdf') TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ join(BASE_DIR, 'templates'), ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.contrib.auth.context_processors.auth', 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.media', 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.request', 'sekizai.context_processors.sekizai', 'qcat.context_processors.template_settings' ], } } ] AUTH_USER_MODEL = 'accounts.User' AUTHENTICATION_BACKENDS = ( 'accounts.authentication.WocatCMSAuthenticationBackend', ) REACTIVATE_WOCAT_ACCOUNT_URL = values.URLValue( environ_prefix='', default='https://wocat.net/accounts/reactivate/' ) LOGIN_URL = 'login' GRAPPELLI_ADMIN_TITLE = 'QCAT Administration' GRAPPELLI_INDEX_DASHBOARD = 'qcat.dashboard.CustomIndexDashboard' # Elasticsearch settings ES_HOST = values.Value(default='localhost', environ_prefix='') ES_PORT = values.IntegerValue(default=9200, environ_prefix='') ES_INDEX_PREFIX = values.Value(default='qcat_', environ_prefix='') # For Elasticsearch >= 2.3: https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes-2.3.html # noqa ES_NESTED_FIELDS_LIMIT = values.IntegerValue(default=250, environ_prefix='') # For each language (as set in the setting ``LANGUAGES``), a language # analyzer can be specified. This helps to analyze the text in the # corresponding language for better search results. # https://www.elastic.co/guide/en/elasticsearch/reference/1.6/analysis-lang-analyzer.html # noqa ES_ANALYZERS = ( ('en', 'english'), ('es', 'spanish'), ) # https://www.elastic.co/guide/en/elasticsearch/reference/2.0/query-dsl-query-string-query.html#_reserved_characters ES_QUERY_RESERVED_CHARS = ['\\', '+', '-', '=', '&&', '||', '>', '<', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', '/'] MESSAGE_TAGS = { messages.INFO: 'secondary', } # Allow various formats to communicate with the API. REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', 'rest_framework_xml.parsers.XMLParser', ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'rest_framework_xml.renderers.XMLRenderer', 'rest_framework_csv.renderers.CSVRenderer', ), 'DEFAULT_THROTTLE_RATES': { 'anon': '10/day', 'user': '******', }, 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning', 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 25, } SWAGGER_SETTINGS = { 'DOC_EXPANSION': 'list', 'JSON_EDITOR': True, 'VALIDATOR_URL': None, } API_PAGE_SIZE = values.IntegerValue(default=25, environ_prefix='') CORS_ORIGIN_WHITELIST = values.ListValue(environ_prefix='', default=[]) DATABASES = values.DatabaseURLValue(environ_required=True) ALLOWED_HOSTS = values.ListValue(default=['localhost', '127.0.0.1']) SECRET_KEY = values.SecretValue(environ_required=True) # The base URL of the REST API used for authentication AUTH_API_URL = values.Value(environ_prefix='', default='https://www.wocat.net/api/v1/') # The username used for API login AUTH_API_USER = values.Value(environ_prefix='') # The key used for API login AUTH_API_KEY = values.Value(environ_prefix='') AUTH_API_TOKEN = values.Value(environ_prefix='') # The URL of the WOCAT authentication form. Used to handle both login # and logout AUTH_LOGIN_FORM = values.Value( environ_prefix='', default='https://dev.wocat.net/en/sitefunctions/login.html' ) AUTH_COOKIE_NAME = values.Value(default='fe_typo_user', environ_prefix='') # Specify specific editions to be used in the filter. If not specified, # always the latest edition of a configuration is used. Specify specific # editions as a dict in the env. # Example to use edition 2015 to filter Technologies: # {"technologies": "2015"} FILTER_EDITIONS = values.DictValue(default={}, environ_prefix='') # https://raw.githubusercontent.com/SeleniumHQ/selenium/master/py/CHANGES # for the latest supported firefox version. TESTING_FIREFOX_PATH = values.Value(environ_prefix='') TESTING_CHROMEDRIVER_PATH = values.Value( environ_prefix='', default='/usr/local/bin/chromedriver') TESTING_POP_BROWSER = values.BooleanValue(environ_prefix='', default=False) # Flag for caching of the whole configuration object. Sections are always cached. USE_CACHING = values.BooleanValue(default=True) # django-cache-url doesn't support the redis package of our choice, set the redis location as # common environment (dict)value. CACHES = values.DictValue(environ_prefix='') KEY_PREFIX = values.Value(environ_prefix='', default='') # If set to true, the template 503.html is displayed. MAINTENANCE_MODE = values.BooleanValue(environ_prefix='', default=False) MAINTENANCE_LOCKFILE_PATH = join(BASE_DIR, 'maintenance.lock') # "Feature toggles" IS_ACTIVE_FEATURE_MODULE = values.BooleanValue( environ_prefix='', default=True ) IS_ACTIVE_FEATURE_WATERSHED = values.BooleanValue( environ_prefix='', default=False ) IS_ACTIVE_FEATURE_MEMORY_PROFILER = values.BooleanValue( environ_prefix='', default=False ) HOST_STRING_DEV = values.Value(environ_prefix='') HOST_STRING_DEMO = values.Value(environ_prefix='') HOST_STRING_LIVE = values.Value(environ_prefix='') # touch file to reload uwsgi TOUCH_FILE_DEV = values.Value(environ_prefix='') TOUCH_FILE_DEMO = values.Value(environ_prefix='') TOUCH_FILE_LIVE = values.Value(environ_prefix='') SENTRY_DSN = values.Value(environ_prefix='') SLACK_TOKEN = values.Value(environ_prefix='') WARN_HEADER = values.Value(environ_prefix='') NEXT_MAINTENANCE = join(BASE_DIR, 'envs/NEXT_MAINTENANCE') DEPLOY_TIMEOUT = values.Value(environ_prefix='', default=900) # Settings for piwik integration. Tracking happens in the frontend # (base template) and backend (API) PIWIK_SITE_ID = values.IntegerValue(environ_prefix='', default=None) PIWIK_URL = values.Value(environ_prefix='', default='https://webstats.wocat.net/') PIWIK_AUTH_TOKEN = values.Value(environ_prefix='') PIWIK_API_VERSION = values.IntegerValue(environ_prefix='', default=1) # google webdeveloper verification GOOGLE_WEBMASTER_TOOLS_KEY = values.Value(environ_prefix='') # Google Maps Javascript API key GOOGLE_MAPS_JAVASCRIPT_API_KEY = values.Value(environ_prefix='') # Mail settings (notification mails) DEFAULT_FROM_EMAIL = '*****@*****.**' DO_SEND_EMAILS = values.BooleanValue(environ_prefix='', default=True) DO_SEND_STAFF_ONLY = values.BooleanValue(environ_prefix='', default=False) WOCAT_IMPORT_DATABASE_URL = values.Value(environ_prefix='') WOCAT_IMPORT_DATABASE_URL_LOCAL = values.Value(environ_prefix='') WOCAT_CONTACT_PAGE = values.Value( environ_prefix='', default='https://www.wocat.net/about/wocat-secretariat' ) WOCAT_MAILBOX_USER_ID = values.IntegerValue(environ_prefix='') # TODO: Temporary test of UNCCD flagging. TEMP_UNCCD_TEST = values.ListValue(environ_prefix='') CDE_SUBNET_ADDR = values.Value(environ_prefix='', default='0.0.0.') # Prevent error when submitting very large forms. Default is 1000. # (https://docs.djangoproject.com/en/2.0/ref/settings/#data-upload-max-number-fields) DATA_UPLOAD_MAX_NUMBER_FIELDS = 2000
class Common(Configuration): """Common configuration base class.""" SECRET_KEY = '(_j4e0=pbe(b+b1$^ch_48be0=gszglcgfzz^dy=(gnx=@m*b7' DEBUG = values.BooleanValue(False) MAIL_ADMINS = values.BooleanValue(False) ADMINS = AdminsValue((('Max Brauer', '*****@*****.**'), )) MANAGERS = ADMINS LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse', }, 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { 'level': 'INFO', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', }, 'null': { 'class': 'logging.NullHandler', }, 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django': { 'handlers': ['console'], }, 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': False, }, 'django.security': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': False, }, 'django.security.DisallowedHost': { 'handlers': ['null'], 'propagate': False, }, 'py.warnings': { 'handlers': ['console'], }, } } ALLOWED_HOSTS = values.ListValue(['www.example.com']) SITE_ID = values.IntegerValue(1) # Internationalization # https://docs.djangoproject.com/en/dev/topics/i18n/ LANGUAGE_CODE = values.Value('en-us') TIME_ZONE = values.Value('Europe/Berlin') USE_I18N = True USE_L10N = True USE_TZ = True # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/var/www/example.com/media/" MEDIA_ROOT = values.PathValue(os.path.join(BaseDir.BASE_DIR, 'media')) # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. # Examples: "http://example.com/media/", "http://media.example.com/" MEDIA_URL = values.Value('/media/') # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/var/www/example.com/static/" STATIC_ROOT = values.PathValue( os.path.join(BaseDir.BASE_DIR, 'static_root')) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/dev/howto/static-files/ STATIC_URL = values.Value('/static/') # Additional locations of static files STATICFILES_DIRS = ( # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. os.path.join(BaseDir.BASE_DIR, 'static'), os.path.join(os.path.dirname(BaseDir.BASE_DIR), 'node_modules', 'normalize.css'), ) STATICFILES_FINDERS = values.ListValue([ 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.FileSystemFinder', 'sass_processor.finders.CssFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ]) MIDDLEWARE_CLASSES = values.ListValue([ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', ]) ROOT_URLCONF = 'bureau.config.urls' WSGI_APPLICATION = 'bureau.config.wsgi.application' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BaseDir.BASE_DIR, 'templates'), ], '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', 'bureau.context_processors.django_version', ], 'debug': values.BooleanValue( False, environ_name='DJANGO_TEMPLATES_TEMPLATE_DEBUG'), 'string_if_invalid': values.Value( '', environ_name='DJANGO_TEMPLATES_STRING_IF_INVALID'), }, }, ] # the following line is only necessary because django-template-debug uses it TEMPLATE_DEBUG = TEMPLATES[0]['OPTIONS'].get('debug', False) FIXTURE_DIRS = (os.path.join(BaseDir.BASE_DIR, 'fixtures'), ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', 'django.contrib.admindocs', 'sass_processor', 'bureau.apps.core.apps.CoreConfig', ) CACHES = values.DictValue({ 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', } }) EMAIL_SUBJECT_PREFIX = '[bureau]' DEFAULT_FROM_EMAIL = values.EmailValue('*****@*****.**') SERVER_EMAIL = DEFAULT_FROM_EMAIL
class Base(Core): """Settings that may change per-environment, some with defaults.""" @classmethod def setup(cls): super(Base, cls).setup() # For the sake of convenience we want to make UPLOAD_TRY_SYMBOLS_URL # optional as an environment variable. If it's not set, set it # by taking the UPLOAD_DEFAULT_URL and adding the prefix "/try" # right after the bucket name. if not cls.UPLOAD_TRY_SYMBOLS_URL: default_url = urlparse(cls.UPLOAD_DEFAULT_URL) path = default_url.path.split("/") # Since it always start with '/', the point after the bucket # name is the 3rd one. path.insert(2, "try") # Note `._replace` is actually not a private method. try_url = default_url._replace(path="/".join(path)) cls.UPLOAD_TRY_SYMBOLS_URL = try_url.geturl() SECRET_KEY = values.SecretValue() DEBUG = values.BooleanValue(default=False) DEBUG_PROPAGATE_EXCEPTIONS = values.BooleanValue(default=False) ALLOWED_HOSTS = values.ListValue([]) DATABASES = values.DatabaseURLValue("postgres://postgres@db/postgres") CONN_MAX_AGE = values.IntegerValue(60) REDIS_URL = values.Value("redis://redis-cache:6379/0") REDIS_STORE_URL = values.Value("redis://redis-store:6379/0") REDIS_SOCKET_CONNECT_TIMEOUT = values.IntegerValue(1) REDIS_SOCKET_TIMEOUT = values.IntegerValue(2) REDIS_STORE_SOCKET_CONNECT_TIMEOUT = values.IntegerValue(1) REDIS_STORE_SOCKET_TIMEOUT = values.IntegerValue(2) # Use redis as the Celery broker. @property def CELERY_BROKER_URL(self): return self.REDIS_URL # This name is hardcoded inside django-redis. It it's set to true in `settings` # it means that django-redis will attempt WARNING log any exceptions that # happen with the connection when it swallows the error(s). DJANGO_REDIS_LOG_IGNORED_EXCEPTIONS = values.BooleanValue(True) REDIS_IGNORE_EXCEPTIONS = values.BooleanValue(True) @property def CACHES(self): return { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": self.REDIS_URL, "OPTIONS": { "COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor", # noqa # "SERIALIZER": "django_redis.serializers.msgpack.MSGPackSerializer", # noqa "SERIALIZER": "tecken.cache_extra.MSGPackSerializer", # noqa "SOCKET_CONNECT_TIMEOUT": self.REDIS_SOCKET_CONNECT_TIMEOUT, "SOCKET_TIMEOUT": self.REDIS_SOCKET_TIMEOUT, "IGNORE_EXCEPTIONS": self.REDIS_IGNORE_EXCEPTIONS, }, }, "store": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": self.REDIS_STORE_URL, "OPTIONS": { "COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor", # noqa "SERIALIZER": "tecken.cache_extra.MSGPackSerializer", # noqa "SOCKET_CONNECT_TIMEOUT": self.REDIS_STORE_SOCKET_CONNECT_TIMEOUT, "SOCKET_TIMEOUT": self.REDIS_STORE_SOCKET_TIMEOUT, }, }, } LOGGING_USE_JSON = values.BooleanValue(False) LOGGING_DEFAULT_LEVEL = values.Value("INFO") def LOGGING(self): config = { "version": 1, "disable_existing_loggers": False, "formatters": { "json": { "()": "dockerflow.logging.JsonLogFormatter", "logger_name": "tecken", }, "verbose": { "format": "%(levelname)s %(asctime)s %(name)s %(message)s" }, }, "handlers": { "console": { "level": self.LOGGING_DEFAULT_LEVEL, "class": "logging.StreamHandler", "formatter": ("json" if self.LOGGING_USE_JSON else "verbose"), }, "sentry": { "level": "ERROR", "class": ("raven.contrib.django.raven_compat.handlers.SentryHandler" ), }, "null": { "class": "logging.NullHandler" }, }, "root": { "level": "INFO", "handlers": ["sentry", "console"] }, "loggers": { "django": { "level": "INFO", "handlers": ["console"], "propagate": False, }, "django.db.backends": { "level": "ERROR", "handlers": ["console"], "propagate": False, }, "django.request": { "level": "INFO", "handlers": ["console"], "propagate": False, }, "raven": { "level": "DEBUG", "handlers": ["console"], "propagate": False, }, "sentry.errors": { "level": "DEBUG", "handlers": ["console"], "propagate": False, }, "tecken": { "level": "DEBUG", "handlers": ["console"], "propagate": False, }, "mozilla_django_oidc": { "level": "DEBUG", "handlers": ["console"], "propagate": False, }, "celery.task": { "level": "DEBUG", "handlers": ["console"], "propagate": False, }, "markus": { "level": "INFO", "handlers": ["console"], "propagate": False, }, "request.summary": { "handlers": ["console"], "level": "INFO", "propagate": False, }, "django.security.DisallowedHost": { "handlers": ["null"], "propagate": False, }, "django_redis.cache": { "level": "INFO", "handlers": ["console"], "propagate": False, }, }, } if not self.LOGGING_USE_JSON: # If you're not using JSON logging, there's no point using the # 'request.summary' logger that python-dockerflow uses. config["loggers"]["request.summary"]["handlers"] = [] return config CSRF_FAILURE_VIEW = "tecken.views.csrf_failure" CSRF_USE_SESSIONS = values.BooleanValue(True) # The order here matters. Symbol download goes through these one at a time. # Ideally you want the one most commonly hit first unless there's a # cascading reason you want other buckets first. # By default, each URL is assumed to be private! # If there's a bucket you want to include that should be accessed # by HTTP only, add '?access=public' to the URL. # Note that it's empty by default which is actually not OK. # For production-like environments this must be something or else # Django won't start. (See tecken.apps.TeckenAppConfig) # SYMBOL_URLS = values.ListValue([]) # Same as with SYMBOL_URLS, this has to be set to something # or else Django won't start. UPLOAD_DEFAULT_URL = values.Value() # When an upload comes in with symbols from a Try build, these symbols # mustn't be uploaded with the "regular symbols". # This value can be very similar to UPLOAD_DEFAULT_URL in that # it can use the exact same S3 bucket but have a different prefix. # # Note! By default the value for 'UPLOAD_TRY_SYMBOLS_URL' becomes # the value of 'UPLOAD_DEFAULT_URL' but with a '/try' suffix added. UPLOAD_TRY_SYMBOLS_URL = values.Value() # # The reason for this is to simplify things in local dev and non-Prod # # environments where the location isn't as important. # # Basically, if don't set this, the value # # becomes `settings.UPLOAD_DEFAULT_URL + '/try'` (but carefully so) # @property # def UPLOAD_TRY_SYMBOLS_URL(self): # print(Configuration.LANGUAGES) # print(Configuration.UPLOAD_TRY_SYMBOLS_URL) # # # super() # # print(dir(self)) # # print(self.UPLOAD_TRY_SYMBOLS_URL.value.copy()) # return '/TRY' # value = super().value # return value + '/TRY' # The config is a list of tuples like this: # 'email:url' where the email part can be a glob like regex # For example '*@example.com:https://s3-us-west-2.amazonaws.com/mybucket' # will upload all symbols to a bucket called 'mybucket' for anybody # with a @example.com email. # This is a config that, typed as a Python dictionary, specifies # specific email addresses or patterns to custom URLs. # For example '{"*****@*****.**": "https://s3.amazonaws.com/mybucket"}' # or '{"*@example.com": "https://s3.amazonaws.com/otherbucket"}' for # anybody uploading with an @example.com email address. UPLOAD_URL_EXCEPTIONS = values.DictValue({}) # XXX Can this be deleted? # When an upload comes in, we need to store it somewhere that it # can be shared between the webapp and the Celery worker. # In production-like environments this can't be a local filesystem # path but needs to one that is shared across servers. E.g. EFS. # UPLOAD_INBOX_DIRECTORY = values.Value("./upload-inbox") # The default prefix for locating all symbols SYMBOL_FILE_PREFIX = values.Value("v1") # During upload, for each file in the archive, if the extension # matches this list, the file gets gzip compressed before uploading. COMPRESS_EXTENSIONS = values.ListValue(["sym"]) # For specific file uploads, override the mimetype. # For .sym files, for example, if S3 knows them as 'text/plain' # they become really handy to open in a browser and view directly. MIME_OVERRIDES = values.DictValue({"sym": "text/plain"}) # Number of seconds to wait for a symbol download. If this # trips, no error will be raised and we'll just skip using it # as a known symbol file. # The value gets cached as an empty dict for one hour. SYMBOLS_GET_TIMEOUT = values.Value(5) # Individual strings that can't be allowed in any of the lines in the # content of a symbols archive file. DISALLOWED_SYMBOLS_SNIPPETS = values.ListValue([ # https://bugzilla.mozilla.org/show_bug.cgi?id=1012672 "qcom/proprietary" ]) DOCKERFLOW_CHECKS = [ "dockerflow.django.checks.check_database_connected", "dockerflow.django.checks.check_migrations_applied", "dockerflow.django.checks.check_redis_connected", "tecken.dockerflow_extra.check_redis_store_connected", "tecken.dockerflow_extra.check_storage_urls", ] # We can cache quite aggressively here because the SymbolDownloader # has chance to invalidate certain keys. # Also, any time a symbol archive file is upload, for each file within # that we end up uploading to S3 we also cache invalidate. SYMBOLDOWNLOAD_EXISTS_TTL_SECONDS = values.IntegerValue(60 * 60 * 6) # Whether to start a background task to search for symbols # on Microsoft's server is protected by an in-memory cache. # This is quite important. Don't make it too long or else clients # won't be able retry to see if a Microsoft symbol has been successfully # downloaded by the background job. # We can tweak this when we later learn more about the amount # attempted symbol downloads for .pdb files we get that 404. MICROSOFT_DOWNLOAD_CACHE_TTL_SECONDS = values.IntegerValue(60) # cabextract is installed by Docker and used to unpack .pd_ files to .pdb # It's assumed to be installed on $PATH. CABEXTRACT_PATH = values.Value("cabextract") # dump_syms is downloaded and installed by docker/build_dump_syms.sh # and by default gets to put this specific location. # If you change this, please make sure it works with # how docker/build_dump_syms.sh works. DUMP_SYMS_PATH = values.Value("/dump_syms/dump_syms") # How many uploads to display per page when paginating through # past uploads. API_UPLOADS_BATCH_SIZE = 20 API_UPLOADS_CREATED_BATCH_SIZE = 20 API_FILES_BATCH_SIZE = 40 API_DOWNLOADS_MISSING_BATCH_SIZE = 20 API_DOWNLOADS_MICROSOFT_BATCH_SIZE = 20 # Every time we do a symbol upload, we also take a look to see if there # are incomplete uploads that could have failed due to some unlucky # temporary glitch. # When we do the reattempt, we need to wait sufficiently long because # the upload might just be incomplete because it's in the queue, not # because it failed. # Note also, if the job is put back into a celery job, we also log # this in the cache so that it doesn't add it more than once. That # caching uses this same timeout. UPLOAD_REATTEMPT_LIMIT_SECONDS = values.IntegerValue(60 * 60 * 12) # When you "upload by download", the URL's domain needs to be in this # whitelist. This is to double-check that we don't allow downloads from # domains we don't fully trust. ALLOW_UPLOAD_BY_DOWNLOAD_DOMAINS = values.ListValue( ["queue.taskcluster.net"]) # A list of file extensions that if a file is NOT one of these extensions # we can immediately return 404 and not bother to process for anything # else. # It's case sensitive and has to be lower case. # As a way to get marginal optimization of this, make sure '.sym' is # first in the list since it's the most common. DOWNLOAD_FILE_EXTENSIONS_WHITELIST = values.ListValue( [".sym", ".dl_", ".ex_", ".pd_", ".dbg.gz", ".tar.bz2"])
class Base(Configuration): """ The are correct settings that are primarily targeted at the production system but allow (where appriate) easy overrides either via subclassing or environment variables. """ ########################################################################### # # General settings # PROJECT_NAME = 'pyconde' BASE_DIR = os.path.dirname(os.path.dirname(__file__)) ADMINS = values.ListValue([], converter=parseaddr) ALLOWED_HOSTS = values.ListValue(['localhost', '127.0.0.1']) @property def MANAGERS(self): return self.ADMINS EMAIL_HOST = values.Value() DEFAULT_FROM_EMAIL = values.EmailValue('*****@*****.**') SERVER_EMAIL = values.EmailValue('*****@*****.**') TIME_ZONE = 'Europe/Berlin' LANGUAGE_CODE = 'en' SECRET_KEY = values.SecretValue() EMAIL_SUBJECT_PREFIX = values.Value('[EuroPython 2014] ') USE_I18N = True USE_L10N = True SITE_ID = values.IntegerValue(1) CONFERENCE_ID = values.IntegerValue(1) LANGUAGES = ( ('de', ugettext('German')), ('en', ugettext('English')), ) INTERNAL_IPS = ('127.0.0.1', ) ROOT_URLCONF = '%s.urls' % PROJECT_NAME TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'skins', 'default'), os.path.join(BASE_DIR, 'skins', 'ep14'), ) INSTALLED_APPS = [ # Skins 'pyconde.skins.ep14', 'pyconde.skins.default', 'djangocms_admin_style', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.messages', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.staticfiles', 'django.contrib.markup', 'crispy_forms', 'south', 'easy_thumbnails', 'filer', 'compressor', 'djangocms_text_ckeditor', # must be before 'cms'! 'cms', 'cms.stacks', 'mptt', 'menus', 'sekizai', 'userprofiles', 'userprofiles.contrib.accountverification', 'userprofiles.contrib.emailverification', 'userprofiles.contrib.profiles', 'taggit', 'haystack', #'tinymce', # If you want tinymce, add it in the settings.py file. 'django_gravatar', 'social_auth', 'gunicorn', 'statici18n', 'cms.plugins.inherit', 'cms.plugins.googlemap', 'cms.plugins.link', 'cms.plugins.snippet', #'cms.plugins.twitter', #'cms.plugins.text', 'cmsplugin_filer_file', 'cmsplugin_filer_image', 'djangocms_style', #'cmsplugin_news', 'pyconde.testimonials', # Symposion apps 'pyconde.conference', 'pyconde.speakers', 'pyconde.proposals', 'pyconde.sponsorship', # Custom apps 'pyconde.core', 'pyconde.accounts', 'pyconde.attendees', 'pyconde.events', 'pyconde.reviews', 'pyconde.schedule', 'pyconde.search', 'pyconde.helpers', ] MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.locale.LocaleMiddleware', 'cms.middleware.page.CurrentPageMiddleware', 'cms.middleware.user.CurrentUserMiddleware', 'cms.middleware.toolbar.ToolbarMiddleware', 'cms.middleware.language.LanguageCookieMiddleware', 'social_auth.middleware.SocialAuthExceptionMiddleware', ] TEMPLATE_CONTEXT_PROCESSORS = Configuration.TEMPLATE_CONTEXT_PROCESSORS + ( 'django.core.context_processors.debug', 'django.core.context_processors.request', 'sekizai.context_processors.sekizai', 'pyconde.conference.context_processors.current_conference', 'pyconde.reviews.context_processors.review_roles', 'pyconde.context_processors.less_settings', 'social_auth.context_processors.social_auth_backends', ) DATABASES = values.DatabaseURLValue( 'sqlite:///{0}/djep.db'.format(BASE_DIR), environ_prefix='DJANGO') # Disable south migrations during unittests SOUTH_TESTS_MIGRATE = False FIXTURE_DIRS = (os.path.join(BASE_DIR, 'fixtures'), ) # TODO: As soon as we move to foundation use # https://pypi.python.org/pypi/crispy-forms-foundation CRISPY_TEMPLATE_PACK = 'bootstrap3' # If the project uses Less.js, use the inline-JavaScript renderer in # debug mode. LESS_USE_DYNAMIC_IN_DEBUG = True LOGGING = {'version': 1, 'disable_existing_loggers': True} ########################################################################### # # Debug settings # DEBUG = values.BooleanValue(False) DEBUG_TOOLBAR_CONFIG = {'INTERCEPT_REDIRECTS': False} @property def TEMPLATE_DEBUG(self): return self.DEBUG @property def THUMBNAIL_DEBUG(self): return self.DEBUG ########################################################################### # # File settings # MEDIA_ROOT = values.Value() STATIC_ROOT = values.Value() MEDIA_URL = values.Value('/site_media/') MEDIA_OPTIPNG_PATH = values.Value('optipng') MEDIA_JPEGOPTIM_PATH = values.Value('jpegoptim') STATIC_URL = values.Value('/static_media/') STATICFILES_FINDERS = Configuration.STATICFILES_FINDERS + ( 'pyconde.helpers.static.AppMediaDirectoriesFinder', 'compressor.finders.CompressorFinder', ) STATICFILES_DIRS = values.ListValue() STATICI18N_ROOT = os.path.join(BASE_DIR, PROJECT_NAME, "core", "static") COMPRESS_CSS_FILTERS = ( 'compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.cssmin.CSSMinFilter', ) THUMBNAIL_PROCESSORS = ( 'easy_thumbnails.processors.colorspace', 'easy_thumbnails.processors.autocrop', 'filer.thumbnail_processors.scale_and_crop_with_subject_location', 'easy_thumbnails.processors.filters', ) THUMBNAIL_SIZE = 100 ########################################################################### # # Profile settings # Here we configure what profile module is used and other aspects of a # registered user's profile. # USERPROFILES_CHECK_UNIQUE_EMAIL = True USERPROFILES_DOUBLE_CHECK_EMAIL = False USERPROFILES_DOUBLE_CHECK_PASSWORD = True USERPROFILES_REGISTRATION_FULLNAME = True USERPROFILES_USE_ACCOUNT_VERIFICATION = True USERPROFILES_USE_EMAIL_VERIFICATION = True USERPROFILES_USE_PROFILE = True USERPROFILES_INLINE_PROFILE_ADMIN = True USERPROFILES_USE_PROFILE_VIEW = False USERPROFILES_REGISTRATION_FORM = 'pyconde.accounts.forms.ProfileRegistrationForm' USERPROFILES_PROFILE_FORM = 'pyconde.accounts.forms.ProfileForm' USERPROFILES_EMAIL_VERIFICATION_DONE_URL = 'userprofiles_profile_change' AUTH_PROFILE_MODULE = 'accounts.Profile' ACCOUNTS_FALLBACK_TO_GRAVATAR = False CHILDREN_DATA_DISABLED = True ########################################################################### # # CMS Settings # CMS_TEMPLATES = ( ('cms/default.html', ugettext('Default template')), ('cms/start.html', ugettext('Start page template')), ('cms/page_templates/fullpage.html', ugettext('Full page width (schedule, ...)')), ) # Docs at https://django-cms.readthedocs.org/en/develop/getting_started/configuration.html#cms-languages CMS_LANGUAGES = { 1: [ { 'code': 'en', 'name': ugettext('English'), 'public': True, }, { 'code': 'de', 'name': ugettext('German'), 'public': True, }, ], 'default': { 'fallbacks': ['en', 'de'], 'hide_untranslated': False, } } WYM_TOOLS = ",\n".join([ "{'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'}", "{'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'}", "{'name': 'Superscript', 'title': 'Superscript', 'css': 'wym_tools_superscript'}", "{'name': 'Subscript', 'title': 'Subscript', 'css': 'wym_tools_subscript'}", "{'name': 'InsertOrderedList', 'title': 'Ordered_List', 'css': 'wym_tools_ordered_list'}", "{'name': 'InsertUnorderedList', 'title': 'Unordered_List', 'css': 'wym_tools_unordered_list'}", "{'name': 'Indent', 'title': 'Indent', 'css': 'wym_tools_indent'}", "{'name': 'Outdent', 'title': 'Outdent', 'css': 'wym_tools_outdent'}", "{'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'}", "{'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'}", "{'name': 'Paste', 'title': 'Paste_From_Word', 'css': 'wym_tools_paste'}", "{'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'}", "{'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'}", "{'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'}", "{'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'}", "{'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'}", "{'name': 'Preview', 'title': 'Preview', 'css': 'wym_tools_preview'}", ]) TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', 'relative_urls': False, 'theme_advanced_resizing': True, 'theme_advanced_buttons1_add': 'forecolor,backcolor', 'style_formats': [ { 'title': u'Heading 2 (alternative)', 'block': 'h2', 'classes': 'alt' }, { 'title': u'Heading 3 (alternative)', 'block': 'h3', 'classes': 'alt' }, ] } CMSPLUGIN_NEWS_FEED_TITLE = u'EuroPython 2014 News' CMSPLUGIN_NEWS_FEED_DESCRIPTION = u'News from EuroPython 2014' SCHEDULE_CACHE_SCHEDULE = values.BooleanValue(True) ########################################################################### # # Account and profile settings # AVATAR_MIN_DIMENSION = values.TupleValue(converter=int) AVATAR_MAX_DIMENSION = values.TupleValue(converter=int) ########################################################################### # # Proposal and schedule settings # ATTENDEES_PRODUCT_NUMBER_START = 1000 PROPOSALS_SUPPORT_ADDITIONAL_SPEAKERS = True MAX_CHECKOUT_DURATION = 1800 # 30 minutes # This configures the form that is used for each proposal type identified # by their respective slug. PROPOSALS_TYPED_SUBMISSION_FORMS = { 'training': 'pyconde.proposals.forms.TrainingSubmissionForm', 'talk': 'pyconde.proposals.forms.TalkSubmissionForm', 'poster': 'pyconde.proposals.forms.PosterSubmissionForm', } # These languages should be available when making a session proposal. PROPOSAL_LANGUAGES = ( ('de', ugettext('German')), ('en', ugettext('English')), ) # This setting defines the language that should be pre-selected in the # proposal submission form. PROPOSAL_DEFAULT_LANGUAGE = 'en' ########################################################################### # # Review settings # REVIEWER_APPLICATION_OPEN = values.BooleanValue(False) ########################################################################### # # Search configuration # If no other search backend is specified, Whoosh is used to make the setup # as simple as possible. In production we will be using a Lucene-based # backend like SOLR or ElasticSearch. HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), 'STORAGE': 'file', 'INCLUDE_SPELLING': True, 'BATCH_SIZE': 100, } } ########################################################################### # # Auth settings # LOGIN_ERROR_URL = '/accounts/login/' LOGIN_REDIRECT_URL = '/accounts/welcome/' LOGOUT_REDIRECT_URL = '/' SOCIAL_AUTH_PIPELINE = ( 'social_auth.backends.pipeline.social.social_auth_user', 'social_auth.backends.pipeline.user.get_username', 'social_auth.backends.pipeline.user.create_user', 'social_auth.backends.pipeline.social.associate_user', 'social_auth.backends.pipeline.social.load_extra_data', 'social_auth.backends.pipeline.user.update_user_details', 'social_auth.backends.pipeline.misc.save_status_to_session', 'pyconde.accounts.pipeline.show_request_email_form', 'pyconde.accounts.pipeline.create_profile', ) GITHUB_APP_ID = values.Value() GITHUB_API_SECRET = values.Value() GITHUB_EXTENDED_PERMISSIONS = ['user:email'] TWITTER_CONSUMER_KEY = values.Value() TWITTER_CONSUMER_SECRET = values.Value() GOOGLE_OAUTH2_CLIENT_ID = values.Value() GOOGLE_OAUTH2_CLIENT_SECRET = values.Value() FACEBOOK_APP_ID = values.Value() FACEBOOK_API_SECRET = values.Value() @property def AUTHENTICATION_BACKENDS(self): backends = ['django.contrib.auth.backends.ModelBackend'] if self.GITHUB_APP_ID and self.GITHUB_API_SECRET: backends.insert( -1, 'social_auth.backends.contrib.github.GithubBackend') if self.TWITTER_CONSUMER_KEY and self.WITTER_CONSUMER_SECRET: backends.insert(-1, 'social_auth.backends.twitter.TwitterBackend') if self.FACEBOOK_API_SECRET and self.FACEBOOK_APP_ID: backends.insert(-1, 'social_auth.backends.facebook.FacebookBackend') if self.GOOGLE_OAUTH2_CLIENT_SECRET and self.GOOGLE_OAUTH2_CLIENT_ID: backends.insert(-1, 'social_auth.backends.google.GoogleOAuth2Backend') return backends ########################################################################### # # Payment settings # PAYMILL_PRIVATE_KEY = values.Value() PAYMILL_PUBLIC_KEY = values.Value() PAYMILL_TRANSACTION_DESCRIPTION = 'EuroPython 2014: Purchase ID {purchase_pk}' PAYMENT_METHODS = values.ListValue(['invoice', 'creditcard']) PURCHASE_TERMS_OF_USE_URL = values.Value( "https://ep2014.europython.eu/en/registration/terms-conditions/") PURCHASE_INVOICE_DISABLE_RENDERING = values.BooleanValue(True) # List of emails to be notified when a purchase has been made. PDF is send # to these addresses, too. PURCHASE_INVOICE_EXPORT_RECIPIENTS = values.ListValue([]) PURCHASE_INVOICE_FONT_CONFIG = values.DictValue({'de': {}, 'en': {}}) PURCHASE_INVOICE_FONT_ROOT = values.Value( ) # absolute path on the filesystem PURCHASE_INVOICE_NUMBER_FORMAT = values.Value('INVOICE-{0:d}') PURCHASE_INVOICE_ROOT = values.Value() # absolute path on the filesystem PURCHASE_INVOICE_TEMPLATE_PATH = values.Value( ) # absolute path to invoice template CACHES = values.DictValue({ 'default': { 'BACKEND': 'redis_cache.cache.RedisCache', 'LOCATION': 'localhost:6379:0', 'OPTIONS': { 'PARSER_CLASS': 'redis.connection.HiredisParser' }, }, }) BROKER_URL = values.Value('redis://localhost:6379/0') LOCALE_PATHS = (os.path.join(BASE_DIR, PROJECT_NAME, 'locale'), ) # Default settings for statici18n STATICI18N_OUTPUT_DIR = 'jsi18n' STATICI18N_DOMAIN = 'djangojs' STATICI18N_FILENAME_FUNCTION = 'statici18n.utils.default_filename'
class TestBase(Base): DEBUG = False @property def TEMPLATES(self): TEMP = super().TEMPLATES TEMP[0]['OPTIONS']['debug'] = True return TEMP def _fake_convert_pdf(self, infile, outpath): _, filename = os.path.split(infile) name, ext = filename.rsplit('.', 1) output = os.path.join(outpath, '%s.pdf' % name) args = ['cp', infile, output] return args, output @property def FROIDE_CONFIG(self): config = dict(super().FROIDE_CONFIG) config.update( dict(doc_conversion_call_func=self._fake_convert_pdf, default_law=10000, greetings=[ rec(r"Dear ((?:Mr\.?|Ms\.?) .*),?"), rec(r'Sehr geehrter? ((Herr|Frau) .*),?') ], closings=[ rec(r"Sincerely yours,?"), rec(r'Mit freundlichen Grüßen') ], public_body_officials_public=False)) return config @property def MEDIA_ROOT(self): return os.path.abspath( os.path.join(super().PROJECT_ROOT, "tests", "testdata")) ALLOWED_HOSTS = ('localhost', 'testserver') ELASTICSEARCH_INDEX_PREFIX = 'froide_test' MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' CACHES = values.CacheURLValue('locmem://') TEST_SELENIUM_DRIVER = values.Value('chrome_headless') SECRET_URLS = values.DictValue({ "admin": "admin", "postmark_inbound": "postmark_inbound", "postmark_bounce": "postmark_bounce" }) EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' DEFAULT_FROM_EMAIL = '*****@*****.**' FOI_EMAIL_DOMAIN = 'fragdenstaat.de' CELERY_TASK_ALWAYS_EAGER = True CELERY_TASK_EAGER_PROPAGATES = True MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ] FROIDE_CSRF_MIDDLEWARE = 'django.middleware.csrf.CsrfViewMiddleware'
class Base(Configuration): DEBUG = values.BooleanValue(True) DATABASES = values.DatabaseURLValue('spatialite:///dev.db') SPATIALITE_LIBRARY_PATH = '/usr/local/lib/mod_spatialite.dylib' CONN_MAX_AGE = None INSTALLED_APPS = values.ListValue([ 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', 'django_comments', 'django.contrib.flatpages', 'django.contrib.sitemaps', 'django.contrib.humanize', 'django.contrib.gis', # overwrite management command in # django_elasticsearch_dsl 'froide.helper', # external 'django_elasticsearch_dsl', 'taggit', 'storages', 'treebeard', 'django_filters', 'leaflet', # Semi-external 'filingcabinet', # local 'froide.foirequest', 'froide.foirequestfollower', 'froide.frontpage', 'froide.georegion', 'froide.publicbody', 'froide.document', 'froide.account', 'froide.bounce', 'froide.team', 'froide.foisite', 'froide.problem', 'froide.accesstoken', 'froide.guide', 'froide.comments', 'froide.campaign', # API 'oauth2_provider', 'rest_framework', ]) CACHES = values.CacheURLValue('dummy://') # ############# Site Configuration ######### # Make this unique, and don't share it with anybody. SECRET_KEY = 'make_me_unique!!' SITE_NAME = values.Value('Froide') SITE_EMAIL = values.Value('*****@*****.**') SITE_URL = values.Value('http://*****:*****@example.com'), ) MANAGERS = ADMINS INTERNAL_IPS = values.TupleValue(('127.0.0.1', )) # ############## PATHS ############### PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) LOCALE_PATHS = values.TupleValue( (os.path.abspath(os.path.join(PROJECT_ROOT, '..', "locale")), )) GEOIP_PATH = None # Absolute filesystem path to the directory that will hold user-uploaded files. # Example: "/home/media/media.lawrence.com/media/" MEDIA_ROOT = values.Value( os.path.abspath(os.path.join(PROJECT_ROOT, "..", "files"))) # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash. # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" MEDIA_URL = values.Value('/files/') # Sub path in MEDIA_ROOT that will hold FOI attachments FOI_MEDIA_PATH = values.Value('foi') FOI_MEDIA_URL = values.Value('/files/') FOI_MEDIA_DOMAIN = values.Value('') FOI_MEDIA_TOKENS = False FOI_MEDIA_TOKEN_EXPIRY = 2 * 60 # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/home/media/media.lawrence.com/static/" STATIC_ROOT = values.Value( os.path.abspath(os.path.join(PROJECT_ROOT, "..", "public"))) # Additional locations of static files STATICFILES_DIRS = (os.path.join(PROJECT_ROOT, "static"), ) # ########## URLs ################# ROOT_URLCONF = values.Value('froide.urls') # URL prefix for static files. # Example: "http://media.lawrence.com/static/" # URL that handles the static files like app media. # Example: "http://media.lawrence.com" STATIC_URL = values.Value('/static/') USE_X_ACCEL_REDIRECT = values.BooleanValue(False) X_ACCEL_REDIRECT_PREFIX = values.Value('/protected') # ## URLs that can be translated to a secret value SECRET_URLS = values.DictValue({"admin": "admin"}) # ######## Backends, Finders, Processors, Classes #### AUTH_USER_MODEL = values.Value('account.User') PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'froide.account.hashers.PBKDF2WrappedSHA1PasswordHasher', ] # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.FileSystemFinder', ) TEMPLATES = [{ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(PROJECT_ROOT, "templates"), ], 'OPTIONS': { 'debug': values.BooleanValue(DEBUG), 'loaders': [ 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ], 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.media', 'django.template.context_processors.static', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'froide.helper.context_processors.froide', 'froide.helper.context_processors.site_settings', 'froide.helper.context_processors.block_helper' ] } }] MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'oauth2_provider.middleware.OAuth2TokenMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] COMMENTS_APP = 'froide.comments' # ######### I18N and L10N ################## # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # On Unix systems, a value of None will cause Django to use the same # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = values.Value('Europe/Berlin') USE_TZ = values.BooleanValue(True) # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGE_CODE = values.Value('en') LANGUAGES = ( ('en', _('English')), ('es', _('Spanish')), ('fi-fi', _('Finnish (Finland)')), ('de', _('German')), ('da-dk', _('Danish (Denmark)')), ('it', _('Italian')), ('pt', _('Portuguese')), ('sv-se', _('Swedish (Sweden)')), ('sv-fi', _('Swedish (Finland)')), ('zh-cn', _('Chinese (Simplified)')), ('zh-hk', _('Chinese (Traditional, Hong Kong)')), ) # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = values.BooleanValue(True) # If you set this to False, Django will not format dates, numbers and # calendars according to the current locale USE_L10N = values.BooleanValue(True) DATE_FORMAT = values.Value("d. F Y") SHORT_DATE_FORMAT = values.Value("d.m.Y") DATE_INPUT_FORMATS = values.TupleValue(("%d.%m.%Y", )) SHORT_DATETIME_FORMAT = values.Value("d.m.Y H:i") DATETIME_INPUT_FORMATS = values.TupleValue(("%d.%m.%Y %H:%M", )) TIME_FORMAT = values.Value("H:i") TIME_INPUT_FORMATS = values.TupleValue(("%H:%M", )) HOLIDAYS = [ (1, 1), # New Year's Day (12, 25), # Christmas (12, 26) # Second day of Christmas ] # Weekends are non-working days HOLIDAYS_WEEKENDS = True # Calculates other holidays based on easter sunday HOLIDAYS_FOR_EASTER = (0, -2, 1, 39, 50, 60) # ######## Logging ########## # A sample logging configuration. LOGGING = { 'version': 1, 'disable_existing_loggers': True, 'root': { 'level': 'WARNING', 'handlers': [], }, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'formatters': { 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' }, }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' }, 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', } }, 'loggers': { 'froide': { 'handlers': ['console'], 'propagate': True, 'level': 'DEBUG', }, 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, 'django.db.backends': { 'level': 'ERROR', 'handlers': ['console'], 'propagate': False, } } } # ######## Security ########### CSRF_COOKIE_SECURE = False CSRF_FAILURE_VIEW = values.Value('froide.account.views.csrf_failure') # Change this # ALLOWED_HOSTS = () ALLOWED_REDIRECT_HOSTS = () SESSION_COOKIE_AGE = values.IntegerValue(3628800) # six weeks SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SECURE = False # ######## FilingCabinet Document #### # FILINGCABINET_DOCUMENT_MODEL = 'document.Document' # FILINGCABINET_DOCUMENTCOLLECTION_MODEL = 'document.DocumentCollection' FILINGCABINET_DOCUMENT_MODEL = 'document.Document' FILINGCABINET_DOCUMENTCOLLECTION_MODEL = 'document.DocumentCollection' # ######## Celery ############# CELERY_BEAT_SCHEDULE = { 'fetch-mail': { 'task': 'froide.foirequest.tasks.fetch_mail', 'schedule': crontab(), }, 'detect-asleep': { 'task': 'froide.foirequest.tasks.detect_asleep', 'schedule': crontab(hour=0, minute=0), }, 'detect-overdue': { 'task': 'froide.foirequest.tasks.detect_overdue', 'schedule': crontab(hour=0, minute=0), }, 'update-foirequestfollowers': { 'task': 'froide.foirequestfollower.tasks.batch_update', 'schedule': crontab(hour=0, minute=0), }, 'classification-reminder': { 'task': 'froide.foirequest.tasks.classification_reminder', 'schedule': crontab(hour=7, minute=0, day_of_week=6), }, 'bounce-checker': { 'task': 'froide.bounce.tasks.check_bounces', 'schedule': crontab(hour=3, minute=0), }, 'account-maintenance': { 'task': 'froide.account.tasks.account_maintenance_task', 'schedule': crontab(hour=4, minute=0) } } CELERY_TASK_ALWAYS_EAGER = values.BooleanValue(True) CELERY_TASK_ROUTES = { 'froide.foirequest.tasks.fetch_mail': { "queue": "emailfetch" }, 'froide.foirequest.tasks.process_mail': { "queue": "email" }, 'djcelery_email_send_multiple': { "queue": "emailsend" }, 'froide.helper.tasks.*': { "queue": "searchindex" }, } CELERY_TIMEZONE = 'UTC' # We need to serialize email data as binary # which doesn't work well in JSON CELERY_TASK_SERIALIZER = 'pickle' CELERY_RESULT_SERIALIZER = 'pickle' CELERY_ACCEPT_CONTENT = ['pickle'] CELERY_EMAIL_TASK_CONFIG = {'queue': 'emailsend'} # ######## Search ########### ELASTICSEARCH_INDEX_PREFIX = 'froide' ELASTICSEARCH_DSL = { 'default': { 'hosts': 'localhost:9200' }, } ELASTICSEARCH_DSL_SIGNAL_PROCESSOR = 'django_elasticsearch_dsl.signals.RealTimeSignalProcessor' # ######### API ######### # Do not include xml by default, so lxml doesn't need to be present TASTYPIE_DEFAULT_FORMATS = ['json'] OAUTH2_PROVIDER = { 'SCOPES': { 'read:user': _('Access to user status'), 'read:profile': _('Read user profile information'), 'read:email': _('Read user email'), 'read:request': _('Read your (private) requests'), 'make:request': _('Make requests on your behalf'), 'follow:request': _('Follow/Unfollow requests'), } } OAUTH2_PROVIDER_APPLICATION_MODEL = 'account.Application' LOGIN_URL = 'account-login' REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticatedOrReadOnly', ), 'DEFAULT_PAGINATION_CLASS': 'froide.helper.api_utils.CustomLimitOffsetPagination', 'PAGE_SIZE': 50, 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend', ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'froide.helper.api_renderers.CustomPaginatedCSVRenderer', # 'rest_framework.renderers.BrowsableAPIRenderer', ) } # ######### Froide settings ######## FROIDE_CONFIG = dict( user_can_hide_web=True, public_body_officials_public=True, public_body_officials_email_public=False, request_public_after_due_days=14, payment_possible=True, currency="Euro", default_law=1, search_engine_query= "http://www.google.de/search?as_q=%(query)s&as_epq=&as_oq=&as_eq=&hl=en&lr=&cr=&as_ft=i&as_filetype=&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=%(domain)s&as_rights=&safe=images", greetings=[rec(r"Dear (?:Mr\.?|Mr?s\.? .*?)")], redact_salutation=r"(?:Mr\.?|Mr?s\.?)", custom_replacements=[], closings=[rec(r"Sincerely yours,?")], public_body_boosts={}, autocomplete_body_boosts={}, dryrun=False, read_receipt=False, delivery_receipt=False, dsn=False, delivery_reporter=None, request_throttle= None, # Set to [(15, 7 * 24 * 60 * 60),] for 15 requests in 7 days dryrun_domain="testmail.example.com", allow_pseudonym=False, doc_conversion_binary=None, # replace with libreoffice instance doc_conversion_call_func=None, # see settings_test for use content_urls={ 'terms': '/terms/', 'privary': '/privacy/', 'about': '/about/', 'help': '/help/', }, message_handlers={ 'email': 'froide.foirequest.message_handlers.EmailMessageHandler' }, max_attachment_size=1024 * 1024 * 10, # 10 MB bounce_enabled=False, bounce_max_age=60 * 60 * 24 * 14, # 14 days bounce_format='bounce+{token}@example.com', auto_reply_subject_regex=rec('^(Auto-?Reply|Out of office)'), auto_reply_email_regex=rec('^auto(reply|responder)@')) TESSERACT_DATA_PATH = values.Value('/usr/local/share/tessdata') # ###### Email ############## # Django settings EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_SUBJECT_PREFIX = values.Value('[Froide] ') SERVER_EMAIL = values.Value('*****@*****.**') DEFAULT_FROM_EMAIL = values.Value('*****@*****.**') # Official Notification Mail goes through # the normal Django SMTP Backend EMAIL_HOST = values.Value("") EMAIL_PORT = values.IntegerValue(587) EMAIL_HOST_USER = values.Value("") EMAIL_HOST_PASSWORD = values.Value("") EMAIL_USE_TLS = values.BooleanValue(True) # Froide special case settings # IMAP settings for fetching mail FOI_EMAIL_PORT_IMAP = values.IntegerValue(993) FOI_EMAIL_HOST_IMAP = values.Value("imap.example.com") FOI_EMAIL_ACCOUNT_NAME = values.Value("*****@*****.**") FOI_EMAIL_ACCOUNT_PASSWORD = values.Value("") FOI_EMAIL_USE_SSL = values.BooleanValue(True) # SMTP settings for sending FoI mail FOI_EMAIL_HOST_USER = values.Value(FOI_EMAIL_ACCOUNT_NAME) FOI_EMAIL_HOST_FROM = values.Value(FOI_EMAIL_HOST_USER) FOI_EMAIL_HOST_PASSWORD = values.Value(FOI_EMAIL_ACCOUNT_PASSWORD) FOI_EMAIL_HOST = values.Value("smtp.example.com") FOI_EMAIL_PORT = values.IntegerValue(587) FOI_EMAIL_USE_TLS = values.BooleanValue(True) # The FoI Mail can use a different account FOI_EMAIL_DOMAIN = values.Value("example.com") FOI_EMAIL_TEMPLATE = None # Example: # FOI_EMAIL_TEMPLATE = lambda user_name, secret: "{username}.{secret}@{domain}" % (user_name, secret, FOI_EMAIL_DOMAIN) # Is the message you can send from fixed # or can you send from any address you like? FOI_EMAIL_FIXED_FROM_ADDRESS = values.BooleanValue(True) BOUNCE_EMAIL_HOST_IMAP = values.Value('') BOUNCE_EMAIL_PORT_IMAP = values.Value(993) BOUNCE_EMAIL_ACCOUNT_NAME = values.Value('') BOUNCE_EMAIL_ACCOUNT_PASSWORD = values.Value('') BOUNCE_EMAIL_USE_SSL = values.Value(False)
class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, 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 * RICHIE_ES_HOST * DB_NAME * DB_HOST * DB_PASSWORD * DB_USER """ DEBUG = False SITE_ID = 1 # Security ALLOWED_HOSTS = [] SECRET_KEY = values.Value(None) # System check reference: # https://docs.djangoproject.com/en/2.2/ref/checks/#security SILENCED_SYSTEM_CHECKS = values.ListValue([ # Allow the X_FRAME_OPTIONS to be set to "SAMEORIGIN" "security.W019" ]) # The X_FRAME_OPTIONS value should be set to "SAMEORIGIN" to display # DjangoCMS frontend admin frames. Dockerflow raises a system check security # warning with this setting, one should add "security.W019" to the # SILENCED_SYSTEM_CHECKS setting (see above). X_FRAME_OPTIONS = "SAMEORIGIN" # 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("richie", 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), } } MIGRATION_MODULES = {} # Static files (CSS, JavaScript, Images) STATIC_URL = "/static/" MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(DATA_DIR, "media") STATIC_ROOT = os.path.join(DATA_DIR, "static") # Login/registration related settings LOGIN_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/" LOGIN_URL = "login" LOGOUT_URL = "logout" AUTHENTICATION_BACKENDS = ( "richie.apps.social.backends.EdXOAuth2", "richie.apps.social.backends.EdXOIDC", "django.contrib.auth.backends.ModelBackend", ) # Social auth SOCIAL_AUTH_EDX_OAUTH2_KEY = values.Value() SOCIAL_AUTH_EDX_OAUTH2_SECRET = values.Value() SOCIAL_AUTH_EDX_OAUTH2_ENDPOINT = values.Value() SOCIAL_AUTH_EDX_OIDC_KEY = values.Value() SOCIAL_AUTH_EDX_OIDC_SECRET = values.Value() SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY = values.Value() SOCIAL_AUTH_EDX_OIDC_ENDPOINT = values.Value() SOCIAL_AUTH_POSTGRES_JSONFIELD = False # Mysql compatibility by default SOCIAL_AUTH_PIPELINE = ( # Get the information we can about the user and return it in a simple # format to create the user instance later. In some cases the details are # already part of the auth response from the provider, but sometimes this # could hit a provider API. "social_core.pipeline.social_auth.social_details", # Get the social uid from whichever service we're authing thru. The uid is # the unique identifier of the given user in the provider. "social_core.pipeline.social_auth.social_uid", # Verifies that the current auth process is valid within the current # project, this is where emails and domains whitelists are applied (if # defined). "social_core.pipeline.social_auth.auth_allowed", # Checks if the current social-account is already associated in the site. "social_core.pipeline.social_auth.social_user", # Make up a username for this person. "richie.apps.social.pipeline.user.get_username", # Create a user account if we haven't found one yet. "social_core.pipeline.user.create_user", # Create the record that associates the social account with the user. "social_core.pipeline.social_auth.associate_user", # Populate the extra_data field in the social record with the values # specified by settings (and the default ones like access_token, etc). "social_core.pipeline.social_auth.load_extra_data", # Update the user record with any changed info from the auth service. "social_core.pipeline.user.user_details", ) # Mapping between edx and richie profile fields EDX_USER_PROFILE_TO_DJANGO = values.DictValue() SOCIAL_ERROR_REVERSE_ID = values.Value() # AUTHENTICATION RICHIE_AUTHENTICATION_DELEGATION = { "BASE_URL": values.Value("", environ_name="AUTHENTICATION_BASE_URL", environ_prefix=None), "BACKEND": values.Value("base", environ_name="AUTHENTICATION_BACKEND", environ_prefix=None), # PROFILE_URLS are custom links to access to Auth profile views # from Richie. Link order will reflect the order of display in frontend. # (i) Info - {base_url} is RICHIE_AUTHENTICATION_DELEGATION.BASE_URL # (i) If you need to bind user data into href url, wrap the property between () # e.g: for user.username = johndoe, /u/(username) will be /u/johndoe "PROFILE_URLS": values.ListValue( [ { "label": _("Profile"), "href": _("{base_url:s}/u/(username)") }, { "label": _("Account"), "href": _("{base_url:s}/account/settings") }, ], environ_name="AUTHENTICATION_PROFILE_URLS", environ_prefix=None, ), } # LMS RICHIE_LMS_BACKENDS = [{ # We configure default values that work with the test configuration of # github.com/openfun/openedx-docker. "BASE_URL": values.Value(environ_name="EDX_BASE_URL", environ_prefix=None), # Django backend "BACKEND": values.Value( "richie.apps.courses.lms.edx.EdXLMSBackend", environ_name="EDX_BACKEND", environ_prefix=None, ), "COURSE_REGEX": values.Value( r"^.*/courses/(?P<course_id>.*)/course/?$", environ_name="EDX_COURSE_REGEX", environ_prefix=None, ), "DEFAULT_COURSE_RUN_SYNC_MODE": "sync_to_public", # React frontend "JS_BACKEND": values.Value("base", environ_name="EDX_JS_BACKEND", environ_prefix=None), "JS_COURSE_REGEX": values.Value( r"^.*/courses/(?<course_id>.*)/course/?$", environ_name="EDX_JS_COURSE_REGEX", environ_prefix=None, ), }] RICHIE_COURSE_RUN_SYNC_SECRETS = values.ListValue([]) # Elasticsearch RICHIE_ES_HOST = values.Value("elasticsearch", environ_name="RICHIE_ES_HOST", environ_prefix=None) RICHIE_ES_INDICES_PREFIX = values.Value( default="richie", environ_name="RICHIE_ES_INDICES_PREFIX", environ_prefix=None) # Internationalization TIME_ZONE = "Europe/Paris" USE_I18N = True USE_L10N = True USE_TZ = True # Templates TEMPLATES = [{ "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [os.path.join(BASE_DIR, "templates")], "OPTIONS": { "context_processors": [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "django.template.context_processors.i18n", "django.template.context_processors.debug", "django.template.context_processors.request", "django.template.context_processors.media", "django.template.context_processors.csrf", "django.template.context_processors.tz", "sekizai.context_processors.sekizai", "django.template.context_processors.static", "cms.context_processors.cms_settings", "richie.apps.core.context_processors.site_metas", "social_django.context_processors.backends", "social_django.context_processors.login_redirect", ], "loaders": [ "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ], }, }] MIDDLEWARE = ( "richie.apps.core.cache.LimitBrowserCacheTTLHeaders", "cms.middleware.utils.ApphookReloadMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "dockerflow.django.middleware.DockerflowMiddleware", "cms.middleware.user.CurrentUserMiddleware", "cms.middleware.page.CurrentPageMiddleware", "cms.middleware.toolbar.ToolbarMiddleware", "cms.middleware.language.LanguageCookieMiddleware", "dj_pagination.middleware.PaginationMiddleware", "richie.apps.social.middleware.SocialAuthExceptionMiddleware", ) # Django applications from the highest priority to the lowest INSTALLED_APPS = ( # Richie stuff "richie.apps.demo", "richie.apps.search", "richie.apps.courses", "richie.apps.core", "richie.apps.social", "richie.plugins.glimpse", "richie.plugins.html_sitemap", "richie.plugins.large_banner", "richie.plugins.nesteditem", "richie.plugins.plain_text", "richie.plugins.section", "richie.plugins.simple_picture", "richie.plugins.simple_text_ckeditor", "richie", # Third party apps "dj_pagination", "dockerflow.django", "parler", "rest_framework", "social_django", # Django-cms "djangocms_admin_style", "djangocms_googlemap", "djangocms_link", "djangocms_picture", "djangocms_text_ckeditor", "djangocms_video", "cms", "menus", "sekizai", "treebeard", "filer", "easy_thumbnails", # Django "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.admin", "django.contrib.sites", "django.contrib.sitemaps", "django.contrib.staticfiles", "django.contrib.messages", ) # Languages # - Django LANGUAGE_CODE = "en" # 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"))) # - Django CMS CMS_LANGUAGES = { "default": { "public": True, "hide_untranslated": False, "redirect_on_fallback": True, "fallbacks": ["en", "fr"], }, 1: [ { "public": True, "code": "en", "hide_untranslated": False, "name": _("English"), "fallbacks": ["fr"], "redirect_on_fallback": True, }, { "public": True, "code": "fr", "hide_untranslated": False, "name": _("French"), "fallbacks": ["en"], "redirect_on_fallback": True, }, ], } # - Django Parler PARLER_LANGUAGES = CMS_LANGUAGES # Permisions # - Django CMS CMS_PERMISSION = True # - Django Filer FILER_ENABLE_PERMISSIONS = True FILER_IS_PUBLIC_DEFAULT = True # - Django Pagination PAGINATION_INVALID_PAGE_RAISES_404 = True PAGINATION_DEFAULT_WINDOW = 2 PAGINATION_DEFAULT_MARGIN = 1 # Logging LOGGING = { "version": 1, "disable_existing_loggers": True, "formatters": { "verbose": { "format": "%(levelname)s %(asctime)s %(module)s " "%(process)d %(thread)d %(message)s" } }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", } }, "loggers": { "django.db.backends": { "level": "ERROR", "handlers": ["console"], "propagate": False, } }, } # Cache CACHES = { "default": { "BACKEND": values.Value( "django.core.cache.backends.locmem.LocMemCache", environ_name="CACHE_DEFAULT_BACKEND", environ_prefix=None, ), "LOCATION": values.Value("", environ_name="CACHE_DEFAULT_LOCATION", environ_prefix=None), "OPTIONS": values.DictValue({}, environ_name="CACHE_DEFAULT_OPTIONS", environ_prefix=None), } } # For more details about CMS_CACHE_DURATION, see : # http://docs.django-cms.org/en/latest/reference/configuration.html#cms-cache-durations CMS_CACHE_DURATIONS = values.DictValue({ "menus": 3600, "content": 60, "permissions": 3600 }) # Sessions SESSION_ENGINE = values.Value("django.contrib.sessions.backends.db") # Sentry SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN") # pylint: disable=invalid-name @property def ENVIRONMENT(self): """Environment in which the application is launched.""" return self.__class__.__name__.lower() # 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 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 Common(Configuration): # this will be the django-projects root directory where the manage.py file exists. BASE_DIR = os.path.normpath( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # this will be directory under which all the project's assets will be stored ASSETS_DIR = os.path.join(BASE_DIR, 'assets') CONTENTS_DIR = os.path.join(BASE_DIR, 'contents') # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = values.SecretValue() # SECURITY WARNING: don't run with debug turned on in production! DEBUG = values.BooleanValue(False) ALLOWED_HOSTS = values.ListValue([]) # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django_extensions', 'rest_framework', 'phonenumber_field', 'project.apps.membership.apps.MembershipConfig', 'project.apps.api.apps.ApiConfig', ] 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 = 'project.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 = 'project.wsgi.application' # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = values.DatabaseURLValue('sqlite:///{}'.format( 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', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', 'project.apps.membership.backends.MobileBackend' ] APPEND_SLASH = False # 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 MEDIA_URL = values.Value('/media/') STATIC_URL = values.Value('/statics/') MEDIA_ROOT = os.path.join(CONTENTS_DIR, 'media') STATIC_ROOT = os.path.join(CONTENTS_DIR, 'statics') STATICFILES_DIRS = (os.path.join(ASSETS_DIR, 'static'), ) LOCALE_PATHS = (os.path.join(ASSETS_DIR, 'locale'), ) FIXTURE_DIRS = (os.path.join(ASSETS_DIR, 'fixtures'), ) AUTH_USER_MODEL = 'membership.User' LOGGING_LEVEL = values.Value('DEBUG', environ_prefix='ACHARE') LOGGING = values.DictValue({ 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, 'simple': { 'format': '{levelname} {message}', 'style': '{', }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', } }, 'handlers': { 'console': { 'level': 'INFO', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'verbose' }, 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler', } }, 'loggers': { 'django': { 'handlers': ['console'], 'propagate': True, 'level': 'INFO' }, 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, 'achare': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, } } }) REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning', 'EXCEPTION_HANDLER': 'project.apps.api.views.api_exception_handler', # 'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_PERMISSION_CLASSES': [ 'project.apps.api.permissions.IsRegistered', ], 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ] } SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(days=1), 'REFRESH_TOKEN_LIFETIME': timedelta(days=30), 'ROTATE_REFRESH_TOKENS': False, 'BLACKLIST_AFTER_ROTATION': True, # 'ALGORITHM': 'HS256', # 'SIGNING_KEY': SECRET_KEY, # 'VERIFYING_KEY': None, 'AUTH_HEADER_TYPES': ('Bearer', ), 'USER_ID_FIELD': 'id', 'USER_ID_CLAIM': 'user_id', 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken', ), 'TOKEN_TYPE_CLAIM': 'token_type', # 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', # 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), # 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), } # membership and registration settings REGISTRATION_PIN_EXPIRATION_MINUTES = values.IntegerValue( 5, environ_prefix='ACHARE') VALID_MOBILE_COUNTRY_CODES = values.ListValue(['98'], environ_prefix='ACHARE') REGISTRATION_SEND_SMS_INTERVAL = values.IntegerValue( 120, environ_prefix='ACHARE') REGISTER_ATTEMPTS_LIMIT = values.IntegerValue(3, environ_prefix='ACHARE') VERIFY_ATTEMPTS_LIMIT = values.IntegerValue(10, environ_prefix='ACHARE') REGISTRATION_BAN_MINUTES = values.IntegerValue(30, environ_prefix='ACHARE')
class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, 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 * RICHIE_ES_HOST * DB_NAME * DB_USER * DB_PASSWORD * DB_HOST * DB_PORT """ DEBUG = False SITE_ID = 1 # Security ALLOWED_HOSTS = values.ListValue([]) SECRET_KEY = "ThisIsAnExampleKeyForDevPurposeOnly" # nosec # System check reference: # https://docs.djangoproject.com/en/3.1/ref/checks/#security SILENCED_SYSTEM_CHECKS = values.ListValue([ # Allow the X_FRAME_OPTIONS to be set to "SAMEORIGIN" "security.W019" ]) # The X_FRAME_OPTIONS value should be set to "SAMEORIGIN" to display # DjangoCMS frontend admin frames. Dockerflow raises a system check security # warning with this setting, one should add "security.W019" to the # SILENCED_SYSTEM_CHECKS setting (see above). X_FRAME_OPTIONS = "SAMEORIGIN" # Application definition ROOT_URLCONF = "demo.urls" WSGI_APPLICATION = "demo.wsgi.application" # Database DATABASES = { "default": { "ENGINE": values.Value( "django.db.backends.postgresql_psycopg2", environ_name="DB_ENGINE", environ_prefix=None, ), "NAME": values.Value("richie", environ_name="DB_NAME", environ_prefix=None), "USER": values.Value("richie_user", 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), } } MIGRATION_MODULES = {} # Static files (CSS, JavaScript, Images) STATIC_URL = "/static/" MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(DATA_DIR, "media") STATIC_ROOT = os.path.join(DATA_DIR, "static") # For static files, we want to use a backend that includes a hash in # the filename, that is calculated from the file content, so that browsers always # get the updated version of each file. STATICFILES_STORAGE = values.Value( "base.storage.CDNManifestStaticFilesStorage") AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend", ) # AUTHENTICATION DELEGATION AUTHENTICATION_DELEGATION = { "BASE_URL": values.Value("", environ_name="AUTHENTICATION_BASE_URL", environ_prefix=None), "BACKEND": values.Value( "base", environ_name="AUTHENTICATION_BACKEND", environ_prefix=None, ), # PROFILE_URLS are custom links to access to Auth profile views # from Richie. Link order will reflect the order of display in frontend. # (i) Info - {base_url} is AUTHENTICATION_DELEGATION.BASE_URL # (i) If you need to bind user data into href url, wrap the property between () # e.g: for user.username = johndoe, /u/(username) will be /u/johndoe "PROFILE_URLS": values.ListValue( [ { "label": _("Profile"), "href": "{base_url:s}/u/(username)" }, { "label": _("Account"), "href": "{base_url:s}/account/settings" }, ], environ_name="AUTHENTICATION_PROFILE_URLS", environ_prefix=None, ), } # LMS LMS_BACKENDS = [{ "BACKEND": values.Value( "richie.apps.courses.lms.base.BaseLMSBackend", environ_name="EDX_BACKEND", environ_prefix=None, ), "JS_BACKEND": values.Value( "base", environ_name="EDX_JS_BACKEND", environ_prefix=None, ), "SELECTOR_REGEX": values.Value(r".*", environ_name="EDX_SELECTOR_REGEX", environ_prefix=None), "JS_SELECTOR_REGEX": values.Value(r".*", environ_name="EDX_JS_SELECTOR_REGEX", environ_prefix=None), "JS_COURSE_REGEX": values.Value( r"^.*/courses/(?<course_id>.*)/info$", environ_name="EDX_JS_COURSE_REGEX", environ_prefix=None, ), "BASE_URL": values.Value(environ_name="EDX_BASE_URL", environ_prefix=None), }] # Internationalization TIME_ZONE = "Europe/Paris" USE_I18N = True USE_L10N = True USE_TZ = True LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")] # Mapping between edx and richie profile fields EDX_USER_PROFILE_TO_DJANGO = { "preferred_username": "******", "email": "email", "name": "full_name", "given_name": "first_name", "family_name": "last_name", "locale": "language", "user_id": "user_id", "administrator": "is_staff", } # Templates TEMPLATES = [{ "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [os.path.join(BASE_DIR, "templates")], "OPTIONS": { "context_processors": [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "django.template.context_processors.i18n", "django.template.context_processors.debug", "django.template.context_processors.request", "django.template.context_processors.media", "django.template.context_processors.csrf", "django.template.context_processors.tz", "sekizai.context_processors.sekizai", "django.template.context_processors.static", "cms.context_processors.cms_settings", "richie.apps.core.context_processors.site_metas", ], "loaders": [ "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ], }, }] MIDDLEWARE = ( "richie.apps.core.cache.LimitBrowserCacheTTLHeaders", "cms.middleware.utils.ApphookReloadMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "dockerflow.django.middleware.DockerflowMiddleware", "cms.middleware.user.CurrentUserMiddleware", "cms.middleware.page.CurrentPageMiddleware", "cms.middleware.toolbar.ToolbarMiddleware", "cms.middleware.language.LanguageCookieMiddleware", "dj_pagination.middleware.PaginationMiddleware", ) INSTALLED_APPS = ( # Richie demo stuff "base", # Richie stuff "richie.apps.demo", "richie.apps.search", "richie.apps.courses", "richie.apps.core", "richie.plugins.glimpse", "richie.plugins.html_sitemap", "richie.plugins.large_banner", "richie.plugins.nesteditem", "richie.plugins.plain_text", "richie.plugins.section", "richie.plugins.simple_picture", "richie.plugins.simple_text_ckeditor", "richie", # Third party apps "dj_pagination", "dockerflow.django", "parler", "rest_framework", "storages", # Django-cms "djangocms_admin_style", "djangocms_googlemap", "djangocms_link", "djangocms_picture", "djangocms_text_ckeditor", "djangocms_video", "cms", "menus", "sekizai", "treebeard", "filer", "easy_thumbnails", # Django "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.admin", "django.contrib.sites", "django.contrib.sitemaps", "django.contrib.staticfiles", "django.contrib.messages", ) # Languages # - Django LANGUAGE_CODE = "en" # 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"))) # - Django CMS CMS_LANGUAGES = { "default": { "public": True, "hide_untranslated": False, "redirect_on_fallback": True, "fallbacks": ["en", "fr"], }, 1: [ { "public": True, "code": "en", "hide_untranslated": False, "name": _("English"), "fallbacks": ["fr"], "redirect_on_fallback": True, }, { "public": True, "code": "fr", "hide_untranslated": False, "name": _("French"), "fallbacks": ["en"], "redirect_on_fallback": True, }, ], } # - Django Parler PARLER_LANGUAGES = CMS_LANGUAGES # Permisions # - Django CMS CMS_PERMISSION = True # - Django Filer FILER_ENABLE_PERMISSIONS = True FILER_IS_PUBLIC_DEFAULT = True # - Django Pagination PAGINATION_INVALID_PAGE_RAISES_404 = True PAGINATION_DEFAULT_WINDOW = 2 PAGINATION_DEFAULT_MARGIN = 1 # Logging LOGGING = { "version": 1, "disable_existing_loggers": True, "formatters": { "verbose": { "format": "%(levelname)s %(asctime)s %(module)s " "%(process)d %(thread)d %(message)s" } }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", } }, "loggers": { "django.db.backends": { "level": "ERROR", "handlers": ["console"], "propagate": False, } }, } # Demo RICHIE_DEMO_SITE_DOMAIN = "localhost:8080" RICHIE_DEMO_FIXTURES_DIR = os.path.join(BASE_DIR, "base", "fixtures") # Elasticsearch RICHIE_ES_HOST = values.Value("elasticsearch", environ_name="RICHIE_ES_HOST", environ_prefix=None) RICHIE_ES_INDICES_PREFIX = values.Value( default="richie", environ_name="RICHIE_ES_INDICES_PREFIX", environ_prefix=None) # Cache CACHES = values.DictValue({ "default": { "BACKEND": values.Value( "django_redis.cache.RedisCache", environ_name="CACHE_DEFAULT_BACKEND", environ_prefix=None, ), "LOCATION": values.Value( "mymaster/redis-sentinel:26379,redis-sentinel:26379/0", environ_name="CACHE_DEFAULT_LOCATION", environ_prefix=None, ), "OPTIONS": values.DictValue( {"CLIENT_CLASS": "richie.apps.core.cache.SentinelClient"}, environ_name="CACHE_DEFAULT_OPTIONS", environ_prefix=None, ), "TIMEOUT": values.IntegerValue( 300, environ_name="CACHE_DEFAULT_TIMEOUT", environ_prefix=None, ), } }) # For more details about CMS_CACHE_DURATION, see : # http://docs.django-cms.org/en/latest/reference/configuration.html#cms-cache-durations CMS_CACHE_DURATIONS = values.DictValue({ "menus": 3600, "content": 86400, "permissions": 86400 }) MAX_BROWSER_CACHE_TTL = 600 # Sessions SESSION_ENGINE = values.Value("django.contrib.sessions.backends.cache") # Sentry SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN") # pylint: disable=invalid-name @property def ENVIRONMENT(self): """Environment in which the application is launched.""" return self.__class__.__name__.lower() # pylint: disable=invalid-name @property def RELEASE(self): """ Return the release information. Delegate to the module function to enable easier testing. """ return get_release() # pylint: disable=invalid-name @property def CMS_CACHE_PREFIX(self): """ Set cache prefix specific to release so existing cache is invalidated for new deployments. """ return f"cms_{get_release():s}_" @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.ENVIRONMENT, release=cls.RELEASE, integrations=[DjangoIntegration()], ) with sentry_sdk.configure_scope() as scope: scope.set_extra("application", "backend")
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 """ BASE_DIR = os.path.dirname(__file__) 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_psycopg2", 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" SILENCED_SYSTEM_CHECKS = 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", "rest_framework", "marsha.core.apps.CoreConfig", ] 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", "dockerflow.django.middleware.DockerflowMiddleware", ] 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" WSGI_APPLICATION = "marsha.wsgi.application" REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework_simplejwt.authentication.JWTTokenUserAuthentication", ) } # 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"]) VIDEO_RESOLUTIONS = [144, 240, 480, 720, 1080] # 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_SOURCE_BUCKET_NAME = values.Value() UPDATE_STATE_SHARED_SECRETS = values.ListValue() # 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 # 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", ), } @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() # Try to get the current release from the version.json file generated by # the CI during the Docker image build try: with open(os.path.join(cls.BASE_DIR, "version.json")) as version: release = json.load(version)["version"] except FileNotFoundError: release = "NA" # The DJANGO_SENTRY_DSN environment variable should be set to activate # sentry for an environment sentry_dsn = values.Value(None, environ_name="SENTRY_DSN") if sentry_dsn is not None: sentry_sdk.init( dsn=sentry_dsn, environment=cls.__name__.lower(), release=release, integrations=[DjangoIntegration()], )
class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, 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 * RICHIE_ES_HOST * DB_NAME * DB_USER * DB_PASSWORD * DB_HOST * DB_PORT """ DEBUG = False SITE_ID = 1 # Security ALLOWED_HOSTS = values.ListValue([]) SECRET_KEY = "ThisIsAnExampleKeyForDevPurposeOnly" # nosec # System check reference: # https://docs.djangoproject.com/en/3.1/ref/checks/#security SILENCED_SYSTEM_CHECKS = values.ListValue([ # Allow the X_FRAME_OPTIONS to be set to "SAMEORIGIN" "security.W019" ]) # The X_FRAME_OPTIONS value should be set to "SAMEORIGIN" to display # DjangoCMS frontend admin frames. Dockerflow raises a system check security # warning with this setting, one should add "security.W019" to the # SILENCED_SYSTEM_CHECKS setting (see above). X_FRAME_OPTIONS = "SAMEORIGIN" # Application definition ROOT_URLCONF = "cnfpt.urls" WSGI_APPLICATION = "cnfpt.wsgi.application" # Database DATABASES = { "default": { "ENGINE": values.Value( "django.db.backends.postgresql_psycopg2", environ_name="DB_ENGINE", environ_prefix=None, ), "NAME": values.Value("cnfpt", 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), } } MIGRATION_MODULES = {} # Static files (CSS, JavaScript, Images) STATIC_URL = "/static/" MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(DATA_DIR, "media") STATIC_ROOT = os.path.join(DATA_DIR, "static") # For static files, we want to use a backend that includes a hash in # the filename, that is calculated from the file content, so that browsers always # get the updated version of each file. STATICFILES_STORAGE = values.Value( "base.storage.CDNManifestStaticFilesStorage") AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend", ) # AUTHENTICATION DELEGATION RICHIE_AUTHENTICATION_DELEGATION = { "BASE_URL": values.Value("", environ_name="AUTHENTICATION_BASE_URL", environ_prefix=None), "BACKEND": values.Value( "openedx-hawthorn", environ_name="AUTHENTICATION_BACKEND", environ_prefix=None, ), # PROFILE_URLS are custom links to access to Auth profile views # from Richie. Link order will reflect the order of display in frontend. # (i) Info - {base_url} is AUTHENTICATION_DELEGATION.BASE_URL # (i) If you need to bind user data into href url, wrap the property between () # e.g: for user.username = johndoe, /u/(username) will be /u/johndoe "PROFILE_URLS": values.DictValue( { "dashboard": { "label": _("Dashboard"), "href": _("{base_url:s}/dashboard"), }, "profile": { "label": _("Profile"), "href": _("{base_url:s}/u/(username)"), }, "account": { "label": _("Account"), "href": _("{base_url:s}/account/settings"), }, }, environ_name="AUTHENTICATION_PROFILE_URLS", environ_prefix=None, ), } # LMS RICHIE_LMS_BACKENDS = [{ "BACKEND": values.Value( "richie.apps.courses.lms.edx.EdXLMSBackend", environ_name="EDX_BACKEND", environ_prefix=None, ), "JS_BACKEND": values.Value( "openedx-hawthorn", environ_name="EDX_JS_BACKEND", environ_prefix=None, ), "COURSE_REGEX": values.Value( r"^.*/courses/(?P<course_id>.*)/course/?$", environ_name="EDX_COURSE_REGEX", environ_prefix=None, ), "JS_COURSE_REGEX": values.Value( r"^.*/courses/(.*)/course/?$", environ_name="EDX_JS_COURSE_REGEX", environ_prefix=None, ), "BASE_URL": values.Value(environ_name="EDX_BASE_URL", environ_prefix=None), }] RICHIE_COURSE_RUN_SYNC_SECRETS = values.ListValue([]) # CMS # Minimum enrollment count value that would be shown on course detail page RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT = values.Value( 0, environ_name="RICHIE_MINIMUM_COURSE_RUNS_ENROLLMENT_COUNT", environ_prefix=None, ) # Internationalization TIME_ZONE = "Europe/Paris" USE_I18N = True USE_L10N = True USE_TZ = True LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")] # Templates TEMPLATES = [{ "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [os.path.join(BASE_DIR, "templates")], "OPTIONS": { "context_processors": [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "django.template.context_processors.i18n", "django.template.context_processors.debug", "django.template.context_processors.request", "django.template.context_processors.media", "django.template.context_processors.csrf", "django.template.context_processors.tz", "sekizai.context_processors.sekizai", "django.template.context_processors.static", "cms.context_processors.cms_settings", "richie.apps.core.context_processors.site_metas", ], "loaders": [ "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ], }, }] # Django CMS CMS_PLACEHOLDER_CONF_OVERRIDES = { "courses/cms/course_detail.html course_teaser": { "name": _("Teaser"), "plugins": ["LTIConsumerPlugin"], "limits": { "LTIConsumerPlugin": 1 }, }, "courses/cms/course_detail.html course_audience": { "name": _("Audience"), "plugins": ["CKEditorPlugin"], "limits": { "CKEditorPlugin": 1 }, }, } RICHIE_SIMPLETEXT_CONFIGURATION = [ *RichieCoursesConfigurationMixin.RICHIE_SIMPLETEXT_CONFIGURATION, { "placeholders": ["course_audience"], "ckeditor": "CKEDITOR_LIMITED_CONFIGURATION", "max_length": 1200, }, ] MIDDLEWARE = ( "richie.apps.core.cache.LimitBrowserCacheTTLHeaders", "cms.middleware.utils.ApphookReloadMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "dockerflow.django.middleware.DockerflowMiddleware", "cms.middleware.user.CurrentUserMiddleware", "cms.middleware.page.CurrentPageMiddleware", "cms.middleware.toolbar.ToolbarMiddleware", "cms.middleware.language.LanguageCookieMiddleware", "dj_pagination.middleware.PaginationMiddleware", ) INSTALLED_APPS = ( # CNFPT stuff "base", # Richie stuff "richie.apps.demo", "richie.apps.search", "richie.apps.courses", "richie.apps.core", "richie.plugins.glimpse", "richie.plugins.html_sitemap", "richie.plugins.large_banner", "richie.plugins.lti_consumer", "richie.plugins.nesteditem", "richie.plugins.plain_text", "richie.plugins.section", "richie.plugins.simple_picture", "richie.plugins.simple_text_ckeditor", "richie", # Third party apps "dj_pagination", "dockerflow.django", "parler", "rest_framework", "storages", # Django-cms "djangocms_admin_style", "djangocms_googlemap", "djangocms_link", "djangocms_picture", "djangocms_text_ckeditor", "djangocms_video", "django_check_seo", "cms", "menus", "sekizai", "treebeard", "filer", "easy_thumbnails", # Django "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.admin", "django.contrib.sites", "django.contrib.sitemaps", "django.contrib.staticfiles", "django.contrib.messages", "django.contrib.humanize", ) # Languages # - Django LANGUAGE_CODE = "fr" # 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"))) # - Django CMS CMS_LANGUAGES = { "default": { "public": True, "hide_untranslated": False, "redirect_on_fallback": True, "fallbacks": ["en", "fr"], }, 1: [ { "public": False, "code": "en", "hide_untranslated": False, "name": _("English"), "fallbacks": ["fr"], "redirect_on_fallback": True, }, { "public": True, "code": "fr", "hide_untranslated": False, "name": _("French"), "fallbacks": ["en"], "redirect_on_fallback": True, }, ], } # - Django Parler PARLER_LANGUAGES = CMS_LANGUAGES # Permisions # - Django CMS CMS_PERMISSION = True # - Django Filer FILER_ENABLE_PERMISSIONS = True FILER_IS_PUBLIC_DEFAULT = True # - Django Pagination PAGINATION_INVALID_PAGE_RAISES_404 = True PAGINATION_DEFAULT_WINDOW = 2 PAGINATION_DEFAULT_MARGIN = 1 # Logging LOGGING = { "version": 1, "disable_existing_loggers": True, "formatters": { "verbose": { "format": "%(levelname)s %(asctime)s %(module)s " "%(process)d %(thread)d %(message)s" } }, "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose", } }, "loggers": { "django.db.backends": { "level": "ERROR", "handlers": ["console"], "propagate": False, } }, } # Demo RICHIE_DEMO_SITE_DOMAIN = "localhost:8000" RICHIE_DEMO_FIXTURES_DIR = os.path.join(BASE_DIR, "base", "fixtures") # Elasticsearch RICHIE_ES_HOST = values.Value("elasticsearch", environ_name="RICHIE_ES_HOST", environ_prefix=None) RICHIE_ES_INDICES_PREFIX = values.Value( default="richie", environ_name="RICHIE_ES_INDICES_PREFIX", environ_prefix=None) RICHIE_ES_STATE_WEIGHTS = values.ListValue(None) # LTI Content RICHIE_LTI_PROVIDERS = { "marsha": { "oauth_consumer_key": values.Value( "InsecureOauthConsumerKey", environ_name="LTI_OAUTH_CONSUMER_KEY", environ_prefix=None, ), "shared_secret": values.Value( "InsecureSharedSecret", environ_name="LTI_SHARED_SECRET", environ_prefix=None, ), "base_url": values.Value( "https://marsha\.education/lti/videos/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", # noqa environ_name="LTI_BASE_URL", environ_prefix=None, ), "display_name": "Marsha Video", "is_base_url_regex": True, "automatic_resizing": True, "inline_ratio": 0.5625, } } # Cache CACHES = values.DictValue({ "default": { "BACKEND": values.Value( "base.cache.RedisCacheWithFallback", environ_name="CACHE_DEFAULT_BACKEND", environ_prefix=None, ), "LOCATION": values.Value( "mymaster/redis-sentinel:26379,redis-sentinel:26379/0", environ_name="CACHE_DEFAULT_LOCATION", environ_prefix=None, ), "OPTIONS": values.DictValue( { "CLIENT_CLASS": "richie.apps.core.cache.SentinelClient", }, environ_name="CACHE_DEFAULT_OPTIONS", environ_prefix=None, ), "TIMEOUT": values.IntegerValue(300, environ_name="CACHE_DEFAULT_TIMEOUT", environ_prefix=None), }, "memory_cache": { "BACKEND": values.Value( "django.core.cache.backends.locmem.LocMemCache", environ_name="CACHE_FALLBACK_BACKEND", environ_prefix=None, ), "LOCATION": values.Value( None, environ_name="CACHE_FALLBACK_LOCATION", environ_prefix=None, ), "OPTIONS": values.DictValue( {}, environ_name="CACHE_FALLBACK_OPTIONS", environ_prefix=None, ), }, }) # Search RICHIE_FILTERS_CONFIGURATION = [ ( "richie.apps.search.filter_definitions.NestingWrapper", { "name": "course_runs", "filters": [ ( "richie.apps.search.filter_definitions.AvailabilityFilterDefinition", { "human_name": _("Availability"), "is_drilldown": True, "min_doc_count": 0, "name": "availability", "position": 1, }, ), ], }, ), ( "richie.apps.search.filter_definitions.IndexableHierarchicalFilterDefinition", { "human_name": _("Subjects"), "min_doc_count": 0, "name": "subjects", "position": 2, "reverse_id": "subjects", "term": "categories", }, ), ] # For more details about CMS_CACHE_DURATION, see : # http://docs.django-cms.org/en/latest/reference/configuration.html#cms-cache-durations CMS_CACHE_DURATIONS = values.DictValue({ "menus": 3600, "content": 86400, "permissions": 86400 }) MAX_BROWSER_CACHE_TTL = 600 # Sessions SESSION_ENGINE = values.Value("django.contrib.sessions.backends.cache") # Sentry SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN") # Admin # - Django CMS # Maximum children nodes to allow a parent to be unfoldable # in the page tree admin view CMS_PAGETREE_DESCENDANTS_LIMIT = 80 # - Django CMS Check SEO # Excludes all elements that are not related to the page content DJANGO_CHECK_SEO_EXCLUDE_CONTENT = ( "body > svg, #main-menu, .body-footer, .body-mentions") # pylint: disable=invalid-name @property def ENVIRONMENT(self): """Environment in which the application is launched.""" return self.__class__.__name__.lower() # pylint: disable=invalid-name @property def RELEASE(self): """ Return the release information. Delegate to the module function to enable easier testing. """ return get_release() # pylint: disable=invalid-name @property def CMS_CACHE_PREFIX(self): """ Set cache prefix specific to release so existing cache is invalidated for new deployments. """ return f"cms_{get_release():s}_" @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") # Customize DjangoCMS placeholders configuration cls.CMS_PLACEHOLDER_CONF = merge_dict( cls.CMS_PLACEHOLDER_CONF, cls.CMS_PLACEHOLDER_CONF_OVERRIDES)
class UipaOrgThemeBase(ThemeBase): FROIDE_THEME = 'uipa_org.theme' SITE_NAME = "UIPA.org" SITE_EMAIL = "*****@*****.**" SITE_URL = 'http://localhost:8000' PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) STATIC_ROOT = os.path.abspath(os.path.join(PROJECT_ROOT, "..", "public")) FIXTURE_DIRS = ('fixtures',) MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' SECRET_KEY = os_env('SECRET_KEY') MEDIA_ROOT = os_env('MEDIA_ROOT') DATA_UPLOAD_MAX_MEMORY_SIZE = 26214400 # 25MB TAGGING_AUTOCOMPLETE_MAX_TAGS = 100 @property def INSTALLED_APPS(self): installed = super(UipaOrgThemeBase, self).INSTALLED_APPS installed += [ 'celery_haystack', 'djcelery_email', 'django.contrib.redirects', 'uipa_org.uipa_constants', 'uipa_org.theme.templatetags.uipa_extras', 'tinymce', 'raven.contrib.django.raven_compat' ] return installed MIDDLEWARE_CLASSES = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', 'django.contrib.redirects.middleware.RedirectFallbackMiddleware', 'froide.account.middleware.AcceptNewTermsMiddleware', ] TINYMCE_DEFAULT_CONFIG = { 'plugins': "table,spellchecker,paste,searchreplace", 'theme': "advanced", 'cleanup_on_startup': False } SECRET_URLS = values.DictValue({ "admin": "uipa-admin", "postmark_inbound": "uipa_postmark_inbound", "postmark_bounce": "uipa_postmark_bounce" }) HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine', 'URL': 'http://127.0.0.1:9200/', 'INDEX_NAME': 'haystack', } } TIME_ZONE = values.Value('Pacific/Honolulu') CELERY_IMPORTS = ('uipa_org.tasks',) CELERY_TIMEZONE = values.Value('Pacific/Honolulu') CELERYBEAT_SCHEDULE = { 'fetch-mail': { 'task': 'froide.foirequest.tasks.fetch_mail', 'schedule': crontab(), }, 'detect-asleep': { 'task': 'froide.foirequest.tasks.detect_asleep', 'schedule': crontab(hour=0, minute=0), }, 'detect-overdue': { 'task': 'froide.foirequest.tasks.detect_overdue', 'schedule': crontab(hour=0, minute=0), }, 'update-foirequestfollowers': { 'task': 'froide.foirequestfollower.tasks.batch_update', 'schedule': crontab(hour=0, minute=0), }, 'classification-reminder': { 'task': 'froide.foirequest.tasks.classification_reminder', 'schedule': crontab(hour=7, minute=0, day_of_week=6), }, 'uipa-private_public_reminder': { 'task': 'uipa_org.tasks.private_public_reminder', 'schedule': crontab(hour=0, minute=0), }, 'uipa-make_public_private': { 'task': 'uipa_org.tasks.make_private_public', 'schedule': crontab(hour=0, minute=0), }, 'uipa-deferred_message_notification': { 'task': 'uipa_org.tasks.deferred_message_notification', 'schedule': crontab(hour=6, minute=0), }, } CELERY_RESULT_BACKEND = 'rpc' CELERY_RESULT_PERSISTENT = True @property def FROIDE_CONFIG(self): config = super(UipaOrgThemeBase, self).FROIDE_CONFIG config.update(dict( currency="Dollars", create_new_publicbody=False, publicbody_empty=False, user_can_hide_web=True, public_body_officials_public=True, public_body_officials_email_public=False, request_public_after_due_days=14, payment_possible=False, default_law=1, greetings=[rec(u"Aloha (?:Mr\.?|Ms\.? .*?)")], closings=[rec(u"Mahalo,?")], public_body_boosts={}, dryrun=True, dryrun_domain="beta.uipa.org", allow_pseudonym=False, # doc_conversion_binary=None, # replace with libreoffice instance doc_conversion_binary="/Applications/LibreOffice.app/Contents/MacOS/soffice", doc_conversion_call_func=None, # see settings_test for use api_activated=True, search_engine_query='http://www.google.com/search?as_q=%(query)s&as_epq=&as_oq=&as_eq=&hl=en&lr=&cr=&as_ft=i&as_filetype=&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=%(domain)s&as_rights=&safe=images', show_public_body_employee_name=False, make_public_num_days_after_due_date=365, ga_tracking_id=os_env('GA_TRACKING_ID'), )) return config
class Production(Base): INSTALLED_APPS = Base.INSTALLED_APPS + [ 'raven.contrib.django.raven_compat', ] RAVEN_CONFIG = values.DictValue()