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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5import os 

6from logging.handlers import SysLogHandler 

7 

8from django.core.exceptions import PermissionDenied 

9from django.http import Http404 

10 

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) 

29 

30# Title of site to use 

31SITE_TITLE = get_env_str("WEBLATE_SITE_TITLE", "Weblate") 

32 

33# Site domain 

34SITE_DOMAIN = get_env_str("WEBLATE_SITE_DOMAIN", required=True) 

35 

36# Whether site uses https 

37ENABLE_HTTPS = get_env_bool("WEBLATE_ENABLE_HTTPS") 

38 

39# Site URL 

40SITE_URL = "{}://{}".format("https" if ENABLE_HTTPS else "http", SITE_DOMAIN) 

41 

42# 

43# Django settings for Weblate project. 

44# 

45 

46DEBUG = get_env_bool("WEBLATE_DEBUG", False) 

47 

48ADMINS = ( 

49 ( 

50 get_env_str("WEBLATE_ADMIN_NAME", "Weblate Admin"), 

51 get_env_str("WEBLATE_ADMIN_EMAIL", "weblate@example.com"), 

52 ), 

53) 

54 

55MANAGERS = ADMINS 

56 

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 } 

92 

93# Data directory 

94DATA_DIR = get_env_str("WEBLATE_DATA_DIR", "/app/data") 

95CACHE_DIR = get_env_str("WEBLATE_CACHE_DIR", "/app/cache") 

96 

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") 

102 

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" 

106 

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) 

158 

159SITE_ID = 1 

160 

161# If you set this to False, Django will make some optimizations so as not 

162# to load the internationalization machinery. 

163USE_I18N = True 

164 

165# If you set this to False, Django will not use timezone-aware datetimes. 

166USE_TZ = True 

167 

168# Type of automatic primary key, introduced in Django 3.2 

169DEFAULT_AUTO_FIELD = "django.db.models.AutoField" 

170 

171# URL prefix to use, please see documentation for more details 

172URL_PREFIX = get_env_str("WEBLATE_URL_PREFIX", "") 

173 

174# Absolute filesystem path to the directory that will hold user-uploaded files. 

175MEDIA_ROOT = os.path.join(DATA_DIR, "media") 

176 

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/") 

180 

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") 

185 

186# URL prefix for static files. 

187STATIC_URL = get_env_str("WEBLATE_STATIC_URL", f"{URL_PREFIX}/static/") 

188 

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) 

195 

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) 

203 

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() 

208 

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] 

226 

227 

228# GitHub username and token for sending pull requests. 

229# Please see the documentation for more details. 

230GITHUB_CREDENTIALS = get_env_credentials("GITHUB") 

231 

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") 

235 

236# GitLab username and token for sending merge requests. 

237# Please see the documentation for more details. 

238GITLAB_CREDENTIALS = get_env_credentials("GITLAB") 

239 

240# Gitea username and token for sending pull requests. 

241# Please see the documentation for more details. 

242GITEA_CREDENTIALS = get_env_credentials("GITEA") 

243 

244# Pagure username and token for sending merge requests. 

245# Please see the documentation for more details. 

246PAGURE_CREDENTIALS = get_env_credentials("PAGURE") 

247 

248# Bitbucket username and token for sending merge requests. 

249# Please see the documentation for more details. 

250BITBUCKETSERVER_CREDENTIALS = get_env_credentials("BITBUCKETSERVER") 

251 

252# Bitbucket username and token for sending merge requests. 

253# Please see the documentation for more details. 

254BITBUCKETCLOUD_CREDENTIALS = get_env_credentials("BITBUCKETCLOUD") 

255 

256 

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") 

261 

262# Authentication configuration 

263AUTHENTICATION_BACKENDS: tuple[str, ...] = () 

264 

265# Custom user model 

266AUTH_USER_MODEL = "weblate_auth.User" 

267 

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" 

274 

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",) 

277 

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",) 

286 

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",) 

300 

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",) 

314 

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 ) 

335 

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",) 

345 

346 

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",) 

355 

356 

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",) 

369 

370 

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",) 

377 

378 

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",) 

387 

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",) 

404 

405 

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") 

431 

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",) 

439 

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 ) 

454 

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",) 

477 

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",) 

488 

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",) 

496 

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 ] 

513 

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",) 

523 

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 

529 

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") 

537 

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 ) 

544 

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 ) 

549 

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 ] 

556 

557 AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*SEARCH_UNION) 

558 

559 if not get_env_bool("WEBLATE_AUTH_LDAP_CONNECTION_OPTION_REFERRALS", True): 

560 AUTH_LDAP_CONNECTION_OPTIONS = { 

561 ldap.OPT_REFERRALS: 0, 

562 } 

563 

564# Always include Weblate backend 

565AUTHENTICATION_BACKENDS += ("weblate.accounts.auth.WeblateUserBackend",) 

566 

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) 

605 

606# Custom authentication strategy 

607SOCIAL_AUTH_STRATEGY = "weblate.accounts.strategy.WeblateStrategy" 

608 

609# Raise exceptions so that we can handle them later 

610SOCIAL_AUTH_RAISE_EXCEPTIONS = True 

611 

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" 

620 

621# Value higher than 0 enables validation using zxcvbn 

622PASSWORD_MINIMAL_STRENGTH = get_env_int("WEBLATE_MIN_PASSWORD_SCORE", 3) 

623 

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] 

636 

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 ) 

652 

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] 

660 

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") 

668 

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") 

674 

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) 

679 

680# Email registration filter 

681REGISTRATION_EMAIL_MATCH = get_env_str("WEBLATE_REGISTRATION_EMAIL_MATCH", ".*") 

682 

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) 

686 

687# Shortcut for login required setting 

688REQUIRE_LOGIN = get_env_bool("WEBLATE_REQUIRE_LOGIN") 

689 

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] 

707 

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") 

712 

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 } 

723 

724ROOT_URLCONF = "weblate.urls" 

725 

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] 

780 

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") 

784 

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") 

791 

792 # Enable legal app 

793 INSTALLED_APPS.append("weblate.legal") 

794 

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") 

807 

808 

809modify_env_list(INSTALLED_APPS, "APPS") 

810 

811# Custom exception reporter to include some details 

812DEFAULT_EXCEPTION_REPORTER_FILTER = "weblate.trans.debug.WeblateExceptionReporterFilter" 

813 

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 

819 

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") 

824 

825# GELF TCP integration (Graylog) 

826WEBLATE_LOG_GELF_HOST = get_env_str("WEBLATE_LOG_GELF_HOST", None) 

827 

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") 

830 

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} 

909 

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 } 

922 

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 } 

936 

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") 

939 

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 

944 

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 

970 

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" 

977 

978# Some security headers 

979SECURE_BROWSER_XSS_FILTER = True 

980X_FRAME_OPTIONS = "DENY" 

981SECURE_CONTENT_TYPE_NOSNIFF = True 

982 

983# Optionally enable HSTS 

984SECURE_HSTS_SECONDS = 31536000 if ENABLE_HTTPS else 0 

985SECURE_HSTS_PRELOAD = ENABLE_HTTPS 

986SECURE_HSTS_INCLUDE_SUBDOMAINS = ENABLE_HTTPS 

987 

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 

992 

993# URL of login 

994LOGIN_URL = f"{URL_PREFIX}/accounts/login/" 

995 

996# URL of logout 

997LOGOUT_URL = f"{URL_PREFIX}/accounts/logout/" 

998 

999# Default location for login 

1000LOGIN_REDIRECT_URL = f"{URL_PREFIX}/" 

1001 

1002# Opt-in for Django 6.0 default 

1003FORMS_URLFIELD_ASSUME_HTTPS = True 

1004 

1005# Anonymous user name 

1006ANONYMOUS_USER_NAME = "anonymous" 

1007 

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) 

1012 

1013# Sending HTML in mails 

1014EMAIL_SEND_HTML = True 

1015 

1016# Subject of emails includes site title 

1017EMAIL_SUBJECT_PREFIX = f"[{SITE_TITLE}] " 

1018 

1019# Enable remote hooks 

1020ENABLE_HOOKS = get_env_bool("WEBLATE_ENABLE_HOOKS", True) 

1021 

1022# Version hiding 

1023HIDE_VERSION = get_env_bool("WEBLATE_HIDE_VERSION") 

1024 

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("") 

1029 

1030LICENSE_REQUIRED = get_env_bool("WEBLATE_LICENSE_REQUIRED") 

1031WEBSITE_REQUIRED = get_env_bool("WEBLATE_WEBSITE_REQUIRED", True) 

1032 

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")) 

1036 

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) 

1043 

1044# Use simple language codes for default language/country combinations 

1045SIMPLIFY_LANGUAGES = get_env_bool("WEBLATE_SIMPLIFY_LANGUAGES", True) 

1046 

1047# Default number of elements to display when pagination is active 

1048DEFAULT_PAGE_LIMIT = get_env_int("WEBLATE_DEFAULT_PAGE_LIMIT", 100) 

1049 

1050# Render forms using bootstrap 

1051CRISPY_ALLOWED_TEMPLATE_PACKS = ["bootstrap3", "bootstrap5"] 

1052CRISPY_TEMPLATE_PACK = "bootstrap3" 

1053 

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") 

1134 

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") 

1145 

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") 

1177 

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") 

1206 

1207 

1208# E-mail address that error messages come from. 

1209SERVER_EMAIL = get_env_str("WEBLATE_SERVER_EMAIL", "weblate@example.com") 

1210 

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) 

1214 

1215# List of URLs your site is supposed to serve 

1216ALLOWED_HOSTS = get_env_list("WEBLATE_ALLOWED_HOSTS", ["*"]) 

1217 

1218# Extract redis URL 

1219REDIS_URL = get_env_redis_url() 

1220 

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] 

1244 

1245 

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" 

1252 

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) 

1261 

1262# Fonts CDN URL 

1263FONTS_CDN_URL = None 

1264 

1265# Django compressor offline mode 

1266COMPRESS_OFFLINE = True 

1267COMPRESS_OFFLINE_CONTEXT = "weblate.utils.compress.offline_context" 

1268COMPRESS_CSS_HASHING_METHOD = "content" 

1269 

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"/(.*)$",) 

1273 

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") 

1295 

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) 

1310 

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 

1319 

1320EMAIL_BACKEND = get_env_str( 

1321 "WEBLATE_EMAIL_BACKEND", 

1322 "django.core.mail.backends.smtp.EmailBackend", 

1323 required=True, 

1324) 

1325 

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] 

1335 

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") 

1339 

1340SILENCED_SYSTEM_CHECKS.extend(get_env_list("WEBLATE_SILENCED_SYSTEM_CHECKS")) 

1341 

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 

1353 

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} 

1368 

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/.*$" 

1373 

1374# Database backup type 

1375DATABASE_BACKUP = get_env_str("WEBLATE_DATABASE_BACKUP", "plain") 

1376 

1377# Enable auto updating 

1378AUTO_UPDATE = get_env_bool("WEBLATE_AUTO_UPDATE") 

1379 

1380# Update languages on migration 

1381UPDATE_LANGUAGES = get_env_bool("WEBLATE_UPDATE_LANGUAGES", True) 

1382 

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) 

1388 

1389# Default access control 

1390DEFAULT_ACCESS_CONTROL = get_env_int("WEBLATE_DEFAULT_ACCESS_CONTROL") 

1391 

1392# Default access control 

1393DEFAULT_RESTRICTED_COMPONENT = get_env_bool("WEBLATE_DEFAULT_RESTRICTED_COMPONENT") 

1394 

1395# Default translation propagation 

1396DEFAULT_TRANSLATION_PROPAGATION = get_env_bool( 

1397 "WEBLATE_DEFAULT_TRANSLATION_PROPAGATION", True 

1398) 

1399 

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) 

1406 

1407DEFAULT_AUTO_WATCH = get_env_bool("WEBLATE_DEFAULT_AUTO_WATCH", True) 

1408 

1409DEFAULT_SHARED_TM = get_env_bool("WEBLATE_DEFAULT_SHARED_TM", True) 

1410 

1411DEFAULT_AUTOCLEAN_TM = get_env_bool("WEBLATE_AUTOCLEAN_TM", False) 

1412 

1413CONTACT_FORM = get_env_str("WEBLATE_CONTACT_FORM", "reply-to", required=True) 

1414ADMINS_CONTACT = get_env_list("WEBLATE_ADMINS_CONTACT") 

1415 

1416SSH_EXTRA_ARGS = get_env_str("WEBLATE_SSH_EXTRA_ARGS", "") 

1417 

1418BORG_EXTRA_ARGS = get_env_list("WEBLATE_BORG_EXTRA_ARGS") 

1419 

1420ENABLE_SHARING = get_env_bool("WEBLATE_ENABLE_SHARING") 

1421 

1422SUPPORT_STATUS_CHECK = get_env_bool("WEBLATE_SUPPORT_STATUS_CHECK") 

1423 

1424EXTRA_HTML_HEAD = get_env_str("WEBLATE_EXTRA_HTML_HEAD", "") 

1425 

1426UNUSED_ALERT_DAYS = get_env_int("WEBLATE_UNUSED_ALERT_DAYS", 365) 

1427 

1428USE_X_FORWARDED_HOST = get_env_bool("WEBLATE_USE_X_FORWARDED_HOST", False) 

1429 

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) 

1436 

1437# PGP commits signing 

1438WEBLATE_GPG_IDENTITY = get_env_str("WEBLATE_GPG_IDENTITY") 

1439 

1440# Localize CDN addon 

1441LOCALIZE_CDN_URL = get_env_str("WEBLATE_LOCALIZE_CDN_URL") 

1442LOCALIZE_CDN_PATH = get_env_str("WEBLATE_LOCALIZE_CDN_PATH") 

1443 

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") 

1449 

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") 

1462 

1463# Web Monetization 

1464INTERLEDGER_PAYMENT_POINTERS = get_env_list("WEBLATE_INTERLEDGER_PAYMENT_POINTERS", []) 

1465INTERLEDGER_PAYMENT_BUILTIN = get_env_bool("WEBLATE_INTERLEDGER_PAYMENT_BUILTIN", True) 

1466 

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