• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / wger / 24284668695

11 Apr 2026 02:31PM UTC coverage: 85.12% (-0.04%) from 85.156%
24284668695

push

github

rolandgeider
Watch for default keys

To prevent local instances using default keys in production, server running
with these will warn and generate random ones

0 of 12 new or added lines in 1 file covered. (0.0%)

11 existing lines in 3 files now uncovered.

16995 of 19966 relevant lines covered (85.12%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/settings/main.py
1
# This file is part of wger Workout Manager.
2
#
3
# wger Workout Manager is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU Affero General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# wger Workout Manager is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU Affero General Public License
14

15
# ruff: noqa: F405
16

17
# Standard Library
NEW
18
import secrets
×
NEW
19
import warnings
×
20

21
# Third Party
22
import environ
×
23

24
# wger
25
from .settings_global import *  # noqa: F403
×
26

27
"""
×
28
Main settings file for a production deployment of wger.
29

30
For a more commented version of the options used here, please refer to
31
https://github.com/wger-project/docker/blob/master/config/prod.env
32
"""
33

34
env = environ.Env(
×
35
    # set casting, default value
36
    DJANGO_DEBUG=(bool, False)
37
)
38

39
# A list of keys used in the docker repo as defaults. To prevent instances using
40
# these defaults in production, servers with these keys will warn and generate
41
# random ones
NEW
42
_DEFAULT_KEYS = {
×
43
    'wger-docker-supersecret-key-1234567890!@#$%^&*(-_)',
44
    'wger-docker-secret-jwtkey-1234567890!@#$%^&*(-_=+)',
45
}
46

47
# Use 'DEBUG = True' to get more details for server errors
48
DEBUG = env('DJANGO_DEBUG')
×
49

50
if os.environ.get('DJANGO_ADMINS'):
×
51
    ADMINS = [
×
52
        env.tuple('DJANGO_ADMINS'),
53
    ]
54
    MANAGERS = ADMINS
×
55

56
if os.environ.get('DJANGO_DB_ENGINE'):
×
57
    DATABASES = {
×
58
        'default': {
59
            'ENGINE': env.str('DJANGO_DB_ENGINE'),
60
            'NAME': env.str('DJANGO_DB_DATABASE'),
61
            'USER': env.str('DJANGO_DB_USER'),
62
            'PASSWORD': env.str('DJANGO_DB_PASSWORD'),
63
            'HOST': env.str('DJANGO_DB_HOST'),
64
            'PORT': env.int('DJANGO_DB_PORT'),
65
        }
66
    }
67
else:
68
    DATABASES = {
×
69
        'default': {
70
            'ENGINE': 'django.db.backends.sqlite3',
71
            'NAME': env.str('DJANGO_DB_DATABASE', '/home/wger/db/database.sqlite'),
72
        }
73
    }
74

75
# Timezone for this installation. Consult settings_global.py for more information
76
TIME_ZONE = env.str('TIME_ZONE', 'Europe/Berlin')
×
77

78
# Django's secret key
79
# Generate e.g. with: python -c "import secrets; print(secrets.token_urlsafe(50))" or https://djecrety.ir/
NEW
80
SECRET_KEY = env.str('SECRET_KEY', '')
×
NEW
81
if not SECRET_KEY or SECRET_KEY in _DEFAULT_KEYS:
×
NEW
82
    SECRET_KEY = secrets.token_urlsafe(50)
×
NEW
83
    warnings.warn(
×
84
        'SECRET_KEY is not set or uses the default value so '
85
        'a random key was generated, sessions will not persist across restarts. '
86
        'Set SECRET_KEY in your environment for production use.',
87
        stacklevel=1,
88
    )
89

90
# Your reCaptcha keys
91
RECAPTCHA_PUBLIC_KEY = env.str('RECAPTCHA_PUBLIC_KEY', '')
×
92
RECAPTCHA_PRIVATE_KEY = env.str('RECAPTCHA_PRIVATE_KEY', '')
×
93
RECAPTCHA_REQUIRED_SCORE = env.float('RECAPTCHA_REQUIRED_SCORE', 0.75)
×
94

95
# The site's URL (e.g. http://www.my-local-gym.com or http://localhost:8000)
96
# This is needed for uploaded files and images (exercise images, etc.) to be
97
# properly served.
98
SITE_URL = env.str('SITE_URL', 'http://localhost:8000')
×
99

100
# Path to uploaded files
101
# Absolute filesystem path to the directory that will hold user-uploaded files.
102
MEDIA_ROOT = env.str('DJANGO_MEDIA_ROOT', '/home/wger/media')
×
103
STATIC_ROOT = env.str('DJANGO_STATIC_ROOT', '/home/wger/static')
×
104

105
# If you change these, adjust nginx alias definitions as well
106
MEDIA_URL = env.str('MEDIA_URL', '/media/')
×
107
STATIC_URL = env.str('STATIC_URL', '/static/')
×
108

109
LOGIN_REDIRECT_URL = env.str('LOGIN_REDIRECT_URL', '/')
×
110

111
# Allow all hosts to access the application. Change if used in production.
112
ALLOWED_HOSTS = [
×
113
    '*',
114
]
115

116
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
×
117

118
# Configure a real backend in production
119
if DEBUG:
×
120
    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
×
121

122
if env.bool('ENABLE_EMAIL', False):
×
123
    EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
×
124
    EMAIL_HOST = env.str('EMAIL_HOST')
×
125
    EMAIL_PORT = env.int('EMAIL_PORT')
×
126
    EMAIL_HOST_USER = env.str('EMAIL_HOST_USER')
×
127
    EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD')
×
128
    EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', True)
×
129
    EMAIL_USE_SSL = env.bool('EMAIL_USE_SSL', False)
×
130
    EMAIL_TIMEOUT = 60
×
131

132
# Sender address used for sent emails
133
DEFAULT_FROM_EMAIL = env.str('FROM_EMAIL', 'wger Workout Manager <wger@example.com>')
×
134
WGER_SETTINGS['EMAIL_FROM'] = DEFAULT_FROM_EMAIL
×
135
SERVER_EMAIL = DEFAULT_FROM_EMAIL
×
136
EMAIL_FROM_ADDRESS = DEFAULT_FROM_EMAIL
×
137

138
# Management
139
WGER_SETTINGS['ALLOW_GUEST_USERS'] = env.bool('ALLOW_GUEST_USERS', True)
×
140
WGER_SETTINGS['ALLOW_REGISTRATION'] = env.bool('ALLOW_REGISTRATION', True)
×
141
WGER_SETTINGS['ALLOW_UPLOAD_VIDEOS'] = env.bool('ALLOW_UPLOAD_VIDEOS', True)
×
142
WGER_SETTINGS['DOWNLOAD_INGREDIENTS_FROM'] = env.str('DOWNLOAD_INGREDIENTS_FROM', 'WGER')
×
143
WGER_SETTINGS['EXERCISE_CACHE_TTL'] = env.int('EXERCISE_CACHE_TTL', 3600)
×
144
WGER_SETTINGS['MIN_ACCOUNT_AGE_TO_TRUST'] = env.int('MIN_ACCOUNT_AGE_TO_TRUST', 21)  # in days
×
145
WGER_SETTINGS['SYNC_EXERCISES_CELERY'] = env.bool('SYNC_EXERCISES_CELERY', False)
×
146
WGER_SETTINGS['SYNC_EXERCISE_IMAGES_CELERY'] = env.bool('SYNC_EXERCISE_IMAGES_CELERY', False)
×
147
WGER_SETTINGS['SYNC_EXERCISE_VIDEOS_CELERY'] = env.bool('SYNC_EXERCISE_VIDEOS_CELERY', False)
×
148
WGER_SETTINGS['SYNC_INGREDIENTS_CELERY'] = env.bool('SYNC_INGREDIENTS_CELERY', False)
×
149
if env.str('SYNC_INGREDIENTS_DUMP_URL', ''):
×
150
    WGER_SETTINGS['SYNC_INGREDIENTS_DUMP_URL'] = env.str('SYNC_INGREDIENTS_DUMP_URL')
×
151
WGER_SETTINGS['SYNC_OFF_DAILY_DELTA_CELERY'] = env.bool('SYNC_OFF_DAILY_DELTA_CELERY', False)
×
152
WGER_SETTINGS['EXPORT_INGREDIENTS_BULK_CELERY'] = env.bool('EXPORT_INGREDIENTS_BULK_CELERY', False)
×
153
WGER_SETTINGS['USE_RECAPTCHA'] = env.bool('USE_RECAPTCHA', False)
×
154
WGER_SETTINGS['USE_CELERY'] = env.bool('USE_CELERY', False)
×
155
WGER_SETTINGS['CACHE_API_EXERCISES_CELERY'] = env.bool('CACHE_API_EXERCISES_CELERY', False)
×
156
WGER_SETTINGS['CACHE_API_EXERCISES_CELERY_FORCE_UPDATE'] = env.bool(
×
157
    'CACHE_API_EXERCISES_CELERY_FORCE_UPDATE', False
158
)
159

160
#
161
# Auth Proxy Authentication
162
# https://wger.readthedocs.io/en/latest/administration/auth_proxy.html
163
AUTH_PROXY_HEADER = env.str('AUTH_PROXY_HEADER', '')
×
164
AUTH_PROXY_TRUSTED_IPS = env.list('AUTH_PROXY_TRUSTED_IPS', default=[])
×
165
AUTH_PROXY_CREATE_UNKNOWN_USER = env.bool('AUTH_PROXY_CREATE_UNKNOWN_USER', False)
×
166
AUTH_PROXY_USER_EMAIL_HEADER = env.str('AUTH_PROXY_USER_EMAIL_HEADER', '')
×
167
AUTH_PROXY_USER_NAME_HEADER = env.str('AUTH_PROXY_USER_NAME_HEADER', '')
×
168

169
# Cache
170
if os.environ.get('DJANGO_CACHE_BACKEND'):
×
171
    CACHES = {
×
172
        'default': {
173
            'BACKEND': env.str('DJANGO_CACHE_BACKEND'),
174
            'LOCATION': env.str('DJANGO_CACHE_LOCATION', ''),
175
            'TIMEOUT': env.int('DJANGO_CACHE_TIMEOUT', 300),
176
            'OPTIONS': {'CLIENT_CLASS': env.str('DJANGO_CACHE_CLIENT_CLASS', '')},
177
        }
178
    }
179

180
    if os.environ.get('DJANGO_CACHE_CLIENT_PASSWORD'):
×
181
        CACHES['default']['OPTIONS']['PASSWORD'] = env.str('DJANGO_CACHE_CLIENT_PASSWORD')
×
182

183
    CONNECTION_POOL_KWARGS = dict()
×
184
    if 'DJANGO_CACHE_CLIENT_SSL_KEYFILE' in os.environ:
×
185
        CONNECTION_POOL_KWARGS['ssl_keyfile'] = env.str('DJANGO_CACHE_CLIENT_SSL_KEYFILE')
×
186

187
    if 'DJANGO_CACHE_CLIENT_SSL_CERTFILE' in os.environ:
×
188
        CONNECTION_POOL_KWARGS['ssl_certfile'] = env.str('DJANGO_CACHE_CLIENT_SSL_CERTFILE')
×
189

190
    if 'DJANGO_CACHE_CLIENT_SSL_CERT_REQS' in os.environ:
×
191
        CONNECTION_POOL_KWARGS['ssl_cert_reqs'] = env.str('DJANGO_CACHE_CLIENT_SSL_CERT_REQS')
×
192

193
    if 'DJANGO_CACHE_CLIENT_SSL_CHECK_HOSTNAME' in os.environ:
×
194
        CONNECTION_POOL_KWARGS['ssl_check_hostname'] = env.bool(
×
195
            'DJANGO_CACHE_CLIENT_SSL_CHECK_HOSTNAME'
196
        )
197

198
    if CONNECTION_POOL_KWARGS:
×
199
        CACHES['default']['OPTIONS']['CONNECTION_POOL_KWARGS'] = CONNECTION_POOL_KWARGS
×
200

201
#
202
# Django Compressor
203
# Consult https://django-compressor.readthedocs.io/en/stable/ for more information
204
# (specially the offline compression part)
205
#
206
COMPRESS_ROOT = STATIC_ROOT
×
207
COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', not DEBUG)
×
208
COMPRESS_OFFLINE = env.bool('COMPRESS_OFFLINE', False)
×
209

210
# The site's domain as used by the email verification workflow
211
EMAIL_PAGE_DOMAIN = SITE_URL
×
212

213
#
214
# Django Axes
215
#
216
AXES_ENABLED = env.bool('AXES_ENABLED', True)
×
217
AXES_LOCKOUT_PARAMETERS = env.list('AXES_LOCKOUT_PARAMETERS', default=['ip_address'])
×
218
AXES_FAILURE_LIMIT = env.int('AXES_FAILURE_LIMIT', 10)
×
219
AXES_COOLOFF_TIME = timedelta(minutes=env.float('AXES_COOLOFF_TIME', 30))
×
220
AXES_HANDLER = env.str('AXES_HANDLER', 'axes.handlers.cache.AxesCacheHandler')
×
221
AXES_IPWARE_PROXY_COUNT = env.int('AXES_IPWARE_PROXY_COUNT', 0)
×
222
AXES_IPWARE_META_PRECEDENCE_ORDER = env.list(
×
223
    'AXES_IPWARE_META_PRECEDENCE_ORDER', default=['REMOTE_ADDR']
224
)
225

226
#
227
# Django Rest Framework SimpleJWT
228
#
229
SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'] = timedelta(minutes=env.int('ACCESS_TOKEN_LIFETIME', 15))
×
230
SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'] = timedelta(hours=env.int('REFRESH_TOKEN_LIFETIME', 24))
×
NEW
231
_SIGNING_KEY = env.str('SIGNING_KEY', '')
×
NEW
232
if not _SIGNING_KEY or _SIGNING_KEY in _DEFAULT_KEYS:
×
NEW
233
    _SIGNING_KEY = secrets.token_urlsafe(50)
×
NEW
234
    warnings.warn(
×
235
        'SIGNING_KEY is not set or uses the default value so '
236
        'a random key was generated, sessions will not persist across restarts. '
237
        'Set SIGNING_KEY in your environment for production use.',
238
        stacklevel=1,
239
    )
NEW
240
SIMPLE_JWT['SIGNING_KEY'] = _SIGNING_KEY
×
241

242
#
243
# https://docs.djangoproject.com/en/4.1/ref/csrf/
244
#
245
CSRF_TRUSTED_ORIGINS = env.list(
×
246
    'CSRF_TRUSTED_ORIGINS',
247
    default=['http://127.0.0.1', 'http://localhost', 'https://localhost'],
248
)
249

250
if env.bool('X_FORWARDED_PROTO_HEADER_SET', False):
×
251
    SECURE_PROXY_SSL_HEADER = (
×
252
        env.str('SECURE_PROXY_SSL_HEADER', 'HTTP_X_FORWARDED_PROTO'),
253
        'https',
254
    )
255

256
REST_FRAMEWORK['NUM_PROXIES'] = env.int('NUMBER_OF_PROXIES', 1)
×
257

258
#
259
# Celery message queue configuration
260
#
261
CELERY_BROKER_URL = env.str('CELERY_BROKER', 'redis://cache:6379/2')
×
262
CELERY_RESULT_BACKEND = env.str('CELERY_BACKEND', 'redis://cache:6379/2')
×
263

264
#
265
# Prometheus metrics
266
#
267
EXPOSE_PROMETHEUS_METRICS = env.bool('EXPOSE_PROMETHEUS_METRICS', False)
×
268
PROMETHEUS_URL_PATH = env.str('PROMETHEUS_URL_PATH', 'super-secret-path')
×
269

270
#
271
# Logging
272
#
273
LOGGING = {
×
274
    'version': 1,
275
    'disable_existing_loggers': False,
276
    'formatters': {
277
        'simple': {
278
            'format': 'level={levelname} ts={asctime} module={module} path={pathname} line={lineno} message={message}',
279
            'style': '{',
280
        },
281
    },
282
    'handlers': {
283
        'console': {'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple'},
284
    },
285
    'loggers': {
286
        '': {
287
            'handlers': ['console'],
288
            'level': env.str('LOG_LEVEL_PYTHON', 'INFO').upper(),
289
            'propagate': True,
290
        },
291
    },
292
}
293

294
#
295
# Storage options
296
#
297
STORAGES = {
×
298
    'default': {
299
        'BACKEND': env.str(
300
            'DJANGO_STORAGES_DEFAULT_BACKEND',
301
            'django.core.files.storage.FileSystemStorage',
302
        ),
303
    },
304
    # django.contrib.staticfiles.storage.StaticFilesStorage
305
    'staticfiles': {
306
        'BACKEND': env.str(
307
            'DJANGO_STORAGES_STATICFILES_BACKEND',
308
            'wger.core.storage.LenientManifestStaticFilesStorage',
309
        ),
310
    },
311
}
312

313

314
#
315
# S3 object storage config
316
# See https://wger.readthedocs.io/en/latest/production/docker.html#s3-object-storage
317
#
318
USE_S3_MEDIA_FILES = env.bool('USE_S3_MEDIA_FILES', False)
×
319
USE_S3_STATIC_FILES = env.bool('USE_S3_STATIC_FILES', False)
×
320
if USE_S3_MEDIA_FILES or USE_S3_STATIC_FILES:
×
321
    AWS_ACCESS_KEY_ID = env.str('AWS_ACCESS_KEY_ID')
×
322
    AWS_SECRET_ACCESS_KEY = env.str('AWS_SECRET_ACCESS_KEY')
×
323
    AWS_STORAGE_BUCKET_NAME = env.str('AWS_STORAGE_BUCKET_NAME')
×
324
    AWS_S3_REGION_NAME = env.str('AWS_S3_REGION_NAME')
×
325
    AWS_S3_DOMAIN = env.str('AWS_S3_DOMAIN')
×
326
    AWS_S3_ENDPOINT_URL = env.str(
×
327
        'AWS_S3_ENDPOINT_URL',
328
        f'https://{AWS_S3_REGION_NAME}.{AWS_S3_DOMAIN}',
329
    )
330
    AWS_S3_CUSTOM_DOMAIN = env.str(
×
331
        'AWS_S3_CUSTOM_DOMAIN',
332
        f'{AWS_STORAGE_BUCKET_NAME}.{AWS_S3_REGION_NAME}.{AWS_S3_DOMAIN}',
333
    )
334
    AWS_QUERYSTRING_AUTH = False
×
335

336
    if USE_S3_MEDIA_FILES:
×
337
        STORAGES['default'] = {
×
338
            'BACKEND': 'storages.backends.s3boto3.S3Boto3Storage',
339
            'OPTIONS': {
340
                'location': env.str('S3_MEDIA_FILES_LOCATION', 'media'),
341
            },
342
        }
343
        if env.bool('USE_S3_URL_FOR_MEDIA', True):
×
344
            MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/'
×
345

346
    if USE_S3_STATIC_FILES:
×
347
        STORAGES['staticfiles'] = {
×
348
            'BACKEND': 'storages.backends.s3boto3.S3Boto3Storage',
349
            'OPTIONS': {
350
                'location': env.str('S3_STATIC_FILES_LOCATION', 'static'),
351
            },
352
        }
353
        if env.bool('USE_S3_URL_FOR_STATIC', True):
×
354
            STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/'
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc