Coverage for app/venv/lib/python3.14/site-packages/weblate/settings_docker.py: 64%
427 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
5import os
6from logging.handlers import SysLogHandler
8from django.core.exceptions import PermissionDenied
9from django.http import Http404
11from weblate.api.spectacular import (
12 get_drf_settings,
13 get_drf_standardized_errors_settings,
14 get_spectacular_settings,
15)
16from weblate.utils.environment import (
17 get_env_bool,
18 get_env_credentials,
19 get_env_float,
20 get_env_int,
21 get_env_list,
22 get_env_map,
23 get_env_ratelimit,
24 get_env_redis_url,
25 get_env_str,
26 get_saml_idp,
27 modify_env_list,
28)
30# Title of site to use
31SITE_TITLE = get_env_str("WEBLATE_SITE_TITLE", "Weblate")
33# Site domain
34SITE_DOMAIN = get_env_str("WEBLATE_SITE_DOMAIN", required=True)
36# Whether site uses https
37ENABLE_HTTPS = get_env_bool("WEBLATE_ENABLE_HTTPS")
39# Site URL
40SITE_URL = "{}://{}".format("https" if ENABLE_HTTPS else "http", SITE_DOMAIN)
42#
43# Django settings for Weblate project.
44#
46DEBUG = get_env_bool("WEBLATE_DEBUG", False)
48ADMINS = (
49 (
50 get_env_str("WEBLATE_ADMIN_NAME", "Weblate Admin"),
51 get_env_str("WEBLATE_ADMIN_EMAIL", "weblate@example.com"),
52 ),
53)
55MANAGERS = ADMINS
57if get_env_bool("WEBLATE_DATABASES", True): 57 ↛ 94line 57 didn't jump to line 94 because the condition on line 57 was always true
58 DATABASES = {
59 "default": {
60 # Use 'postgresql' or 'mysql'.
61 "ENGINE": "django.db.backends.postgresql",
62 # Database name.
63 "NAME": get_env_str(
64 "POSTGRES_DB", get_env_str("POSTGRES_DATABASE"), required=True
65 ),
66 # Database user.
67 "USER": get_env_str("POSTGRES_USER", required=True),
68 # Name of role to alter to set parameters in PostgreSQL,
69 # use in case role name is different than user used for authentication.
70 "ALTER_ROLE": get_env_str(
71 "POSTGRES_ALTER_ROLE", get_env_str("POSTGRES_USER", required=True)
72 ),
73 # Database password.
74 "PASSWORD": get_env_str("POSTGRES_PASSWORD", required=True),
75 # Set to empty string for localhost.
76 "HOST": get_env_str("POSTGRES_HOST", required=True),
77 # Set to empty string for default.
78 "PORT": get_env_str("POSTGRES_PORT", ""),
79 # Customizations for databases.
80 "OPTIONS": {"sslmode": get_env_str("POSTGRES_SSL_MODE", "prefer")},
81 # Persistent connections
82 "CONN_MAX_AGE": None
83 if "POSTGRES_CONN_MAX_AGE" not in os.environ
84 else get_env_int("POSTGRES_CONN_MAX_AGE"),
85 "CONN_HEALTH_CHECKS": True,
86 # Disable server-side cursors, might be needed with pgbouncer
87 "DISABLE_SERVER_SIDE_CURSORS": get_env_bool(
88 "POSTGRES_DISABLE_SERVER_SIDE_CURSORS"
89 ),
90 }
91 }
93# Data directory
94DATA_DIR = get_env_str("WEBLATE_DATA_DIR", "/app/data")
95CACHE_DIR = get_env_str("WEBLATE_CACHE_DIR", "/app/cache")
97# Local time zone for this installation. Choices can be found here:
98# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
99# although not all choices may be available on all operating systems.
100# In a Windows environment this must be set to your system time zone.
101TIME_ZONE = get_env_str("WEBLATE_TIME_ZONE", "UTC")
103# Language code for this installation. All choices can be found here:
104# http://www.i18nguy.com/unicode/language-identifiers.html
105LANGUAGE_CODE = "en-us"
107LANGUAGES = (
108 ("ar", "العربية"),
109 ("az", "Azərbaycan"),
110 ("ba", "башҡорт теле"), # codespell:ignore
111 ("be", "Беларуская"),
112 ("be-latn", "Biełaruskaja"),
113 ("bg", "Български"),
114 ("br", "Brezhoneg"),
115 ("ca", "Català"),
116 ("cs", "Čeština"),
117 ("cy", "Cymraeg"),
118 ("da", "Dansk"),
119 ("de", "Deutsch"),
120 ("en", "English"),
121 ("el", "Ελληνικά"),
122 ("en-gb", "English (United Kingdom)"),
123 ("es", "Español"),
124 ("fi", "Suomi"),
125 ("fr", "Français"),
126 ("ga", "Gaeilge"),
127 ("gl", "Galego"),
128 ("he", "עברית"),
129 ("hu", "Magyar"),
130 ("hr", "Hrvatski"),
131 ("id", "Indonesia"),
132 ("is", "Íslenska"),
133 ("it", "Italiano"),
134 ("ja", "日本語"),
135 ("kab", "Taqbaylit"),
136 ("kk", "Қазақ тілі"),
137 ("ko", "한국어"),
138 ("nb", "Norsk bokmål"),
139 ("nl", "Nederlands"),
140 ("pl", "Polski"),
141 ("pt", "Português"),
142 ("pt-br", "Português brasileiro"),
143 ("ro", "Română"),
144 ("ru", "Русский"),
145 ("sk", "Slovenčina"),
146 ("sl", "Slovenščina"),
147 ("sq", "Shqip"),
148 ("sr", "Српски"),
149 ("sr-latn", "Srpski"),
150 ("sv", "Svenska"),
151 ("ta", "தமிழ்"),
152 ("th", "ไทย"),
153 ("tr", "Türkçe"),
154 ("uk", "Українська"),
155 ("zh-hans", "简体中文"),
156 ("zh-hant", "正體中文"),
157)
159SITE_ID = 1
161# If you set this to False, Django will make some optimizations so as not
162# to load the internationalization machinery.
163USE_I18N = True
165# If you set this to False, Django will not use timezone-aware datetimes.
166USE_TZ = True
168# Type of automatic primary key, introduced in Django 3.2
169DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
171# URL prefix to use, please see documentation for more details
172URL_PREFIX = get_env_str("WEBLATE_URL_PREFIX", "")
174# Absolute filesystem path to the directory that will hold user-uploaded files.
175MEDIA_ROOT = os.path.join(DATA_DIR, "media")
177# URL that handles the media served from MEDIA_ROOT. Make sure to use a
178# trailing slash.
179MEDIA_URL = get_env_str("WEBLATE_MEDIA_URL", f"{URL_PREFIX}/media/")
181# Absolute path to the directory static files should be collected to.
182# Don't put anything in this directory yourself; store your static files
183# in apps' "static/" subdirectories and in STATICFILES_DIRS.
184STATIC_ROOT = os.path.join(CACHE_DIR, "static")
186# URL prefix for static files.
187STATIC_URL = get_env_str("WEBLATE_STATIC_URL", f"{URL_PREFIX}/static/")
189# Additional locations of static files
190STATICFILES_DIRS = (
191 # Put strings here, like "/home/html/static" or "C:/www/django/static".
192 # Always use forward slashes, even on Windows.
193 # Don't forget to use absolute paths, not relative paths.
194)
196# List of finder classes that know how to find static files in
197# various locations.
198STATICFILES_FINDERS = (
199 "django.contrib.staticfiles.finders.FileSystemFinder",
200 "django.contrib.staticfiles.finders.AppDirectoriesFinder",
201 "compressor.finders.CompressorFinder",
202)
204# Make this unique, and don't share it with anybody.
205# You can generate it using weblate-generate-secret-key
206with open("/app/data/secret") as handle:
207 SECRET_KEY = handle.read()
209TEMPLATES = [
210 {
211 "BACKEND": "django.template.backends.django.DjangoTemplates",
212 "OPTIONS": {
213 "context_processors": [
214 "django.contrib.auth.context_processors.auth",
215 "django.template.context_processors.debug",
216 "django.template.context_processors.i18n",
217 "django.template.context_processors.request",
218 "django.template.context_processors.csrf",
219 "django.contrib.messages.context_processors.messages",
220 "weblate.trans.context_processors.weblate_context",
221 ],
222 },
223 "APP_DIRS": True,
224 }
225]
228# GitHub username and token for sending pull requests.
229# Please see the documentation for more details.
230GITHUB_CREDENTIALS = get_env_credentials("GITHUB")
232# Azure DevOps username, token, and organization for sending pull requests.
233# Please see the documentation for more details.
234AZURE_DEVOPS_CREDENTIALS = get_env_credentials("AZURE_DEVOPS")
236# GitLab username and token for sending merge requests.
237# Please see the documentation for more details.
238GITLAB_CREDENTIALS = get_env_credentials("GITLAB")
240# Gitea username and token for sending pull requests.
241# Please see the documentation for more details.
242GITEA_CREDENTIALS = get_env_credentials("GITEA")
244# Pagure username and token for sending merge requests.
245# Please see the documentation for more details.
246PAGURE_CREDENTIALS = get_env_credentials("PAGURE")
248# Bitbucket username and token for sending merge requests.
249# Please see the documentation for more details.
250BITBUCKETSERVER_CREDENTIALS = get_env_credentials("BITBUCKETSERVER")
252# Bitbucket username and token for sending merge requests.
253# Please see the documentation for more details.
254BITBUCKETCLOUD_CREDENTIALS = get_env_credentials("BITBUCKETCLOUD")
257# Default pull request message.
258# Please see the documentation for more details.
259if "WEBLATE_DEFAULT_PULL_MESSAGE" in os.environ: 259 ↛ 260line 259 didn't jump to line 260 because the condition on line 259 was never true
260 DEFAULT_PULL_MESSAGE = get_env_str("WEBLATE_DEFAULT_PULL_MESSAGE")
262# Authentication configuration
263AUTHENTICATION_BACKENDS: tuple[str, ...] = ()
265# Custom user model
266AUTH_USER_MODEL = "weblate_auth.User"
268# WebAuthn
269OTP_WEBAUTHN_RP_NAME = SITE_TITLE
270OTP_WEBAUTHN_RP_ID = SITE_DOMAIN.split(":")[0]
271OTP_WEBAUTHN_ALLOWED_ORIGINS = [SITE_URL]
272OTP_WEBAUTHN_ALLOW_PASSWORDLESS_LOGIN = False
273OTP_WEBAUTHN_HELPER_CLASS = "weblate.accounts.utils.WeblateWebAuthnHelper"
275if "WEBLATE_NO_EMAIL_AUTH" not in os.environ: 275 ↛ 279line 275 didn't jump to line 279 because the condition on line 275 was always true
276 AUTHENTICATION_BACKENDS += ("social_core.backends.email.EmailAuth",)
278# GitHub auth
279SOCIAL_AUTH_GITHUB_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_GITHUB_KEY")
280if SOCIAL_AUTH_GITHUB_KEY: 280 ↛ 281line 280 didn't jump to line 281 because the condition on line 280 was never true
281 SOCIAL_AUTH_GITHUB_SCOPE = ["user:email"]
282 SOCIAL_AUTH_GITHUB_SECRET = get_env_str(
283 "WEBLATE_SOCIAL_AUTH_GITHUB_SECRET", required=True
284 )
285 AUTHENTICATION_BACKENDS += ("social_core.backends.github.GithubOAuth2",)
287# GitHub org specific auth
288SOCIAL_AUTH_GITHUB_ORG_NAME = get_env_str("WEBLATE_SOCIAL_AUTH_GITHUB_ORG_NAME")
289if SOCIAL_AUTH_GITHUB_ORG_NAME: 289 ↛ 290line 289 didn't jump to line 290 because the condition on line 289 was never true
290 SOCIAL_AUTH_GITHUB_ORG_KEY = get_env_str(
291 "WEBLATE_SOCIAL_AUTH_GITHUB_ORG_KEY", SOCIAL_AUTH_GITHUB_KEY, required=True
292 )
293 SOCIAL_AUTH_GITHUB_ORG_SECRET = get_env_str(
294 "WEBLATE_SOCIAL_AUTH_GITHUB_ORG_SECRET",
295 required=True,
296 fallback_name="WEBLATE_SOCIAL_AUTH_GITHUB_SECRET",
297 )
298 SOCIAL_AUTH_GITHUB_ORG_SCOPE = ["user:email", "read:org"]
299 AUTHENTICATION_BACKENDS += ("social_core.backends.github.GithubOrganizationOAuth2",)
301# GitHub team specific auth
302SOCIAL_AUTH_GITHUB_TEAM_ID = get_env_str("WEBLATE_SOCIAL_AUTH_GITHUB_TEAM_ID")
303if SOCIAL_AUTH_GITHUB_TEAM_ID: 303 ↛ 304line 303 didn't jump to line 304 because the condition on line 303 was never true
304 SOCIAL_AUTH_GITHUB_TEAM_KEY = get_env_str(
305 "WEBLATE_SOCIAL_AUTH_GITHUB_TEAM_KEY", SOCIAL_AUTH_GITHUB_KEY, required=True
306 )
307 SOCIAL_AUTH_GITHUB_TEAM_SECRET = get_env_str(
308 "WEBLATE_SOCIAL_AUTH_GITHUB_TEAM_SECRET",
309 required=True,
310 fallback_name="WEBLATE_SOCIAL_AUTH_GITHUB_SECRET",
311 )
312 SOCIAL_AUTH_GITHUB_TEAM_SCOPE = ["user:email", "read:org"]
313 AUTHENTICATION_BACKENDS += ("social_core.backends.github.GithubTeamOAuth2",)
315# GitHub Enterprise specific auth
316SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY = get_env_str(
317 "WEBLATE_SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY"
318)
319if SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: 319 ↛ 320line 319 didn't jump to line 320 because the condition on line 319 was never true
320 SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET = get_env_str(
321 "WEBLATE_SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET", required=True
322 )
323 SOCIAL_AUTH_GITHUB_ENTERPRISE_URL = get_env_str(
324 "WEBLATE_SOCIAL_AUTH_GITHUB_ENTERPRISE_URL", required=True
325 )
326 SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL = get_env_str(
327 "WEBLATE_SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL", required=True
328 )
329 SOCIAL_AUTH_GITHUB_ENTERPRISE_SCOPE = get_env_list(
330 "WEBLATE_SOCIAL_AUTH_GITHUB_ENTERPRISE_SCOPE", default=["user:email"]
331 )
332 AUTHENTICATION_BACKENDS += (
333 "social_core.backends.github_enterprise.GithubEnterpriseOAuth2",
334 )
336SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY = get_env_str(
337 "WEBLATE_SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY"
338)
339if SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY: 339 ↛ 340line 339 didn't jump to line 340 because the condition on line 339 was never true
340 SOCIAL_AUTH_BITBUCKET_OAUTH2_SECRET = get_env_str(
341 "WEBLATE_SOCIAL_AUTH_BITBUCKET_OAUTH2_SECRET", required=True
342 )
343 SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY = True
344 AUTHENTICATION_BACKENDS += ("social_core.backends.bitbucket.BitbucketOAuth2",)
347SOCIAL_AUTH_FACEBOOK_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_FACEBOOK_KEY")
348if SOCIAL_AUTH_FACEBOOK_KEY: 348 ↛ 349line 348 didn't jump to line 349 because the condition on line 348 was never true
349 SOCIAL_AUTH_FACEBOOK_SECRET = get_env_str(
350 "WEBLATE_SOCIAL_AUTH_FACEBOOK_SECRET", required=True
351 )
352 SOCIAL_AUTH_FACEBOOK_SCOPE = ["email", "public_profile"]
353 SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {"fields": "id,name,email"}
354 AUTHENTICATION_BACKENDS += ("social_core.backends.facebook.FacebookOAuth2",)
357SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_GOOGLE_OAUTH2_KEY")
358if SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: 358 ↛ 359line 358 didn't jump to line 359 because the condition on line 358 was never true
359 SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = get_env_str(
360 "WEBLATE_SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET", required=True
361 )
362 SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = get_env_list(
363 "WEBLATE_SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS"
364 )
365 SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_EMAILS = get_env_list(
366 "WEBLATE_SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_EMAILS"
367 )
368 AUTHENTICATION_BACKENDS += ("social_core.backends.google.GoogleOAuth2",)
371SOCIAL_AUTH_MUSICBRAINZ_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_MUSICBRAINZ_KEY")
372if SOCIAL_AUTH_MUSICBRAINZ_KEY: 372 ↛ 373line 372 didn't jump to line 373 because the condition on line 372 was never true
373 SOCIAL_AUTH_MUSICBRAINZ_SECRET = get_env_str(
374 "WEBLATE_SOCIAL_AUTH_MUSICBRAINZ_SECRET", required=True
375 )
376 AUTHENTICATION_BACKENDS += ("social_core.backends.musicbrainz.MusicBrainzOAuth2",)
379SOCIAL_AUTH_GITLAB_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_GITLAB_KEY")
380if SOCIAL_AUTH_GITLAB_KEY: 380 ↛ 381line 380 didn't jump to line 381 because the condition on line 380 was never true
381 SOCIAL_AUTH_GITLAB_SECRET = get_env_str(
382 "WEBLATE_SOCIAL_AUTH_GITLAB_SECRET", required=True
383 )
384 if "WEBLATE_SOCIAL_AUTH_GITLAB_API_URL" in os.environ:
385 SOCIAL_AUTH_GITLAB_API_URL = get_env_str("WEBLATE_SOCIAL_AUTH_GITLAB_API_URL")
386 AUTHENTICATION_BACKENDS += ("social_core.backends.gitlab.GitLabOAuth2",)
388SOCIAL_AUTH_AUTH0_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_AUTH0_KEY")
389if SOCIAL_AUTH_AUTH0_KEY: 389 ↛ 390line 389 didn't jump to line 390 because the condition on line 389 was never true
390 SOCIAL_AUTH_AUTH0_SECRET = get_env_str(
391 "WEBLATE_SOCIAL_AUTH_AUTH0_SECRET", required=True
392 )
393 SOCIAL_AUTH_AUTH0_DOMAIN = get_env_str(
394 "WEBLATE_SOCIAL_AUTH_AUTH0_DOMAIN", required=True
395 )
396 SOCIAL_AUTH_AUTH0_TITLE = get_env_str("WEBLATE_SOCIAL_AUTH_AUTH0_TITLE")
397 SOCIAL_AUTH_AUTH0_IMAGE = get_env_str("WEBLATE_SOCIAL_AUTH_AUTH0_IMAGE")
398 SOCIAL_AUTH_AUTH0_SCOPE = ["openid", "profile", "email"]
399 if "WEBLATE_SOCIAL_AUTH_AUTH0_AUTH_EXTRA_ARGUMENTS" in os.environ:
400 SOCIAL_AUTH_AUTH0_AUTH_EXTRA_ARGUMENTS = get_env_map(
401 "WEBLATE_SOCIAL_AUTH_AUTH0_AUTH_EXTRA_ARGUMENTS"
402 )
403 AUTHENTICATION_BACKENDS += ("social_core.backends.auth0.Auth0OAuth2",)
406# SAML
407WEBLATE_SAML_IDP = get_saml_idp()
408if WEBLATE_SAML_IDP: 408 ↛ 409line 408 didn't jump to line 409 because the condition on line 408 was never true
409 AUTHENTICATION_BACKENDS += ("social_core.backends.saml.SAMLAuth",)
410 # The keys are generated on container startup if missing
411 with open("/app/data/ssl/saml.crt") as handle:
412 SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = handle.read()
413 with open("/app/data/ssl/saml.key") as handle:
414 SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = handle.read()
415 SOCIAL_AUTH_SAML_SP_ENTITY_ID = f"{SITE_URL}/accounts/metadata/saml/"
416 # Identity Provider
417 SOCIAL_AUTH_SAML_ENABLED_IDPS = {"weblate": WEBLATE_SAML_IDP}
418 SOCIAL_AUTH_SAML_SUPPORT_CONTACT = SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = {
419 "givenName": ADMINS[0][0],
420 "emailAddress": ADMINS[0][1],
421 }
422 SOCIAL_AUTH_SAML_ORG_INFO = {
423 "en-US": {
424 "name": "weblate",
425 "displayname": SITE_TITLE,
426 "url": SITE_URL,
427 }
428 }
429 SOCIAL_AUTH_SAML_IMAGE = get_env_str("WEBLATE_SAML_IDP_IMAGE")
430 SOCIAL_AUTH_SAML_TITLE = get_env_str("WEBLATE_SAML_IDP_TITLE")
432# Azure
433SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_AZUREAD_OAUTH2_KEY")
434if SOCIAL_AUTH_AZUREAD_OAUTH2_KEY: 434 ↛ 435line 434 didn't jump to line 435 because the condition on line 434 was never true
435 SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = get_env_str(
436 "WEBLATE_SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET", required=True
437 )
438 AUTHENTICATION_BACKENDS += ("social_core.backends.azuread.AzureADOAuth2",)
440# Azure AD Tenant
441SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY = get_env_str(
442 "WEBLATE_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY"
443)
444if SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY: 444 ↛ 445line 444 didn't jump to line 445 because the condition on line 444 was never true
445 SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET = get_env_str(
446 "WEBLATE_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET", required=True
447 )
448 SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID = get_env_str(
449 "WEBLATE_SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID", required=True
450 )
451 AUTHENTICATION_BACKENDS += (
452 "social_core.backends.azuread_tenant.AzureADTenantOAuth2",
453 )
455# Keycloak
456SOCIAL_AUTH_KEYCLOAK_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_KEYCLOAK_KEY")
457if SOCIAL_AUTH_KEYCLOAK_KEY: 457 ↛ 458line 457 didn't jump to line 458 because the condition on line 457 was never true
458 SOCIAL_AUTH_KEYCLOAK_SECRET = get_env_str(
459 "WEBLATE_SOCIAL_AUTH_KEYCLOAK_SECRET", required=True
460 )
461 SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY = get_env_str(
462 "WEBLATE_SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY", required=True
463 )
464 SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL = get_env_str(
465 "WEBLATE_SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL", required=True
466 )
467 SOCIAL_AUTH_KEYCLOAK_ALGORITHM = get_env_str(
468 "WEBLATE_SOCIAL_AUTH_KEYCLOAK_ALGORITHM", "RS256"
469 )
470 SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL = get_env_str(
471 "WEBLATE_SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL", required=True
472 )
473 SOCIAL_AUTH_KEYCLOAK_IMAGE = get_env_str("WEBLATE_SOCIAL_AUTH_KEYCLOAK_IMAGE")
474 SOCIAL_AUTH_KEYCLOAK_TITLE = get_env_str("WEBLATE_SOCIAL_AUTH_KEYCLOAK_TITLE")
475 SOCIAL_AUTH_KEYCLOAK_ID_KEY = "email"
476 AUTHENTICATION_BACKENDS += ("social_core.backends.keycloak.KeycloakOAuth2",)
478# Linux distros
479if "WEBLATE_SOCIAL_AUTH_FEDORA" in os.environ: 479 ↛ 480line 479 didn't jump to line 480 because the condition on line 479 was never true
480 AUTHENTICATION_BACKENDS += ("social_core.backends.fedora.FedoraOpenId",)
481if "WEBLATE_SOCIAL_AUTH_OPENSUSE" in os.environ: 481 ↛ 482line 481 didn't jump to line 482 because the condition on line 481 was never true
482 AUTHENTICATION_BACKENDS += ("social_core.backends.suse.OpenSUSEOpenId",)
483 SOCIAL_AUTH_OPENSUSE_FORCE_EMAIL_VALIDATION = True
484if "WEBLATE_SOCIAL_AUTH_UBUNTU" in os.environ: 484 ↛ 485line 484 didn't jump to line 485 because the condition on line 484 was never true
485 AUTHENTICATION_BACKENDS += ("social_core.backends.ubuntu.UbuntuOpenId",)
486if "WEBLATE_SOCIAL_AUTH_OPENINFRA" in os.environ: 486 ↛ 487line 486 didn't jump to line 487 because the condition on line 486 was never true
487 AUTHENTICATION_BACKENDS += ("social_core.backends.openinfra.OpenInfraOpenId",)
489# Slack
490SOCIAL_AUTH_SLACK_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_SLACK_KEY")
491if SOCIAL_AUTH_SLACK_KEY: 491 ↛ 492line 491 didn't jump to line 492 because the condition on line 491 was never true
492 SOCIAL_AUTH_SLACK_SECRET = get_env_str(
493 "WEBLATE_SOCIAL_AUTH_SLACK_SECRET", required=True
494 )
495 AUTHENTICATION_BACKENDS += ("social_core.backends.slack.SlackOAuth2",)
497# Generic OpenID Connect
498SOCIAL_AUTH_OIDC_OIDC_ENDPOINT = get_env_str("WEBLATE_SOCIAL_AUTH_OIDC_OIDC_ENDPOINT")
499if SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: 499 ↛ 500line 499 didn't jump to line 500 because the condition on line 499 was never true
500 AUTHENTICATION_BACKENDS += (
501 "social_core.backends.open_id_connect.OpenIdConnectAuth",
502 )
503 SOCIAL_AUTH_OIDC_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_OIDC_KEY", required=True)
504 SOCIAL_AUTH_OIDC_TITLE = get_env_str("WEBLATE_SOCIAL_AUTH_OIDC_TITLE")
505 SOCIAL_AUTH_OIDC_IMAGE = get_env_str("WEBLATE_SOCIAL_AUTH_OIDC_IMAGE")
506 SOCIAL_AUTH_OIDC_SECRET = get_env_str(
507 "WEBLATE_SOCIAL_AUTH_OIDC_SECRET", required=True
508 )
509 if "WEBLATE_SOCIAL_AUTH_OIDC_USERNAME_KEY" in os.environ:
510 SOCIAL_AUTH_OIDC_USERNAME_KEY = os.environ[
511 "WEBLATE_SOCIAL_AUTH_OIDC_USERNAME_KEY"
512 ]
514# Gitea
515SOCIAL_AUTH_GITEA_KEY = get_env_str("WEBLATE_SOCIAL_AUTH_GITEA_KEY")
516if SOCIAL_AUTH_GITEA_KEY: 516 ↛ 517line 516 didn't jump to line 517 because the condition on line 516 was never true
517 SOCIAL_AUTH_GITEA_SECRET = get_env_str(
518 "WEBLATE_SOCIAL_AUTH_GITEA_SECRET", required=True
519 )
520 if "WEBLATE_SOCIAL_AUTH_GITEA_API_URL" in os.environ:
521 SOCIAL_AUTH_GITEA_API_URL = get_env_str("WEBLATE_SOCIAL_AUTH_GITEA_API_URL")
522 AUTHENTICATION_BACKENDS += ("social_core.backends.gitea.GiteaOAuth2",)
524# https://docs.weblate.org/en/latest/admin/auth.html#ldap-authentication
525AUTH_LDAP_SERVER_URI = get_env_str("WEBLATE_AUTH_LDAP_SERVER_URI")
526if AUTH_LDAP_SERVER_URI: 526 ↛ 527line 526 didn't jump to line 527 because the condition on line 526 was never true
527 import ldap
528 from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
530 AUTH_LDAP_USER_DN_TEMPLATE = get_env_str("WEBLATE_AUTH_LDAP_USER_DN_TEMPLATE")
531 AUTHENTICATION_BACKENDS += ("django_auth_ldap.backend.LDAPBackend",)
532 AUTH_LDAP_USER_ATTR_MAP = get_env_map(
533 "WEBLATE_AUTH_LDAP_USER_ATTR_MAP", {"full_name": "name", "email": "mail"}
534 )
535 AUTH_LDAP_BIND_DN = get_env_str("WEBLATE_AUTH_LDAP_BIND_DN")
536 AUTH_LDAP_BIND_PASSWORD = get_env_str("WEBLATE_AUTH_LDAP_BIND_PASSWORD")
538 if "WEBLATE_AUTH_LDAP_USER_SEARCH" in os.environ:
539 AUTH_LDAP_USER_SEARCH = LDAPSearch(
540 get_env_str("WEBLATE_AUTH_LDAP_USER_SEARCH"),
541 ldap.SCOPE_SUBTREE,
542 get_env_str("WEBLATE_AUTH_LDAP_USER_SEARCH_FILTER", "(uid=%(user)s)"),
543 )
545 if "WEBLATE_AUTH_LDAP_USER_SEARCH_UNION" in os.environ:
546 SEARCH_FILTER = get_env_str(
547 "WEBLATE_AUTH_LDAP_USER_SEARCH_FILTER", "(uid=%(user)s)"
548 )
550 SEARCH_UNION = [
551 LDAPSearch(string, ldap.SCOPE_SUBTREE, SEARCH_FILTER)
552 for string in get_env_str("WEBLATE_AUTH_LDAP_USER_SEARCH_UNION").split(
553 get_env_str("WEBLATE_AUTH_LDAP_USER_SEARCH_UNION_DELIMITER", "|")
554 )
555 ]
557 AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*SEARCH_UNION)
559 if not get_env_bool("WEBLATE_AUTH_LDAP_CONNECTION_OPTION_REFERRALS", True):
560 AUTH_LDAP_CONNECTION_OPTIONS = {
561 ldap.OPT_REFERRALS: 0,
562 }
564# Always include Weblate backend
565AUTHENTICATION_BACKENDS += ("weblate.accounts.auth.WeblateUserBackend",)
567# Social auth settings
568SOCIAL_AUTH_PIPELINE = [
569 "social_core.pipeline.social_auth.social_details",
570 "social_core.pipeline.social_auth.social_uid",
571 "social_core.pipeline.social_auth.auth_allowed",
572 "social_core.pipeline.social_auth.social_user",
573 "weblate.accounts.pipeline.store_params",
574 "weblate.accounts.pipeline.verify_open",
575 "social_core.pipeline.user.get_username",
576 "weblate.accounts.pipeline.require_email",
577 "social_core.pipeline.mail.mail_validation",
578 "weblate.accounts.pipeline.revoke_mail_code",
579 "weblate.accounts.pipeline.ensure_valid",
580 "weblate.accounts.pipeline.remove_account",
581 "social_core.pipeline.social_auth.associate_by_email",
582 "weblate.accounts.pipeline.reauthenticate",
583 "weblate.accounts.pipeline.verify_username",
584 "social_core.pipeline.user.create_user",
585 "social_core.pipeline.social_auth.associate_user",
586 "social_core.pipeline.social_auth.load_extra_data",
587 "weblate.accounts.pipeline.second_factor",
588 "weblate.accounts.pipeline.cleanup_next",
589 "weblate.accounts.pipeline.user_full_name",
590 "weblate.accounts.pipeline.store_email",
591 "weblate.accounts.pipeline.notify_connect",
592 "weblate.accounts.pipeline.handle_invite",
593 "weblate.accounts.pipeline.password_reset",
594]
595SOCIAL_AUTH_DISCONNECT_PIPELINE = (
596 "social_core.pipeline.disconnect.allowed_to_disconnect",
597 "social_core.pipeline.disconnect.get_entries",
598 "social_core.pipeline.disconnect.revoke_tokens",
599 "weblate.accounts.pipeline.cycle_session",
600 "weblate.accounts.pipeline.adjust_primary_mail",
601 "weblate.accounts.pipeline.notify_disconnect",
602 "social_core.pipeline.disconnect.disconnect",
603 "weblate.accounts.pipeline.cleanup_next",
604)
606# Custom authentication strategy
607SOCIAL_AUTH_STRATEGY = "weblate.accounts.strategy.WeblateStrategy"
609# Raise exceptions so that we can handle them later
610SOCIAL_AUTH_RAISE_EXCEPTIONS = True
612SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = "weblate.accounts.pipeline.send_validation"
613SOCIAL_AUTH_EMAIL_VALIDATION_URL = f"{URL_PREFIX}/accounts/email-sent/"
614SOCIAL_AUTH_LOGIN_ERROR_URL = f"{URL_PREFIX}/accounts/login/"
615SOCIAL_AUTH_EMAIL_FORM_URL = f"{URL_PREFIX}/accounts/email/"
616SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = f"{URL_PREFIX}/accounts/profile/#account"
617SOCIAL_AUTH_PROTECTED_USER_FIELDS = ("email",)
618SOCIAL_AUTH_SLUGIFY_USERNAMES = True
619SOCIAL_AUTH_SLUGIFY_FUNCTION = "weblate.accounts.pipeline.slugify_username"
621# Value higher than 0 enables validation using zxcvbn
622PASSWORD_MINIMAL_STRENGTH = get_env_int("WEBLATE_MIN_PASSWORD_SCORE", 3)
624# Password validation configuration
625AUTH_PASSWORD_VALIDATORS = [
626 {
627 "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
628 },
629 {
630 "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
631 "OPTIONS": {"min_length": 10},
632 },
633 {"NAME": "weblate.accounts.password_validation.MaximalLengthValidator"},
634 {"NAME": "weblate.accounts.password_validation.PastPasswordsValidator"},
635]
637# Optional password strength validation by django-zxcvbn-password
638if PASSWORD_MINIMAL_STRENGTH > 0: 638 ↛ 643line 638 didn't jump to line 643 because the condition on line 638 was always true
639 AUTH_PASSWORD_VALIDATORS.append(
640 {"NAME": "django_zxcvbn_password_validator.ZxcvbnPasswordValidator"}
641 )
642else:
643 AUTH_PASSWORD_VALIDATORS.extend(
644 [
645 {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
646 {
647 "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"
648 },
649 {"NAME": "weblate.accounts.password_validation.CharsPasswordValidator"},
650 ]
651 )
653# Password hashing (prefer Argon)
654PASSWORD_HASHERS = [
655 "django.contrib.auth.hashers.Argon2PasswordHasher",
656 "django.contrib.auth.hashers.PBKDF2PasswordHasher",
657 "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
658 "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
659]
661# Content-Security-Policy header
662CSP_SCRIPT_SRC = get_env_list("WEBLATE_CSP_SCRIPT_SRC")
663CSP_IMG_SRC = get_env_list("WEBLATE_CSP_IMG_SRC")
664CSP_CONNECT_SRC = get_env_list("WEBLATE_CSP_CONNECT_SRC")
665CSP_STYLE_SRC = get_env_list("WEBLATE_CSP_STYLE_SRC")
666CSP_FONT_SRC = get_env_list("WEBLATE_CSP_FONT_SRC")
667CSP_FORM_SRC = get_env_list("WEBLATE_CSP_FORM_SRC")
669# Allow new user registrations
670REGISTRATION_OPEN = get_env_bool("WEBLATE_REGISTRATION_OPEN", True)
671REGISTRATION_CAPTCHA = get_env_bool("WEBLATE_REGISTRATION_CAPTCHA", True)
672REGISTRATION_REBIND = get_env_bool("WEBLATE_REGISTRATION_REBIND", False)
673REGISTRATION_ALLOW_BACKENDS = get_env_list("WEBLATE_REGISTRATION_ALLOW_BACKENDS")
675# VCS configuration
676VCS_CLONE_DEPTH = get_env_int("WEBLATE_VCS_CLONE_DEPTH", 1)
677VCS_API_DELAY = get_env_int("WEBLATE_VCS_API_DELAY", 10)
678VCS_FILE_PROTOCOL = get_env_bool("WEBLATE_VCS_FILE_PROTOCOL", False)
680# Email registration filter
681REGISTRATION_EMAIL_MATCH = get_env_str("WEBLATE_REGISTRATION_EMAIL_MATCH", ".*")
683if "WEBLATE_PRIVATE_COMMIT_EMAIL_TEMPLATE" in os.environ: 683 ↛ 684line 683 didn't jump to line 684 because the condition on line 683 was never true
684 PRIVATE_COMMIT_EMAIL_TEMPLATE = get_env_str("WEBLATE_PRIVATE_COMMIT_EMAIL_TEMPLATE")
685PRIVATE_COMMIT_EMAIL_OPT_IN = get_env_bool("WEBLATE_PRIVATE_COMMIT_EMAIL_OPT_IN", True)
687# Shortcut for login required setting
688REQUIRE_LOGIN = get_env_bool("WEBLATE_REQUIRE_LOGIN")
690# Middleware
691MIDDLEWARE = [
692 "weblate.middleware.RedirectMiddleware",
693 "weblate.middleware.ProxyMiddleware",
694 "corsheaders.middleware.CorsMiddleware",
695 "django.middleware.security.SecurityMiddleware",
696 "django.contrib.sessions.middleware.SessionMiddleware",
697 "django.middleware.csrf.CsrfViewMiddleware",
698 "weblate.accounts.middleware.AuthenticationMiddleware",
699 "django.contrib.messages.middleware.MessageMiddleware",
700 "django.middleware.clickjacking.XFrameOptionsMiddleware",
701 "social_django.middleware.SocialAuthExceptionMiddleware",
702 "weblate.accounts.middleware.RequireLoginMiddleware",
703 "weblate.api.middleware.ThrottlingMiddleware",
704 "weblate.middleware.SecurityMiddleware",
705 "weblate.wladmin.middleware.ManageMiddleware",
706]
708# Rollbar integration
709ROLLBAR_KEY = get_env_str("ROLLBAR_KEY")
710if ROLLBAR_KEY: 710 ↛ 711line 710 didn't jump to line 711 because the condition on line 710 was never true
711 MIDDLEWARE.append("rollbar.contrib.django.middleware.RollbarNotifierMiddleware")
713 ROLLBAR = {
714 "access_token": ROLLBAR_KEY,
715 "environment": get_env_str("ROLLBAR_ENVIRONMENT", "production"),
716 "branch": "main",
717 "root": "/usr/local/lib/python3.9/dist-packages/weblate/",
718 "exception_level_filters": [
719 (PermissionDenied, "ignored"),
720 (Http404, "ignored"),
721 ],
722 }
724ROOT_URLCONF = "weblate.urls"
726# Django and Weblate apps
727INSTALLED_APPS = [
728 # Docker customization app, listed first to allow overriding static files
729 "customize",
730 # Weblate apps on top to override Django locales and templates
731 "weblate.addons",
732 "weblate.auth",
733 "weblate.checks",
734 "weblate.formats",
735 "weblate.glossary",
736 "weblate.machinery",
737 "weblate.trans",
738 "weblate.lang",
739 "weblate_language_data",
740 "weblate.memory",
741 "weblate.screenshots",
742 "weblate.fonts",
743 "weblate.accounts",
744 "weblate.configuration",
745 "weblate.utils",
746 "weblate.vcs",
747 "weblate.wladmin",
748 "weblate.metrics",
749 "weblate",
750 # Optional: Git exporter
751 "weblate.gitexport",
752 # Standard Django modules
753 "django.contrib.auth",
754 "django.contrib.contenttypes",
755 "django.contrib.sessions",
756 "django.contrib.messages",
757 "django.contrib.staticfiles",
758 "django.contrib.admin",
759 "django.contrib.sitemaps",
760 "django.contrib.humanize",
761 # Third party Django modules
762 "social_django",
763 "crispy_forms",
764 "crispy_bootstrap3",
765 "crispy_bootstrap5",
766 "compressor",
767 "rest_framework",
768 "rest_framework.authtoken",
769 "django_filters",
770 "django_celery_beat",
771 "corsheaders",
772 "django_otp",
773 "django_otp.plugins.otp_static",
774 "django_otp.plugins.otp_totp",
775 "django_otp_webauthn",
776 "drf_spectacular",
777 "drf_spectacular_sidecar",
778 "drf_standardized_errors",
779]
781# django_zxcvbn_password_validator integration
782if PASSWORD_MINIMAL_STRENGTH > 0: 782 ↛ 786line 782 didn't jump to line 786 because the condition on line 782 was always true
783 INSTALLED_APPS.append("django_zxcvbn_password_validator")
785# Legal integration
786LEGAL_INTEGRATION = get_env_str("WEBLATE_LEGAL_INTEGRATION")
787if LEGAL_INTEGRATION: 787 ↛ 789line 787 didn't jump to line 789 because the condition on line 787 was never true
788 # Hosted Weblate legal documents
789 if LEGAL_INTEGRATION == "wllegal":
790 INSTALLED_APPS.append("wllegal")
792 # Enable legal app
793 INSTALLED_APPS.append("weblate.legal")
795 # TOS confirmation enforcement
796 if LEGAL_INTEGRATION in {"tos-confirm", "wllegal"}:
797 # Social auth pipeline to confirm TOS upon registration/subsequent sign in
798 SOCIAL_AUTH_PIPELINE.insert(
799 SOCIAL_AUTH_PIPELINE.index(
800 "weblate.accounts.pipeline.second_factor",
801 )
802 + 1,
803 "weblate.legal.pipeline.tos_confirm",
804 )
805 # Middleware to enforce TOS confirmation of signed in users
806 MIDDLEWARE.append("weblate.legal.middleware.RequireTOSMiddleware")
809modify_env_list(INSTALLED_APPS, "APPS")
811# Custom exception reporter to include some details
812DEFAULT_EXCEPTION_REPORTER_FILTER = "weblate.trans.debug.WeblateExceptionReporterFilter"
814# Default logging of Weblate messages
815# - to syslog in production (if available)
816# - otherwise to console
817# - you can also choose "logfile" to log into separate file
818# after configuring it below
820# Syslog is not present inside Docker
821HAVE_SYSLOG = False
822DEFAULT_LOG = ["console" if DEBUG or not HAVE_SYSLOG else "syslog"]
823DEFAULT_LOGLEVEL = get_env_str("WEBLATE_LOGLEVEL", "DEBUG" if DEBUG else "INFO")
825# GELF TCP integration (Graylog)
826WEBLATE_LOG_GELF_HOST = get_env_str("WEBLATE_LOG_GELF_HOST", None)
828if WEBLATE_LOG_GELF_HOST: 828 ↛ 829line 828 didn't jump to line 829 because the condition on line 828 was never true
829 DEFAULT_LOG.append("gelf")
831# A sample logging configuration. The only tangible logging
832# performed by this configuration is to send an email to
833# the site admins on every HTTP 500 error when DEBUG=False.
834# See http://docs.djangoproject.com/en/stable/topics/logging for
835# more details on how to customize your logging configuration.
836LOGGING: dict = {
837 "version": 1,
838 "disable_existing_loggers": True,
839 "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
840 "formatters": {
841 "simple": {"format": "[%(asctime)s: %(levelname)s/%(process)s] %(message)s"},
842 "logfile": {"format": "%(asctime)s %(levelname)s %(message)s"},
843 "django.server": {
844 "()": "django.utils.log.ServerFormatter",
845 "format": "[%(server_time)s] %(message)s",
846 },
847 },
848 "handlers": {
849 "mail_admins": {
850 "level": "ERROR",
851 "filters": ["require_debug_false"],
852 "class": "django.utils.log.AdminEmailHandler",
853 "include_html": True,
854 },
855 "console": {
856 "level": "DEBUG",
857 "class": "logging.StreamHandler",
858 "formatter": "simple",
859 },
860 "django.server": {
861 "level": "INFO",
862 "class": "logging.StreamHandler",
863 "formatter": "django.server",
864 },
865 },
866 "loggers": {
867 "django.request": {
868 "handlers": [*DEFAULT_LOG],
869 "level": "ERROR",
870 "propagate": True,
871 },
872 "django.server": {
873 "handlers": ["django.server"],
874 "level": "INFO",
875 "propagate": False,
876 },
877 # Logging database queries
878 "django.db.backends": {
879 "handlers": [*DEFAULT_LOG],
880 # Toggle to DEBUG to log all database queries
881 "level": get_env_str("WEBLATE_LOGLEVEL_DATABASE", "CRITICAL"),
882 },
883 "weblate": {
884 "handlers": [*DEFAULT_LOG],
885 "level": DEFAULT_LOGLEVEL,
886 },
887 # Logging VCS operations
888 "weblate.vcs": {
889 "handlers": [*DEFAULT_LOG],
890 "level": DEFAULT_LOGLEVEL,
891 },
892 # Python Social Auth
893 "social": {
894 "handlers": [*DEFAULT_LOG],
895 "level": DEFAULT_LOGLEVEL,
896 },
897 # Django Authentication Using LDAP
898 "django_auth_ldap": {
899 "handlers": [*DEFAULT_LOG],
900 "level": DEFAULT_LOGLEVEL,
901 },
902 # SAML IdP
903 "djangosaml2idp": {
904 "handlers": [*DEFAULT_LOG],
905 "level": DEFAULT_LOGLEVEL,
906 },
907 },
908}
910# Configure syslog setup if it's present
911if HAVE_SYSLOG: 911 ↛ 912line 911 didn't jump to line 912 because the condition on line 911 was never true
912 LOGGING["formatters"]["syslog"] = {
913 "format": "weblate[%(process)d]: %(levelname)s %(message)s",
914 }
915 LOGGING["handlers"]["syslog"] = {
916 "level": "DEBUG",
917 "class": "logging.handlers.SysLogHandler",
918 "formatter": "syslog",
919 "address": "/dev/log",
920 "facility": SysLogHandler.LOG_LOCAL2,
921 }
923# Configure GELF integration if presetn
924if WEBLATE_LOG_GELF_HOST: 924 ↛ 925line 924 didn't jump to line 925 because the condition on line 924 was never true
925 LOGGING["formatters"]["gelf"] = {
926 "()": "logging_gelf.formatters.GELFFormatter",
927 "null_character": True,
928 }
929 LOGGING["handlers"]["gelf"] = {
930 "level": "DEBUG",
931 "class": "logging_gelf.handlers.GELFTCPSocketHandler",
932 "formatter": "gelf",
933 "host": WEBLATE_LOG_GELF_HOST,
934 "port": get_env_int("WEBLATE_LOG_GELF_PORT", 12201),
935 }
937if get_env_bool("WEBLATE_ADMIN_NOTIFY_ERROR", True): 937 ↛ 943line 937 didn't jump to line 943 because the condition on line 937 was always true
938 LOGGING["loggers"]["django.request"]["handlers"].append("mail_admins")
940# Use HTTPS when creating redirect URLs for social authentication, see
941# documentation for more details:
942# https://python-social-auth-docs.readthedocs.io/en/latest/configuration/settings.html#processing-redirects-and-urlopen
943SOCIAL_AUTH_REDIRECT_IS_HTTPS = ENABLE_HTTPS
945# Make CSRF cookie HttpOnly, see documentation for more details:
946# https://docs.djangoproject.com/en/1.11/ref/settings/#csrf-cookie-httponly
947CSRF_COOKIE_HTTPONLY = True
948CSRF_COOKIE_SECURE = ENABLE_HTTPS
949# Store CSRF token in session
950CSRF_USE_SESSIONS = True
951# Customize CSRF failure view
952CSRF_FAILURE_VIEW = "weblate.trans.views.error.csrf_failure"
953SESSION_COOKIE_SECURE = ENABLE_HTTPS
954SESSION_COOKIE_HTTPONLY = True
955# SSL redirect
956SECURE_SSL_REDIRECT = ENABLE_HTTPS
957SECURE_SSL_HOST = SITE_DOMAIN
958# Sent referrer only for same origin links
959SECURE_REFERRER_POLICY = "same-origin"
960# SSL redirect URL exemption list
961SECURE_REDIRECT_EXEMPT = (r"healthz/$",) # Allowing HTTP access to health check
962# Session cookie age (in seconds)
963SESSION_COOKIE_AGE = 1000
964SESSION_COOKIE_AGE_AUTHENTICATED = 1209600
965SESSION_COOKIE_SAMESITE = "Lax"
966# Increase allowed upload size
967DATA_UPLOAD_MAX_MEMORY_SIZE = 50000000
968# Allow more fields for case with a lot of subscriptions in profile
969DATA_UPLOAD_MAX_NUMBER_FIELDS = 2000
971# Apply session coookie settings to language cookie as well with exception
972# of SameSite as we want language to be honored in CSRF error messages.
973LANGUAGE_COOKIE_SECURE = SESSION_COOKIE_SECURE
974LANGUAGE_COOKIE_HTTPONLY = SESSION_COOKIE_HTTPONLY
975LANGUAGE_COOKIE_AGE = SESSION_COOKIE_AGE_AUTHENTICATED * 10
976LANGUAGE_COOKIE_SAMESITE = "None"
978# Some security headers
979SECURE_BROWSER_XSS_FILTER = True
980X_FRAME_OPTIONS = "DENY"
981SECURE_CONTENT_TYPE_NOSNIFF = True
983# Optionally enable HSTS
984SECURE_HSTS_SECONDS = 31536000 if ENABLE_HTTPS else 0
985SECURE_HSTS_PRELOAD = ENABLE_HTTPS
986SECURE_HSTS_INCLUDE_SUBDOMAINS = ENABLE_HTTPS
988# HTTPS detection behind reverse proxy
989WEBLATE_SECURE_PROXY_SSL_HEADER = get_env_list("WEBLATE_SECURE_PROXY_SSL_HEADER")
990if WEBLATE_SECURE_PROXY_SSL_HEADER: 990 ↛ 991line 990 didn't jump to line 991 because the condition on line 990 was never true
991 SECURE_PROXY_SSL_HEADER = WEBLATE_SECURE_PROXY_SSL_HEADER
993# URL of login
994LOGIN_URL = f"{URL_PREFIX}/accounts/login/"
996# URL of logout
997LOGOUT_URL = f"{URL_PREFIX}/accounts/logout/"
999# Default location for login
1000LOGIN_REDIRECT_URL = f"{URL_PREFIX}/"
1002# Opt-in for Django 6.0 default
1003FORMS_URLFIELD_ASSUME_HTTPS = True
1005# Anonymous user name
1006ANONYMOUS_USER_NAME = "anonymous"
1008# Reverse proxy settings
1009IP_PROXY_HEADER = get_env_str("WEBLATE_IP_PROXY_HEADER")
1010IP_BEHIND_REVERSE_PROXY = bool(IP_PROXY_HEADER)
1011IP_PROXY_OFFSET = get_env_int("WEBLATE_IP_PROXY_OFFSET", -1)
1013# Sending HTML in mails
1014EMAIL_SEND_HTML = True
1016# Subject of emails includes site title
1017EMAIL_SUBJECT_PREFIX = f"[{SITE_TITLE}] "
1019# Enable remote hooks
1020ENABLE_HOOKS = get_env_bool("WEBLATE_ENABLE_HOOKS", True)
1022# Version hiding
1023HIDE_VERSION = get_env_bool("WEBLATE_HIDE_VERSION")
1025# Licensing filter
1026if "WEBLATE_LICENSE_FILTER" in os.environ: 1026 ↛ 1027line 1026 didn't jump to line 1027 because the condition on line 1026 was never true
1027 LICENSE_FILTER = set(get_env_list("WEBLATE_LICENSE_FILTER"))
1028 LICENSE_FILTER.discard("")
1030LICENSE_REQUIRED = get_env_bool("WEBLATE_LICENSE_REQUIRED")
1031WEBSITE_REQUIRED = get_env_bool("WEBLATE_WEBSITE_REQUIRED", True)
1033# Language filter
1034if "WEBLATE_BASIC_LANGUAGES" in os.environ: 1034 ↛ 1035line 1034 didn't jump to line 1035 because the condition on line 1034 was never true
1035 BASIC_LANGUAGES = set(get_env_list("WEBLATE_BASIC_LANGUAGES"))
1037# By default the length of a given translation is limited to the length of
1038# the source string * 10 characters. Set this option to False to allow longer
1039# translations (up to 10.000 characters)
1040LIMIT_TRANSLATION_LENGTH_BY_SOURCE_LENGTH = get_env_bool(
1041 "WEBLATE_LIMIT_TRANSLATION_LENGTH_BY_SOURCE_LENGTH", True
1042)
1044# Use simple language codes for default language/country combinations
1045SIMPLIFY_LANGUAGES = get_env_bool("WEBLATE_SIMPLIFY_LANGUAGES", True)
1047# Default number of elements to display when pagination is active
1048DEFAULT_PAGE_LIMIT = get_env_int("WEBLATE_DEFAULT_PAGE_LIMIT", 100)
1050# Render forms using bootstrap
1051CRISPY_ALLOWED_TEMPLATE_PACKS = ["bootstrap3", "bootstrap5"]
1052CRISPY_TEMPLATE_PACK = "bootstrap3"
1054# List of quality checks
1055CHECK_LIST = [
1056 "weblate.checks.same.SameCheck",
1057 "weblate.checks.chars.BeginNewlineCheck",
1058 "weblate.checks.chars.EndNewlineCheck",
1059 "weblate.checks.chars.BeginSpaceCheck",
1060 "weblate.checks.chars.EndSpaceCheck",
1061 "weblate.checks.chars.DoubleSpaceCheck",
1062 "weblate.checks.chars.EndStopCheck",
1063 "weblate.checks.chars.EndColonCheck",
1064 "weblate.checks.chars.EndQuestionCheck",
1065 "weblate.checks.chars.EndExclamationCheck",
1066 "weblate.checks.chars.EndInterrobangCheck",
1067 "weblate.checks.chars.EndEllipsisCheck",
1068 "weblate.checks.chars.EndSemicolonCheck",
1069 "weblate.checks.chars.MaxLengthCheck",
1070 "weblate.checks.chars.KashidaCheck",
1071 "weblate.checks.chars.PunctuationSpacingCheck",
1072 "weblate.checks.chars.KabyleCharactersCheck",
1073 "weblate.checks.format.PythonFormatCheck",
1074 "weblate.checks.format.PythonBraceFormatCheck",
1075 "weblate.checks.format.PHPFormatCheck",
1076 "weblate.checks.format.CFormatCheck",
1077 "weblate.checks.format.PerlFormatCheck",
1078 "weblate.checks.format.PerlBraceFormatCheck",
1079 "weblate.checks.format.JavaScriptFormatCheck",
1080 "weblate.checks.format.LuaFormatCheck",
1081 "weblate.checks.format.ObjectPascalFormatCheck",
1082 "weblate.checks.format.SchemeFormatCheck",
1083 "weblate.checks.format.CSharpFormatCheck",
1084 "weblate.checks.format.JavaFormatCheck",
1085 "weblate.checks.format.JavaMessageFormatCheck",
1086 "weblate.checks.format.PercentPlaceholdersCheck",
1087 "weblate.checks.format.VueFormattingCheck",
1088 "weblate.checks.format.I18NextInterpolationCheck",
1089 "weblate.checks.format.ESTemplateLiteralsCheck",
1090 "weblate.checks.format.AutomatticComponentsCheck",
1091 "weblate.checks.angularjs.AngularJSInterpolationCheck",
1092 "weblate.checks.icu.ICUMessageFormatCheck",
1093 "weblate.checks.icu.ICUSourceCheck",
1094 "weblate.checks.qt.QtFormatCheck",
1095 "weblate.checks.qt.QtPluralCheck",
1096 "weblate.checks.ruby.RubyFormatCheck",
1097 "weblate.checks.consistency.PluralsCheck",
1098 "weblate.checks.consistency.SamePluralsCheck",
1099 "weblate.checks.consistency.ConsistencyCheck",
1100 "weblate.checks.consistency.ReusedCheck",
1101 "weblate.checks.consistency.TranslatedCheck",
1102 "weblate.checks.chars.EscapedNewlineCountingCheck",
1103 "weblate.checks.chars.NewLineCountCheck",
1104 "weblate.checks.markup.BBCodeCheck",
1105 "weblate.checks.chars.ZeroWidthSpaceCheck",
1106 "weblate.checks.render.MaxSizeCheck",
1107 "weblate.checks.markup.XMLValidityCheck",
1108 "weblate.checks.markup.XMLTagsCheck",
1109 "weblate.checks.markup.MarkdownRefLinkCheck",
1110 "weblate.checks.markup.MarkdownLinkCheck",
1111 "weblate.checks.markup.MarkdownSyntaxCheck",
1112 "weblate.checks.markup.URLCheck",
1113 "weblate.checks.markup.SafeHTMLCheck",
1114 "weblate.checks.markup.RSTReferencesCheck",
1115 "weblate.checks.markup.RSTSyntaxCheck",
1116 "weblate.checks.placeholders.PlaceholderCheck",
1117 "weblate.checks.placeholders.RegexCheck",
1118 "weblate.checks.duplicate.DuplicateCheck",
1119 "weblate.checks.source.OptionalPluralCheck",
1120 "weblate.checks.source.EllipsisCheck",
1121 "weblate.checks.source.MultipleFailingCheck",
1122 "weblate.checks.source.LongUntranslatedCheck",
1123 "weblate.checks.format.MultipleUnnamedFormatsCheck",
1124 "weblate.checks.glossary.GlossaryCheck",
1125 "weblate.checks.glossary.ProhibitedInitialCharacterCheck",
1126 "weblate.checks.fluent.syntax.FluentSourceSyntaxCheck",
1127 "weblate.checks.fluent.syntax.FluentTargetSyntaxCheck",
1128 "weblate.checks.fluent.parts.FluentPartsCheck",
1129 "weblate.checks.fluent.references.FluentReferencesCheck",
1130 "weblate.checks.fluent.inner_html.FluentSourceInnerHTMLCheck",
1131 "weblate.checks.fluent.inner_html.FluentTargetInnerHTMLCheck",
1132]
1133modify_env_list(CHECK_LIST, "CHECK")
1135# List of automatic fixups
1136AUTOFIX_LIST = [
1137 "weblate.trans.autofixes.whitespace.SameBookendingWhitespace",
1138 "weblate.trans.autofixes.chars.ReplaceTrailingDotsWithEllipsis",
1139 "weblate.trans.autofixes.chars.RemoveZeroSpace",
1140 "weblate.trans.autofixes.chars.RemoveControlChars",
1141 "weblate.trans.autofixes.chars.DevanagariDanda",
1142 "weblate.trans.autofixes.html.BleachHTML",
1143]
1144modify_env_list(AUTOFIX_LIST, "AUTOFIX")
1146# List of enabled addons
1147WEBLATE_ADDONS = [
1148 "weblate.addons.gettext.GenerateMoAddon",
1149 "weblate.addons.gettext.UpdateLinguasAddon",
1150 "weblate.addons.gettext.UpdateConfigureAddon",
1151 "weblate.addons.gettext.MsgmergeAddon",
1152 "weblate.addons.gettext.GettextAuthorComments",
1153 "weblate.addons.cleanup.CleanupAddon",
1154 "weblate.addons.cleanup.RemoveBlankAddon",
1155 "weblate.addons.consistency.LanguageConsistencyAddon",
1156 "weblate.addons.discovery.DiscoveryAddon",
1157 "weblate.addons.autotranslate.AutoTranslateAddon",
1158 "weblate.addons.flags.SourceEditAddon",
1159 "weblate.addons.flags.TargetEditAddon",
1160 "weblate.addons.flags.SameEditAddon",
1161 "weblate.addons.flags.BulkEditAddon",
1162 "weblate.addons.flags.TargetRepoUpdateAddon",
1163 "weblate.addons.generate.GenerateFileAddon",
1164 "weblate.addons.generate.PseudolocaleAddon",
1165 "weblate.addons.generate.PrefillAddon",
1166 "weblate.addons.generate.FillReadOnlyAddon",
1167 "weblate.addons.properties.PropertiesSortAddon",
1168 "weblate.addons.git.GitSquashAddon",
1169 "weblate.addons.removal.RemoveComments",
1170 "weblate.addons.removal.RemoveSuggestions",
1171 "weblate.addons.resx.ResxUpdateAddon",
1172 "weblate.addons.cdn.CDNJSAddon",
1173 "weblate.addons.webhooks.WebhookAddon",
1174 "weblate.addons.webhooks.SlackWebhookAddon",
1175]
1176modify_env_list(WEBLATE_ADDONS, "ADDONS")
1178# Machinery configuration
1179WEBLATE_MACHINERY = [
1180 "weblate.machinery.apertium.ApertiumAPYTranslation",
1181 "weblate.machinery.aws.AWSTranslation",
1182 "weblate.machinery.alibaba.AlibabaTranslation",
1183 "weblate.machinery.baidu.BaiduTranslation",
1184 "weblate.machinery.deepl.DeepLTranslation",
1185 "weblate.machinery.glosbe.GlosbeTranslation",
1186 "weblate.machinery.google.GoogleTranslation",
1187 "weblate.machinery.googlev3.GoogleV3Translation",
1188 "weblate.machinery.libretranslate.LibreTranslateTranslation",
1189 "weblate.machinery.microsoft.MicrosoftCognitiveTranslation",
1190 "weblate.machinery.modernmt.ModernMTTranslation",
1191 "weblate.machinery.mymemory.MyMemoryTranslation",
1192 "weblate.machinery.netease.NeteaseSightTranslation",
1193 "weblate.machinery.tmserver.TMServerTranslation",
1194 "weblate.machinery.yandex.YandexTranslation",
1195 "weblate.machinery.yandexv2.YandexV2Translation",
1196 "weblate.machinery.saptranslationhub.SAPTranslationHub",
1197 "weblate.machinery.youdao.YoudaoTranslation",
1198 "weblate.machinery.systran.SystranTranslation",
1199 "weblate.machinery.openai.OpenAITranslation",
1200 "weblate.machinery.openai.AzureOpenAITranslation",
1201 "weblate.machinery.weblatetm.WeblateTranslation",
1202 "weblate.memory.machine.WeblateMemory",
1203 "weblate.machinery.cyrtranslit.CyrTranslitTranslation",
1204]
1205modify_env_list(WEBLATE_MACHINERY, "MACHINERY")
1208# E-mail address that error messages come from.
1209SERVER_EMAIL = get_env_str("WEBLATE_SERVER_EMAIL", "weblate@example.com")
1211# Default email address to use for various automated correspondence from
1212# the site managers. Used for registration emails.
1213DEFAULT_FROM_EMAIL = get_env_str("WEBLATE_DEFAULT_FROM_EMAIL", SERVER_EMAIL)
1215# List of URLs your site is supposed to serve
1216ALLOWED_HOSTS = get_env_list("WEBLATE_ALLOWED_HOSTS", ["*"])
1218# Extract redis URL
1219REDIS_URL = get_env_redis_url()
1221# Configuration for caching
1222CACHES = {
1223 "default": {
1224 "BACKEND": "django_redis.cache.RedisCache",
1225 "LOCATION": REDIS_URL,
1226 # If redis is running on same host as Weblate, you might
1227 # want to use unix sockets instead:
1228 "OPTIONS": {
1229 "CLIENT_CLASS": "django_redis.client.DefaultClient",
1230 "CONNECTION_POOL_KWARGS": {},
1231 },
1232 "KEY_PREFIX": "weblate",
1233 "TIMEOUT": 3600,
1234 },
1235 "avatar": {
1236 "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
1237 "LOCATION": os.path.join(CACHE_DIR, "avatar"),
1238 "TIMEOUT": 86400,
1239 "OPTIONS": {"MAX_ENTRIES": 1000},
1240 },
1241}
1242if not get_env_bool("REDIS_VERIFY_SSL", True) and REDIS_URL.startswith("rediss://"): 1242 ↛ 1243line 1242 didn't jump to line 1243 because the condition on line 1242 was never true
1243 CACHES["default"]["OPTIONS"]["CONNECTION_POOL_KWARGS"]["ssl_cert_reqs"] = None # type: ignore[index]
1246# Store sessions in cache
1247SESSION_ENGINE = os.environ.get(
1248 "WEBLATE_SESSION_ENGINE", "django.contrib.sessions.backends.cache"
1249)
1250# Store messages in session
1251MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"
1253# REST framework settings for API
1254REST_FRAMEWORK = get_drf_settings(
1255 require_login=REQUIRE_LOGIN,
1256 anon_throttle=get_env_ratelimit("WEBLATE_API_RATELIMIT_ANON", "100/day"),
1257 user_throttle=get_env_ratelimit("WEBLATE_API_RATELIMIT_USER", "5000/hour"),
1258)
1259DRF_STANDARDIZED_ERRORS = get_drf_standardized_errors_settings()
1260SPECTACULAR_SETTINGS = get_spectacular_settings(INSTALLED_APPS, SITE_URL, SITE_TITLE)
1262# Fonts CDN URL
1263FONTS_CDN_URL = None
1265# Django compressor offline mode
1266COMPRESS_OFFLINE = True
1267COMPRESS_OFFLINE_CONTEXT = "weblate.utils.compress.offline_context"
1268COMPRESS_CSS_HASHING_METHOD = "content"
1270# Require login for all URLs
1271if REQUIRE_LOGIN: 1271 ↛ 1272line 1271 didn't jump to line 1272 because the condition on line 1271 was never true
1272 LOGIN_REQUIRED_URLS = (r"/(.*)$",)
1274# In such case you will want to include some of the exceptions
1275LOGIN_REQUIRED_URLS_EXCEPTIONS = get_env_list(
1276 "WEBLATE_LOGIN_REQUIRED_URLS_EXCEPTIONS",
1277 [
1278 rf"{URL_PREFIX}/accounts/(.*)$", # Required for login
1279 rf"{URL_PREFIX}/admin/login/(.*)$", # Required for admin login
1280 rf"{URL_PREFIX}/static/(.*)$", # Required for development mode
1281 rf"{URL_PREFIX}/widget/(.*)$", # Allowing public access to widgets
1282 rf"{URL_PREFIX}/data/(.*)$", # Allowing public access to data exports
1283 rf"{URL_PREFIX}/hooks/(.*)$", # Allowing public access to notification hooks
1284 rf"{URL_PREFIX}/healthz/$", # Allowing public access to health check
1285 rf"{URL_PREFIX}/api/(.*)$", # Allowing access to API
1286 rf"{URL_PREFIX}/js/i18n/$", # JavaScript localization
1287 rf"{URL_PREFIX}/css/custom\.css$", # Custom CSS support
1288 rf"{URL_PREFIX}/contact/$", # Optional for contact form
1289 rf"{URL_PREFIX}/legal/(.*)$", # Optional for legal app
1290 rf"{URL_PREFIX}/avatar/(.*)$", # Optional for avatars
1291 rf"{URL_PREFIX}/site.webmanifest$", # The request for the manifest is made without credentials
1292 ],
1293)
1294modify_env_list(LOGIN_REQUIRED_URLS_EXCEPTIONS, "LOGIN_REQUIRED_URLS_EXCEPTIONS")
1296# Email server
1297EMAIL_HOST = get_env_str("WEBLATE_EMAIL_HOST", "localhost", required=True)
1298EMAIL_HOST_USER = get_env_str(
1299 "WEBLATE_EMAIL_HOST_USER", get_env_str("WEBLATE_EMAIL_USER")
1300)
1301EMAIL_HOST_PASSWORD = get_env_str(
1302 "WEBLATE_EMAIL_HOST_PASSWORD", get_env_str("WEBLATE_EMAIL_PASSWORD")
1303)
1304DEFAULT_EMAIL_PORT = 25
1305if "WEBLATE_EMAIL_USE_TLS" in os.environ: 1305 ↛ 1306line 1305 didn't jump to line 1306 because the condition on line 1305 was never true
1306 DEFAULT_EMAIL_PORT = 587
1307elif "WEBLATE_EMAIL_USE_SSL" in os.environ: 1307 ↛ 1308line 1307 didn't jump to line 1308 because the condition on line 1307 was never true
1308 DEFAULT_EMAIL_PORT = 465
1309EMAIL_PORT = get_env_int("WEBLATE_EMAIL_PORT", DEFAULT_EMAIL_PORT)
1311# Detect SSL/TLS setup
1312if "WEBLATE_EMAIL_USE_TLS" in os.environ or "WEBLATE_EMAIL_USE_SSL" in os.environ: 1312 ↛ 1313line 1312 didn't jump to line 1313 because the condition on line 1312 was never true
1313 EMAIL_USE_SSL = get_env_bool("WEBLATE_EMAIL_USE_SSL")
1314 EMAIL_USE_TLS = get_env_bool("WEBLATE_EMAIL_USE_TLS", not EMAIL_USE_SSL)
1315elif EMAIL_PORT in {25, 587}: 1315 ↛ 1317line 1315 didn't jump to line 1317 because the condition on line 1315 was always true
1316 EMAIL_USE_TLS = True
1317elif EMAIL_PORT == 465:
1318 EMAIL_USE_SSL = True
1320EMAIL_BACKEND = get_env_str(
1321 "WEBLATE_EMAIL_BACKEND",
1322 "django.core.mail.backends.smtp.EmailBackend",
1323 required=True,
1324)
1326# Silence some of the Django system checks
1327SILENCED_SYSTEM_CHECKS = [
1328 # We have modified django.contrib.auth.middleware.AuthenticationMiddleware
1329 # as weblate.accounts.middleware.AuthenticationMiddleware
1330 "admin.E408",
1331 # Silence drf_spectacular until these are addressed
1332 "drf_spectacular.W001",
1333 "drf_spectacular.W002",
1334]
1336# Silence WebAuthn origin error
1337if not ENABLE_HTTPS: 1337 ↛ 1340line 1337 didn't jump to line 1340 because the condition on line 1337 was always true
1338 SILENCED_SYSTEM_CHECKS.append("otp_webauthn.E031")
1340SILENCED_SYSTEM_CHECKS.extend(get_env_list("WEBLATE_SILENCED_SYSTEM_CHECKS"))
1342# Celery worker configuration for production
1343CELERY_TASK_ALWAYS_EAGER = get_env_bool("WEBLATE_CELERY_EAGER")
1344CELERY_BROKER_URL = REDIS_URL
1345if REDIS_URL.startswith("rediss://"): 1345 ↛ 1346line 1345 didn't jump to line 1346 because the condition on line 1345 was never true
1346 CELERY_BROKER_URL = "{}?ssl_cert_reqs={}".format(
1347 CELERY_BROKER_URL,
1348 "CERT_REQUIRED" if get_env_bool("REDIS_VERIFY_SSL", True) else "CERT_NONE",
1349 )
1350CELERY_RESULT_BACKEND = CELERY_BROKER_URL
1351CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
1352CELERY_BROKER_CONNECTION_RETRY = True
1354# Celery settings, it is not recommended to change these
1355CELERY_WORKER_MAX_MEMORY_PER_CHILD = 450000 if DEBUG else 250000
1356CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
1357CELERY_TASK_ROUTES = {
1358 "weblate.trans.tasks.auto_translate*": {"queue": "translate"},
1359 "weblate.accounts.tasks.notify_*": {"queue": "notify"},
1360 "weblate.accounts.tasks.send_mails": {"queue": "notify"},
1361 "weblate.addons.tasks.addon_change": {"queue": "notify"},
1362 "weblate.utils.tasks.settings_backup": {"queue": "backup"},
1363 "weblate.utils.tasks.database_backup": {"queue": "backup"},
1364 "weblate.wladmin.tasks.backup": {"queue": "backup"},
1365 "weblate.wladmin.tasks.backup_service": {"queue": "backup"},
1366 "weblate.memory.tasks.*": {"queue": "memory"},
1367}
1369# CORS allowed origins
1370CORS_ALLOWED_ORIGINS = get_env_list("WEBLATE_CORS_ALLOWED_ORIGINS")
1371CORS_ALLOW_ALL_ORIGINS = get_env_bool("WEBLATE_CORS_ALLOW_ALL_ORIGINS", False)
1372CORS_URLS_REGEX = rf"^{URL_PREFIX}/api/.*$"
1374# Database backup type
1375DATABASE_BACKUP = get_env_str("WEBLATE_DATABASE_BACKUP", "plain")
1377# Enable auto updating
1378AUTO_UPDATE = get_env_bool("WEBLATE_AUTO_UPDATE")
1380# Update languages on migration
1381UPDATE_LANGUAGES = get_env_bool("WEBLATE_UPDATE_LANGUAGES", True)
1383# Avatars
1384ENABLE_AVATARS = get_env_bool("WEBLATE_ENABLE_AVATARS", True)
1385AVATAR_URL_PREFIX = get_env_str(
1386 "WEBLATE_AVATAR_URL_PREFIX", "https://www.gravatar.com/", required=ENABLE_AVATARS
1387)
1389# Default access control
1390DEFAULT_ACCESS_CONTROL = get_env_int("WEBLATE_DEFAULT_ACCESS_CONTROL")
1392# Default access control
1393DEFAULT_RESTRICTED_COMPONENT = get_env_bool("WEBLATE_DEFAULT_RESTRICTED_COMPONENT")
1395# Default translation propagation
1396DEFAULT_TRANSLATION_PROPAGATION = get_env_bool(
1397 "WEBLATE_DEFAULT_TRANSLATION_PROPAGATION", True
1398)
1400DEFAULT_COMMITER_EMAIL = get_env_str(
1401 "WEBLATE_DEFAULT_COMMITER_EMAIL", "noreply@weblate.org", required=True
1402)
1403DEFAULT_COMMITER_NAME = get_env_str(
1404 "WEBLATE_DEFAULT_COMMITER_NAME", "Weblate", required=True
1405)
1407DEFAULT_AUTO_WATCH = get_env_bool("WEBLATE_DEFAULT_AUTO_WATCH", True)
1409DEFAULT_SHARED_TM = get_env_bool("WEBLATE_DEFAULT_SHARED_TM", True)
1411DEFAULT_AUTOCLEAN_TM = get_env_bool("WEBLATE_AUTOCLEAN_TM", False)
1413CONTACT_FORM = get_env_str("WEBLATE_CONTACT_FORM", "reply-to", required=True)
1414ADMINS_CONTACT = get_env_list("WEBLATE_ADMINS_CONTACT")
1416SSH_EXTRA_ARGS = get_env_str("WEBLATE_SSH_EXTRA_ARGS", "")
1418BORG_EXTRA_ARGS = get_env_list("WEBLATE_BORG_EXTRA_ARGS")
1420ENABLE_SHARING = get_env_bool("WEBLATE_ENABLE_SHARING")
1422SUPPORT_STATUS_CHECK = get_env_bool("WEBLATE_SUPPORT_STATUS_CHECK")
1424EXTRA_HTML_HEAD = get_env_str("WEBLATE_EXTRA_HTML_HEAD", "")
1426UNUSED_ALERT_DAYS = get_env_int("WEBLATE_UNUSED_ALERT_DAYS", 365)
1428USE_X_FORWARDED_HOST = get_env_bool("WEBLATE_USE_X_FORWARDED_HOST", False)
1430# Wildcard loading
1431for name in os.environ:
1432 if name.startswith("WEBLATE_RATELIMIT_") and name.endswith(
1433 ("_ATTEMPTS", "_WINDOW", "_LOCKOUT")
1434 ):
1435 locals()[name[8:]] = get_env_int(name)
1437# PGP commits signing
1438WEBLATE_GPG_IDENTITY = get_env_str("WEBLATE_GPG_IDENTITY")
1440# Localize CDN addon
1441LOCALIZE_CDN_URL = get_env_str("WEBLATE_LOCALIZE_CDN_URL")
1442LOCALIZE_CDN_PATH = get_env_str("WEBLATE_LOCALIZE_CDN_PATH")
1444# Integration links
1445GET_HELP_URL = get_env_str("WEBLATE_GET_HELP_URL")
1446STATUS_URL = get_env_str("WEBLATE_STATUS_URL")
1447LEGAL_URL = get_env_str("WEBLATE_LEGAL_URL")
1448PRIVACY_URL = get_env_str("WEBLATE_PRIVACY_URL")
1450# Third party services integration
1451MATOMO_SITE_ID = get_env_str("WEBLATE_MATOMO_SITE_ID")
1452MATOMO_URL = get_env_str("WEBLATE_MATOMO_URL")
1453GOOGLE_ANALYTICS_ID = get_env_str("WEBLATE_GOOGLE_ANALYTICS_ID")
1454SENTRY_DSN = get_env_str("SENTRY_DSN")
1455SENTRY_ENVIRONMENT = get_env_str("SENTRY_ENVIRONMENT", SITE_DOMAIN)
1456SENTRY_MONITOR_BEAT_TASKS = get_env_bool("SENTRY_MONITOR_BEAT_TASKS", True)
1457SENTRY_TRACES_SAMPLE_RATE = get_env_float("SENTRY_TRACES_SAMPLE_RATE")
1458SENTRY_PROFILES_SAMPLE_RATE = get_env_float("SENTRY_PROFILES_SAMPLE_RATE", 1.0)
1459SENTRY_TOKEN = get_env_str("SENTRY_TOKEN")
1460SENTRY_SEND_PII = get_env_bool("SENTRY_SEND_PII", False)
1461ZAMMAD_URL = get_env_str("WEBLATE_ZAMMAD_URL")
1463# Web Monetization
1464INTERLEDGER_PAYMENT_POINTERS = get_env_list("WEBLATE_INTERLEDGER_PAYMENT_POINTERS", [])
1465INTERLEDGER_PAYMENT_BUILTIN = get_env_bool("WEBLATE_INTERLEDGER_PAYMENT_BUILTIN", True)
1467ADDITIONAL_CONFIG = "/app/data/settings-override.py"
1468if os.path.exists(ADDITIONAL_CONFIG): 1468 ↛ 1469line 1468 didn't jump to line 1469 because the condition on line 1468 was never true
1469 with open(ADDITIONAL_CONFIG) as handle:
1470 code = compile(handle.read(), ADDITIONAL_CONFIG, "exec")
1471 exec(code) # noqa: S102