Coverage for app/venv/lib/python3.14/site-packages/weblate/settings_example.py: 0%

188 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 

5# mypy: disable-error-code="var-annotated" 

6from __future__ import annotations 

7 

8import os 

9import platform 

10from logging.handlers import SysLogHandler 

11 

12from weblate.api.spectacular import ( 

13 get_drf_settings, 

14 get_drf_standardized_errors_settings, 

15 get_spectacular_settings, 

16) 

17 

18# Title of site to use 

19SITE_TITLE = "Weblate" 

20 

21# Site domain 

22SITE_DOMAIN = "" 

23 

24# Whether site uses https 

25ENABLE_HTTPS = False 

26 

27# Site URL 

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

29 

30# 

31# Django settings for Weblate project. 

32# 

33 

34DEBUG = True 

35 

36ADMINS: tuple[tuple[str, str], ...] = ( 

37 # ("Your Name", "your_email@example.com"), 

38) 

39 

40MANAGERS = ADMINS 

41 

42DATABASES = { 

43 "default": { 

44 # Use "postgresql" or "mysql". 

45 "ENGINE": "django.db.backends.postgresql", 

46 # Database name. 

47 "NAME": "weblate", 

48 # Database user. 

49 "USER": "weblate", 

50 # Name of role to alter to set parameters in PostgreSQL, 

51 # use in case role name is different than user used for authentication. 

52 # "ALTER_ROLE": "weblate", 

53 # Database password. 

54 "PASSWORD": "", 

55 # Set to empty string for localhost. 

56 "HOST": "127.0.0.1", 

57 # Set to empty string for default. 

58 "PORT": "", 

59 # Customizations for databases. 

60 "OPTIONS": { 

61 # In case of using an older MySQL server, 

62 # which has MyISAM as a default storage 

63 # "init_command": "SET storage_engine=INNODB", 

64 # Uncomment for MySQL older than 5.7: 

65 # "init_command": "SET sql_mode='STRICT_TRANS_TABLES'", 

66 # Set emoji capable charset for MySQL: 

67 # "charset": "utf8mb4", 

68 # Change connection timeout in case you get MySQL gone away error: 

69 # "connect_timeout": 28800, 

70 }, 

71 # Persistent connections 

72 "CONN_MAX_AGE": None, 

73 "CONN_HEALTH_CHECKS": True, 

74 # Disable server-side cursors, might be needed with pgbouncer 

75 "DISABLE_SERVER_SIDE_CURSORS": False, 

76 } 

77} 

78 

79# Data directory, you can use following for the development purposes: 

80# os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data") 

81DATA_DIR = "/home/weblate/data" 

82CACHE_DIR = f"{DATA_DIR}/cache" 

83 

84# Local time zone for this installation. Choices can be found here: 

85# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 

86# although not all choices may be available on all operating systems. 

87# In a Windows environment this must be set to your system time zone. 

88TIME_ZONE = "UTC" 

89 

90# Language code for this installation. All choices can be found here: 

91# http://www.i18nguy.com/unicode/language-identifiers.html 

92LANGUAGE_CODE = "en-us" 

93 

94LANGUAGES = ( 

95 ("ar", "العربية"), 

96 ("az", "Azərbaycan"), 

97 ("ba", "башҡорт теле"), # codespell:ignore 

98 ("be", "Беларуская"), 

99 ("be-latn", "Biełaruskaja"), 

100 ("bg", "Български"), 

101 ("br", "Brezhoneg"), 

102 ("ca", "Català"), 

103 ("cs", "Čeština"), 

104 ("cy", "Cymraeg"), 

105 ("da", "Dansk"), 

106 ("de", "Deutsch"), 

107 ("en", "English"), 

108 ("el", "Ελληνικά"), 

109 ("en-gb", "English (United Kingdom)"), 

110 ("es", "Español"), 

111 ("fi", "Suomi"), 

112 ("fr", "Français"), 

113 ("ga", "Gaeilge"), 

114 ("gl", "Galego"), 

115 ("he", "עברית"), 

116 ("hu", "Magyar"), 

117 ("hr", "Hrvatski"), 

118 ("id", "Indonesia"), 

119 ("is", "Íslenska"), 

120 ("it", "Italiano"), 

121 ("ja", "日本語"), 

122 ("kab", "Taqbaylit"), 

123 ("kk", "Қазақ тілі"), 

124 ("ko", "한국어"), 

125 ("nb", "Norsk bokmål"), 

126 ("nl", "Nederlands"), 

127 ("pl", "Polski"), 

128 ("pt", "Português"), 

129 ("pt-br", "Português brasileiro"), 

130 ("ro", "Română"), 

131 ("ru", "Русский"), 

132 ("sk", "Slovenčina"), 

133 ("sl", "Slovenščina"), 

134 ("sq", "Shqip"), 

135 ("sr", "Српски"), 

136 ("sr-latn", "Srpski"), 

137 ("sv", "Svenska"), 

138 ("ta", "தமிழ்"), 

139 ("th", "ไทย"), 

140 ("tr", "Türkçe"), 

141 ("uk", "Українська"), 

142 ("zh-hans", "简体中文"), 

143 ("zh-hant", "正體中文"), 

144) 

145 

146SITE_ID = 1 

147 

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

149# to load the internationalization machinery. 

150USE_I18N = True 

151 

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

153USE_TZ = True 

154 

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

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

157 

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

159URL_PREFIX = "" 

160 

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

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

163 

164# URL that handles the media served from MEDIA_ROOT. Make sure to use a 

165# trailing slash. 

166MEDIA_URL = f"{URL_PREFIX}/media/" 

167 

168# Absolute path to the directory static files should be collected to. 

169# Don't put anything in this directory yourself; store your static files 

170# in apps' "static/" subdirectories and in STATICFILES_DIRS. 

171STATIC_ROOT = os.path.join(CACHE_DIR, "static") 

172 

173# URL prefix for static files. 

174STATIC_URL = f"{URL_PREFIX}/static/" 

175 

176# Additional locations of static files 

177STATICFILES_DIRS = ( 

178 # Put strings here, like "/home/html/static" or "C:/www/django/static". 

179 # Always use forward slashes, even on Windows. 

180 # Don't forget to use absolute paths, not relative paths. 

181) 

182 

183# List of finder classes that know how to find static files in 

184# various locations. 

185STATICFILES_FINDERS = ( 

186 "django.contrib.staticfiles.finders.FileSystemFinder", 

187 "django.contrib.staticfiles.finders.AppDirectoriesFinder", 

188 "compressor.finders.CompressorFinder", 

189) 

190 

191# Make this unique, and don't share it with anybody. 

192# You can generate it using weblate-generate-secret-key 

193SECRET_KEY = "" 

194 

195TEMPLATES = [ 

196 { 

197 "BACKEND": "django.template.backends.django.DjangoTemplates", 

198 "OPTIONS": { 

199 "context_processors": [ 

200 "django.contrib.auth.context_processors.auth", 

201 "django.template.context_processors.debug", 

202 "django.template.context_processors.i18n", 

203 "django.template.context_processors.request", 

204 "django.template.context_processors.csrf", 

205 "django.contrib.messages.context_processors.messages", 

206 "weblate.trans.context_processors.weblate_context", 

207 ], 

208 }, 

209 "APP_DIRS": True, 

210 } 

211] 

212 

213 

214# GitHub username and token for sending pull requests. 

215# Please see the documentation for more details. 

216GITHUB_CREDENTIALS = {} 

217 

218# Azure DevOps username and token for sending pull requests. 

219# Please see the documentation for more details. 

220AZURE_DEVOPS_CREDENTIALS = {} 

221 

222# GitLab username and token for sending merge requests. 

223# Please see the documentation for more details. 

224GITLAB_CREDENTIALS = {} 

225 

226# Bitbucket username and token for sending merge requests. 

227# Please see the documentation for more details. 

228BITBUCKETSERVER_CREDENTIALS = {} 

229 

230# Bitbucket username, app-password and workspace for sending merge requests. 

231# Please see the documentation for more details. 

232BITBUCKETCLOUD_CREDENTIALS = {} 

233 

234# Authentication configuration 

235AUTHENTICATION_BACKENDS: tuple[str, ...] = ( 

236 "social_core.backends.email.EmailAuth", 

237 # "social_core.backends.google.GoogleOAuth2", 

238 # "social_core.backends.github.GithubOAuth2", 

239 # "social_core.backends.github_enterprise.GithubEnterpriseOAuth2", 

240 # "social_core.backends.bitbucket.BitbucketOAuth2", 

241 # "social_core.backends.suse.OpenSUSEOpenId", 

242 # "social_core.backends.ubuntu.UbuntuOpenId", 

243 # "social_core.backends.fedora.FedoraOpenId", 

244 # "social_core.backends.facebook.FacebookOAuth2", 

245 "weblate.accounts.auth.WeblateUserBackend", 

246) 

247 

248# Custom user model 

249AUTH_USER_MODEL = "weblate_auth.User" 

250 

251# WebAuthn 

252OTP_WEBAUTHN_RP_NAME = SITE_TITLE 

253OTP_WEBAUTHN_RP_ID = SITE_DOMAIN.split(":", 1)[0] 

254OTP_WEBAUTHN_ALLOWED_ORIGINS = [SITE_URL] 

255OTP_WEBAUTHN_ALLOW_PASSWORDLESS_LOGIN = False 

256OTP_WEBAUTHN_HELPER_CLASS = "weblate.accounts.utils.WeblateWebAuthnHelper" 

257 

258# Social auth backends setup 

259SOCIAL_AUTH_GITHUB_KEY = "" 

260SOCIAL_AUTH_GITHUB_SECRET = "" 

261SOCIAL_AUTH_GITHUB_SCOPE = ["user:email"] 

262 

263SOCIAL_AUTH_GITHUB_ORG_KEY = "" 

264SOCIAL_AUTH_GITHUB_ORG_SECRET = "" 

265SOCIAL_AUTH_GITHUB_ORG_NAME = "" 

266 

267SOCIAL_AUTH_GITHUB_TEAM_KEY = "" 

268SOCIAL_AUTH_GITHUB_TEAM_SECRET = "" 

269SOCIAL_AUTH_GITHUB_TEAM_ID = "" 

270 

271SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY = "" 

272SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET = "" 

273SOCIAL_AUTH_GITHUB_ENTERPRISE_URL = "" 

274SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL = "" 

275SOCIAL_AUTH_GITHUB_ENTERPRISE_SCOPE = "" 

276 

277SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY = "" 

278SOCIAL_AUTH_BITBUCKET_OAUTH2_SECRET = "" 

279SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY = True 

280 

281SOCIAL_AUTH_FACEBOOK_KEY = "" 

282SOCIAL_AUTH_FACEBOOK_SECRET = "" 

283SOCIAL_AUTH_FACEBOOK_SCOPE = ["email", "public_profile"] 

284SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {"fields": "id,name,email"} 

285 

286SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = "" 

287SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = "" 

288 

289# Social auth settings 

290SOCIAL_AUTH_PIPELINE = ( 

291 "social_core.pipeline.social_auth.social_details", 

292 "social_core.pipeline.social_auth.social_uid", 

293 "social_core.pipeline.social_auth.auth_allowed", 

294 "social_core.pipeline.social_auth.social_user", 

295 "weblate.accounts.pipeline.store_params", 

296 "weblate.accounts.pipeline.verify_open", 

297 "social_core.pipeline.user.get_username", 

298 "weblate.accounts.pipeline.require_email", 

299 "social_core.pipeline.mail.mail_validation", 

300 "weblate.accounts.pipeline.revoke_mail_code", 

301 "weblate.accounts.pipeline.ensure_valid", 

302 "weblate.accounts.pipeline.remove_account", 

303 "social_core.pipeline.social_auth.associate_by_email", 

304 "weblate.accounts.pipeline.reauthenticate", 

305 "weblate.accounts.pipeline.verify_username", 

306 "social_core.pipeline.user.create_user", 

307 "social_core.pipeline.social_auth.associate_user", 

308 "social_core.pipeline.social_auth.load_extra_data", 

309 "weblate.accounts.pipeline.second_factor", 

310 "weblate.accounts.pipeline.cleanup_next", 

311 "weblate.accounts.pipeline.user_full_name", 

312 "weblate.accounts.pipeline.store_email", 

313 "weblate.accounts.pipeline.notify_connect", 

314 "weblate.accounts.pipeline.handle_invite", 

315 "weblate.accounts.pipeline.password_reset", 

316) 

317SOCIAL_AUTH_DISCONNECT_PIPELINE = ( 

318 "social_core.pipeline.disconnect.allowed_to_disconnect", 

319 "social_core.pipeline.disconnect.get_entries", 

320 "social_core.pipeline.disconnect.revoke_tokens", 

321 "weblate.accounts.pipeline.cycle_session", 

322 "weblate.accounts.pipeline.adjust_primary_mail", 

323 "weblate.accounts.pipeline.notify_disconnect", 

324 "social_core.pipeline.disconnect.disconnect", 

325 "weblate.accounts.pipeline.cleanup_next", 

326) 

327 

328# Custom authentication strategy 

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

330 

331# Raise exceptions so that we can handle them later 

332SOCIAL_AUTH_RAISE_EXCEPTIONS = True 

333 

334SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = "weblate.accounts.pipeline.send_validation" 

335SOCIAL_AUTH_EMAIL_VALIDATION_URL = f"{URL_PREFIX}/accounts/email-sent/" 

336SOCIAL_AUTH_LOGIN_ERROR_URL = f"{URL_PREFIX}/accounts/login/" 

337SOCIAL_AUTH_EMAIL_FORM_URL = f"{URL_PREFIX}/accounts/email/" 

338SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = f"{URL_PREFIX}/accounts/profile/#account" 

339SOCIAL_AUTH_PROTECTED_USER_FIELDS = ("email",) 

340SOCIAL_AUTH_SLUGIFY_USERNAMES = True 

341SOCIAL_AUTH_SLUGIFY_FUNCTION = "weblate.accounts.pipeline.slugify_username" 

342 

343# Value higher than 0 enables validation using zxcvbn 

344PASSWORD_MINIMAL_STRENGTH = 0 

345 

346# Password validation configuration 

347AUTH_PASSWORD_VALIDATORS = [ 

348 { 

349 "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" 

350 }, 

351 { 

352 "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 

353 "OPTIONS": {"min_length": 10}, 

354 }, 

355 {"NAME": "weblate.accounts.password_validation.MaximalLengthValidator"}, 

356 {"NAME": "weblate.accounts.password_validation.PastPasswordsValidator"}, 

357] 

358 

359# Optional password strength validation by django-zxcvbn-password-validator 

360if PASSWORD_MINIMAL_STRENGTH > 0: 

361 AUTH_PASSWORD_VALIDATORS.append( 

362 {"NAME": "django_zxcvbn_password_validator.ZxcvbnPasswordValidator"} 

363 ) 

364else: 

365 AUTH_PASSWORD_VALIDATORS.extend( 

366 [ 

367 {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, 

368 { 

369 "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator" 

370 }, 

371 {"NAME": "weblate.accounts.password_validation.CharsPasswordValidator"}, 

372 ] 

373 ) 

374 

375# Password hashing (prefer Argon) 

376PASSWORD_HASHERS = [ 

377 "django.contrib.auth.hashers.Argon2PasswordHasher", 

378 "django.contrib.auth.hashers.PBKDF2PasswordHasher", 

379 "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", 

380 "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", 

381] 

382 

383# Allow new user registrations 

384REGISTRATION_OPEN = True 

385 

386# Shortcut for login required setting 

387REQUIRE_LOGIN = False 

388 

389# Middleware 

390MIDDLEWARE = [ 

391 "weblate.middleware.RedirectMiddleware", 

392 "weblate.middleware.ProxyMiddleware", 

393 "corsheaders.middleware.CorsMiddleware", 

394 "django.middleware.security.SecurityMiddleware", 

395 "django.contrib.sessions.middleware.SessionMiddleware", 

396 "django.middleware.csrf.CsrfViewMiddleware", 

397 "weblate.accounts.middleware.AuthenticationMiddleware", 

398 "django.contrib.messages.middleware.MessageMiddleware", 

399 "django.middleware.clickjacking.XFrameOptionsMiddleware", 

400 "social_django.middleware.SocialAuthExceptionMiddleware", 

401 "weblate.accounts.middleware.RequireLoginMiddleware", 

402 "weblate.api.middleware.ThrottlingMiddleware", 

403 "weblate.middleware.SecurityMiddleware", 

404 "weblate.wladmin.middleware.ManageMiddleware", 

405] 

406 

407ROOT_URLCONF = "weblate.urls" 

408 

409# Django and Weblate apps 

410INSTALLED_APPS = [ 

411 # Weblate apps on top to override Django locales and templates 

412 "weblate.addons", 

413 "weblate.auth", 

414 "weblate.checks", 

415 "weblate.formats", 

416 "weblate.glossary", 

417 "weblate.machinery", 

418 "weblate.trans", 

419 "weblate.lang", 

420 "weblate_language_data", 

421 "weblate.memory", 

422 "weblate.screenshots", 

423 "weblate.fonts", 

424 "weblate.accounts", 

425 "weblate.configuration", 

426 "weblate.utils", 

427 "weblate.vcs", 

428 "weblate.wladmin", 

429 "weblate.metrics", 

430 "weblate", 

431 # Optional: Git exporter 

432 "weblate.gitexport", 

433 # Standard Django modules 

434 "django.contrib.auth", 

435 "django.contrib.contenttypes", 

436 "django.contrib.sessions", 

437 "django.contrib.messages", 

438 "django.contrib.staticfiles", 

439 "django.contrib.admin", 

440 "django.contrib.sitemaps", 

441 "django.contrib.humanize", 

442 # Third party Django modules 

443 "social_django", 

444 "crispy_forms", 

445 "crispy_bootstrap3", 

446 "crispy_bootstrap5", 

447 "compressor", 

448 "rest_framework", 

449 "rest_framework.authtoken", 

450 "django_filters", 

451 "django_celery_beat", 

452 "corsheaders", 

453 "django_otp", 

454 "django_otp.plugins.otp_static", 

455 "django_otp.plugins.otp_totp", 

456 "django_otp_webauthn", 

457 "drf_spectacular", 

458 "drf_spectacular_sidecar", 

459 "drf_standardized_errors", 

460] 

461 

462# django_zxcvbn_password_validator integration 

463if PASSWORD_MINIMAL_STRENGTH > 0: 

464 INSTALLED_APPS.append("django_zxcvbn_password_validator") 

465 

466# Custom exception reporter to include some details 

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

468 

469# Default logging of Weblate messages 

470# - to syslog in production (if available) 

471# - otherwise to console 

472# - you can also choose "logfile" to log into separate file 

473# after configuring it below 

474 

475# Detect if we can connect to syslog 

476HAVE_SYSLOG = False 

477if platform.system() != "Windows": 

478 try: 

479 handler = SysLogHandler(address="/dev/log", facility=SysLogHandler.LOG_LOCAL2) 

480 # Since Python 3.7 connect failures are silently discarded, so 

481 # the exception is almost never raised here. Instead we look whether the socket 

482 # to syslog is open after init. 

483 HAVE_SYSLOG = handler.socket.fileno() != -1 # type: ignore[attr-defined] 

484 handler.close() 

485 except OSError: 

486 HAVE_SYSLOG = False 

487 

488DEFAULT_LOG = ["console" if DEBUG or not HAVE_SYSLOG else "syslog"] 

489DEFAULT_LOGLEVEL = "DEBUG" if DEBUG else "INFO" 

490 

491# GELF TCP integration (Graylog) 

492WEBLATE_LOG_GELF_HOST = None 

493 

494if WEBLATE_LOG_GELF_HOST: 

495 DEFAULT_LOG.append("gelf") 

496 

497# A sample logging configuration. The only tangible logging 

498# performed by this configuration is to send an email to 

499# the site admins on every HTTP 500 error when DEBUG=False. 

500# See http://docs.djangoproject.com/en/stable/topics/logging for 

501# more details on how to customize your logging configuration. 

502LOGGING: dict = { 

503 "version": 1, 

504 "disable_existing_loggers": True, 

505 "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, 

506 "formatters": { 

507 "simple": {"format": "[%(asctime)s: %(levelname)s/%(process)s] %(message)s"}, 

508 "logfile": {"format": "%(asctime)s %(levelname)s %(message)s"}, 

509 "django.server": { 

510 "()": "django.utils.log.ServerFormatter", 

511 "format": "[%(server_time)s] %(message)s", 

512 }, 

513 }, 

514 "handlers": { 

515 "mail_admins": { 

516 "level": "ERROR", 

517 "filters": ["require_debug_false"], 

518 "class": "django.utils.log.AdminEmailHandler", 

519 "include_html": True, 

520 }, 

521 "console": { 

522 "level": "DEBUG", 

523 "class": "logging.StreamHandler", 

524 "formatter": "simple", 

525 }, 

526 "django.server": { 

527 "level": "INFO", 

528 "class": "logging.StreamHandler", 

529 "formatter": "django.server", 

530 }, 

531 # Logging to a file 

532 # "logfile": { 

533 # "level":"DEBUG", 

534 # "class":"logging.handlers.RotatingFileHandler", 

535 # "filename": "/var/log/weblate/weblate.log", 

536 # "maxBytes": 100000, 

537 # "backupCount": 3, 

538 # "formatter": "logfile", 

539 # }, 

540 }, 

541 "loggers": { 

542 "django.request": { 

543 "handlers": ["mail_admins", *DEFAULT_LOG], 

544 "level": "ERROR", 

545 "propagate": True, 

546 }, 

547 "django.server": { 

548 "handlers": ["django.server"], 

549 "level": "INFO", 

550 "propagate": False, 

551 }, 

552 # Logging database queries 

553 "django.db.backends": { 

554 "handlers": [*DEFAULT_LOG], 

555 # Toggle to DEBUG to log all database queries 

556 "level": "CRITICAL", 

557 }, 

558 "weblate": { 

559 "handlers": [*DEFAULT_LOG], 

560 "level": DEFAULT_LOGLEVEL, 

561 }, 

562 # Logging VCS operations 

563 "weblate.vcs": { 

564 "handlers": [*DEFAULT_LOG], 

565 "level": DEFAULT_LOGLEVEL, 

566 }, 

567 # Python Social Auth 

568 "social": { 

569 "handlers": [*DEFAULT_LOG], 

570 "level": DEFAULT_LOGLEVEL, 

571 }, 

572 # Django Authentication Using LDAP 

573 "django_auth_ldap": { 

574 "handlers": [*DEFAULT_LOG], 

575 "level": DEFAULT_LOGLEVEL, 

576 }, 

577 # SAML IdP 

578 "djangosaml2idp": { 

579 "handlers": [*DEFAULT_LOG], 

580 "level": DEFAULT_LOGLEVEL, 

581 }, 

582 }, 

583} 

584 

585# Configure syslog setup if it's present 

586if HAVE_SYSLOG: 

587 LOGGING["formatters"]["syslog"] = { 

588 "format": "weblate[%(process)d]: %(levelname)s %(message)s", 

589 } 

590 LOGGING["handlers"]["syslog"] = { 

591 "level": "DEBUG", 

592 "class": "logging.handlers.SysLogHandler", 

593 "formatter": "syslog", 

594 "address": "/dev/log", 

595 "facility": SysLogHandler.LOG_LOCAL2, 

596 } 

597 

598# Configure GELF integration if presetn 

599if WEBLATE_LOG_GELF_HOST: 

600 LOGGING["formatters"]["gelf"] = { 

601 "()": "logging_gelf.formatters.GELFFormatter", 

602 "null_character": True, 

603 } 

604 LOGGING["handlers"]["gelf"] = { 

605 "level": "DEBUG", 

606 "class": "logging_gelf.handlers.GELFTCPSocketHandler", 

607 "formatter": "gelf", 

608 "host": WEBLATE_LOG_GELF_HOST, 

609 "port": 12201, 

610 } 

611 

612# Use HTTPS when creating redirect URLs for social authentication, see 

613# documentation for more details: 

614# https://python-social-auth-docs.readthedocs.io/en/latest/configuration/settings.html#processing-redirects-and-urlopen 

615SOCIAL_AUTH_REDIRECT_IS_HTTPS = ENABLE_HTTPS 

616 

617# Make CSRF cookie HttpOnly, see documentation for more details: 

618# https://docs.djangoproject.com/en/1.11/ref/settings/#csrf-cookie-httponly 

619CSRF_COOKIE_HTTPONLY = True 

620CSRF_COOKIE_SECURE = ENABLE_HTTPS 

621# Store CSRF token in session 

622CSRF_USE_SESSIONS = True 

623# Customize CSRF failure view 

624CSRF_FAILURE_VIEW = "weblate.trans.views.error.csrf_failure" 

625SESSION_COOKIE_SECURE = ENABLE_HTTPS 

626SESSION_COOKIE_HTTPONLY = True 

627# SSL redirect 

628SECURE_SSL_REDIRECT = ENABLE_HTTPS 

629SECURE_SSL_HOST = SITE_DOMAIN 

630# Sent referrer only for same origin links 

631SECURE_REFERRER_POLICY = "same-origin" 

632# SSL redirect URL exemption list 

633SECURE_REDIRECT_EXEMPT = (r"healthz/$",) # Allowing HTTP access to health check 

634# Session cookie age (in seconds) 

635SESSION_COOKIE_AGE = 1000 

636SESSION_COOKIE_AGE_AUTHENTICATED = 1209600 

637SESSION_COOKIE_SAMESITE = "Lax" 

638# Increase allowed upload size 

639DATA_UPLOAD_MAX_MEMORY_SIZE = 50000000 

640# Allow more fields for case with a lot of subscriptions in profile 

641DATA_UPLOAD_MAX_NUMBER_FIELDS = 2000 

642 

643# Apply session coookie settings to language cookie as well with exception 

644# of SameSite as we want language to be honored in CSRF error messages. 

645LANGUAGE_COOKIE_SECURE = SESSION_COOKIE_SECURE 

646LANGUAGE_COOKIE_HTTPONLY = SESSION_COOKIE_HTTPONLY 

647LANGUAGE_COOKIE_AGE = SESSION_COOKIE_AGE_AUTHENTICATED * 10 

648LANGUAGE_COOKIE_SAMESITE = "None" 

649 

650# Some security headers 

651SECURE_BROWSER_XSS_FILTER = True 

652X_FRAME_OPTIONS = "DENY" 

653SECURE_CONTENT_TYPE_NOSNIFF = True 

654 

655# Optionally enable HSTS 

656SECURE_HSTS_SECONDS = 31536000 if ENABLE_HTTPS else 0 

657SECURE_HSTS_PRELOAD = ENABLE_HTTPS 

658SECURE_HSTS_INCLUDE_SUBDOMAINS = ENABLE_HTTPS 

659 

660# HTTPS detection behind reverse proxy 

661SECURE_PROXY_SSL_HEADER = None 

662 

663# URL of login 

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

665 

666# URL of logout 

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

668 

669# Default location for login 

670LOGIN_REDIRECT_URL = f"{URL_PREFIX}/" 

671 

672# Opt-in for Django 6.0 default 

673FORMS_URLFIELD_ASSUME_HTTPS = True 

674 

675# Anonymous user name 

676ANONYMOUS_USER_NAME = "anonymous" 

677 

678# Reverse proxy settings 

679IP_PROXY_HEADER = "HTTP_X_FORWARDED_FOR" 

680IP_BEHIND_REVERSE_PROXY = False 

681IP_PROXY_OFFSET = -1 

682 

683# Sending HTML in mails 

684EMAIL_SEND_HTML = True 

685 

686# Subject of emails includes site title 

687EMAIL_SUBJECT_PREFIX = f"[{SITE_TITLE}] " 

688 

689# Enable remote hooks 

690ENABLE_HOOKS = True 

691 

692# By default the length of a given translation is limited to the length of 

693# the source string * 10 characters. Set this option to False to allow longer 

694# translations (up to 10.000 characters) 

695LIMIT_TRANSLATION_LENGTH_BY_SOURCE_LENGTH = True 

696 

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

698SIMPLIFY_LANGUAGES = True 

699 

700# Render forms using bootstrap 

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

702CRISPY_TEMPLATE_PACK = "bootstrap3" 

703 

704# List of quality checks 

705# CHECK_LIST = ( 

706# "weblate.checks.same.SameCheck", 

707# "weblate.checks.chars.BeginNewlineCheck", 

708# "weblate.checks.chars.EndNewlineCheck", 

709# "weblate.checks.chars.BeginSpaceCheck", 

710# "weblate.checks.chars.EndSpaceCheck", 

711# "weblate.checks.chars.DoubleSpaceCheck", 

712# "weblate.checks.chars.EndStopCheck", 

713# "weblate.checks.chars.EndColonCheck", 

714# "weblate.checks.chars.EndQuestionCheck", 

715# "weblate.checks.chars.EndExclamationCheck", 

716# "weblate.checks.chars.EndInterrobangCheck", 

717# "weblate.checks.chars.EndEllipsisCheck", 

718# "weblate.checks.chars.EndSemicolonCheck", 

719# "weblate.checks.chars.MaxLengthCheck", 

720# "weblate.checks.chars.KashidaCheck", 

721# "weblate.checks.chars.PunctuationSpacingCheck", 

722# "weblate.checks.chars.KabyleCharactersCheck", 

723# "weblate.checks.format.PythonFormatCheck", 

724# "weblate.checks.format.PythonBraceFormatCheck", 

725# "weblate.checks.format.PHPFormatCheck", 

726# "weblate.checks.format.CFormatCheck", 

727# "weblate.checks.format.PerlFormatCheck", 

728# "weblate.checks.format.PerlBraceFormatCheck", 

729# "weblate.checks.format.JavaScriptFormatCheck", 

730# "weblate.checks.format.LuaFormatCheck", 

731# "weblate.checks.format.ObjectPascalFormatCheck", 

732# "weblate.checks.format.SchemeFormatCheck", 

733# "weblate.checks.format.CSharpFormatCheck", 

734# "weblate.checks.format.JavaFormatCheck", 

735# "weblate.checks.format.JavaMessageFormatCheck", 

736# "weblate.checks.format.PercentPlaceholdersCheck", 

737# "weblate.checks.format.VueFormattingCheck", 

738# "weblate.checks.format.I18NextInterpolationCheck", 

739# "weblate.checks.format.ESTemplateLiteralsCheck", 

740# "weblate.checks.format.AutomatticComponentsCheck", 

741# "weblate.checks.angularjs.AngularJSInterpolationCheck", 

742# "weblate.checks.icu.ICUMessageFormatCheck", 

743# "weblate.checks.icu.ICUSourceCheck", 

744# "weblate.checks.qt.QtFormatCheck", 

745# "weblate.checks.qt.QtPluralCheck", 

746# "weblate.checks.ruby.RubyFormatCheck", 

747# "weblate.checks.consistency.PluralsCheck", 

748# "weblate.checks.consistency.SamePluralsCheck", 

749# "weblate.checks.consistency.ConsistencyCheck", 

750# "weblate.checks.consistency.ReusedCheck", 

751# "weblate.checks.consistency.TranslatedCheck", 

752# "weblate.checks.chars.EscapedNewlineCountingCheck", 

753# "weblate.checks.chars.NewLineCountCheck", 

754# "weblate.checks.markup.BBCodeCheck", 

755# "weblate.checks.chars.ZeroWidthSpaceCheck", 

756# "weblate.checks.render.MaxSizeCheck", 

757# "weblate.checks.markup.XMLValidityCheck", 

758# "weblate.checks.markup.XMLTagsCheck", 

759# "weblate.checks.markup.MarkdownRefLinkCheck", 

760# "weblate.checks.markup.MarkdownLinkCheck", 

761# "weblate.checks.markup.MarkdownSyntaxCheck", 

762# "weblate.checks.markup.URLCheck", 

763# "weblate.checks.markup.SafeHTMLCheck", 

764# "weblate.checks.markup.RSTReferencesCheck", 

765# "weblate.checks.markup.RSTSyntaxCheck", 

766# "weblate.checks.placeholders.PlaceholderCheck", 

767# "weblate.checks.placeholders.RegexCheck", 

768# "weblate.checks.duplicate.DuplicateCheck", 

769# "weblate.checks.source.OptionalPluralCheck", 

770# "weblate.checks.source.EllipsisCheck", 

771# "weblate.checks.source.MultipleFailingCheck", 

772# "weblate.checks.source.LongUntranslatedCheck", 

773# "weblate.checks.format.MultipleUnnamedFormatsCheck", 

774# "weblate.checks.glossary.GlossaryCheck", 

775# "weblate.checks.glossary.ProhibitedInitialCharacterCheck", 

776# "weblate.checks.fluent.syntax.FluentSourceSyntaxCheck", 

777# "weblate.checks.fluent.syntax.FluentTargetSyntaxCheck", 

778# "weblate.checks.fluent.parts.FluentPartsCheck", 

779# "weblate.checks.fluent.references.FluentReferencesCheck", 

780# "weblate.checks.fluent.inner_html.FluentSourceInnerHTMLCheck", 

781# "weblate.checks.fluent.inner_html.FluentTargetInnerHTMLCheck", 

782# ) 

783 

784# List of automatic fixups 

785# AUTOFIX_LIST = ( 

786# "weblate.trans.autofixes.whitespace.SameBookendingWhitespace", 

787# "weblate.trans.autofixes.chars.ReplaceTrailingDotsWithEllipsis", 

788# "weblate.trans.autofixes.chars.RemoveZeroSpace", 

789# "weblate.trans.autofixes.chars.RemoveControlChars", 

790# "weblate.trans.autofixes.chars.DevanagariDanda", 

791# "weblate.trans.autofixes.html.BleachHTML", 

792# ) 

793 

794# List of enabled addons 

795# WEBLATE_ADDONS = ( 

796# "weblate.addons.gettext.GenerateMoAddon", 

797# "weblate.addons.gettext.UpdateLinguasAddon", 

798# "weblate.addons.gettext.UpdateConfigureAddon", 

799# "weblate.addons.gettext.MsgmergeAddon", 

800# "weblate.addons.gettext.GettextAuthorComments", 

801# "weblate.addons.cleanup.CleanupAddon", 

802# "weblate.addons.cleanup.RemoveBlankAddon", 

803# "weblate.addons.consistency.LanguageConsistencyAddon", 

804# "weblate.addons.discovery.DiscoveryAddon", 

805# "weblate.addons.autotranslate.AutoTranslateAddon", 

806# "weblate.addons.flags.SourceEditAddon", 

807# "weblate.addons.flags.TargetEditAddon", 

808# "weblate.addons.flags.SameEditAddon", 

809# "weblate.addons.flags.BulkEditAddon", 

810# "weblate.addons.flags.TargetRepoUpdateAddon", 

811# "weblate.addons.generate.GenerateFileAddon", 

812# "weblate.addons.generate.PseudolocaleAddon", 

813# "weblate.addons.generate.PrefillAddon", 

814# "weblate.addons.generate.FillReadOnlyAddon", 

815# "weblate.addons.properties.PropertiesSortAddon", 

816# "weblate.addons.git.GitSquashAddon", 

817# "weblate.addons.removal.RemoveComments", 

818# "weblate.addons.removal.RemoveSuggestions", 

819# "weblate.addons.resx.ResxUpdateAddon", 

820# "weblate.addons.cdn.CDNJSAddon", 

821# "weblate.addons.webhooks.WebhookAddon", 

822# "weblate.addons.webhooks.SlackWebhookAddon", 

823# ) 

824 

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

826SERVER_EMAIL = "noreply@example.com" 

827 

828# Default email address to use for various automated correspondence from 

829# the site managers. Used for registration emails. 

830DEFAULT_FROM_EMAIL = "noreply@example.com" 

831 

832# List of URLs your site is supposed to serve 

833ALLOWED_HOSTS = ["*"] 

834 

835# Configuration for caching 

836CACHES = { 

837 "default": { 

838 "BACKEND": "django_redis.cache.RedisCache", 

839 "LOCATION": "redis://127.0.0.1:6379/1", 

840 # If redis is running on same host as Weblate, you might 

841 # want to use unix sockets instead: 

842 # "LOCATION": "unix:///var/run/redis/redis.sock?db=1", 

843 "OPTIONS": { 

844 "CLIENT_CLASS": "django_redis.client.DefaultClient", 

845 # If you set password here, adjust CELERY_BROKER_URL as well 

846 "PASSWORD": None, 

847 "CONNECTION_POOL_KWARGS": {}, 

848 }, 

849 "KEY_PREFIX": "weblate", 

850 "TIMEOUT": 3600, 

851 }, 

852 "avatar": { 

853 "BACKEND": "django.core.cache.backends.filebased.FileBasedCache", 

854 "LOCATION": os.path.join(CACHE_DIR, "avatar"), 

855 "TIMEOUT": 86400, 

856 "OPTIONS": {"MAX_ENTRIES": 1000}, 

857 }, 

858} 

859 

860# Store sessions in cache 

861SESSION_ENGINE = "django.contrib.sessions.backends.cache" 

862# Store messages in session 

863MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" 

864 

865# REST framework settings for API 

866REST_FRAMEWORK = get_drf_settings( 

867 require_login=REQUIRE_LOGIN, 

868 anon_throttle="100/day", 

869 user_throttle="5000/hour", 

870) 

871DRF_STANDARDIZED_ERRORS = get_drf_standardized_errors_settings() 

872SPECTACULAR_SETTINGS = get_spectacular_settings(INSTALLED_APPS, SITE_URL, SITE_TITLE) 

873 

874# Fonts CDN URL 

875FONTS_CDN_URL = None 

876 

877# Django compressor offline mode 

878COMPRESS_OFFLINE = False 

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

880COMPRESS_CSS_HASHING_METHOD = "content" 

881 

882# Require login for all URLs 

883if REQUIRE_LOGIN: 

884 LOGIN_REQUIRED_URLS = (r"/(.*)$",) 

885 

886# In such case you will want to include some of the exceptions 

887# LOGIN_REQUIRED_URLS_EXCEPTIONS = ( 

888# rf"{URL_PREFIX}/accounts/(.*)$", # Required for login 

889# rf"{URL_PREFIX}/admin/login/(.*)$", # Required for admin login 

890# rf"{URL_PREFIX}/static/(.*)$", # Required for development mode 

891# rf"{URL_PREFIX}/widget/(.*)$", # Allowing public access to widgets 

892# rf"{URL_PREFIX}/data/(.*)$", # Allowing public access to data exports 

893# rf"{URL_PREFIX}/hooks/(.*)$", # Allowing public access to notification hooks 

894# rf"{URL_PREFIX}/healthz/$", # Allowing public access to health check 

895# rf"{URL_PREFIX}/api/(.*)$", # Allowing access to API 

896# rf"{URL_PREFIX}/js/i18n/$", # JavaScript localization 

897# rf"{URL_PREFIX}/css/custom\.css$", # Custom CSS support 

898# rf"{URL_PREFIX}/contact/$", # Optional for contact form 

899# rf"{URL_PREFIX}/legal/(.*)$", # Optional for legal app 

900# rf"{URL_PREFIX}/avatar/(.*)$", # Optional for avatars 

901# rf"{URL_PREFIX}/site.webmanifest$", # The request for the manifest is made without credentials 

902# ) 

903 

904# Silence some of the Django system checks 

905SILENCED_SYSTEM_CHECKS = [ 

906 # We have modified django.contrib.auth.middleware.AuthenticationMiddleware 

907 # as weblate.accounts.middleware.AuthenticationMiddleware 

908 "admin.E408", 

909 # Silence drf_spectacular until these are addressed 

910 "drf_spectacular.W001", 

911 "drf_spectacular.W002", 

912] 

913 

914# Celery worker configuration for testing 

915# CELERY_TASK_ALWAYS_EAGER = True 

916# CELERY_BROKER_URL = "memory://" 

917# CELERY_TASK_EAGER_PROPAGATES = True 

918# Celery worker configuration for production 

919CELERY_TASK_ALWAYS_EAGER = False 

920CELERY_BROKER_URL = "redis://localhost:6379" 

921CELERY_RESULT_BACKEND: str | None = CELERY_BROKER_URL 

922CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True 

923CELERY_BROKER_CONNECTION_RETRY = True 

924 

925# Celery settings, it is not recommended to change these 

926CELERY_WORKER_MAX_MEMORY_PER_CHILD = 450000 if DEBUG else 250000 

927CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" 

928CELERY_TASK_ROUTES = { 

929 "weblate.trans.tasks.auto_translate*": {"queue": "translate"}, 

930 "weblate.accounts.tasks.notify_*": {"queue": "notify"}, 

931 "weblate.accounts.tasks.send_mails": {"queue": "notify"}, 

932 "weblate.addons.tasks.addon_change": {"queue": "notify"}, 

933 "weblate.utils.tasks.settings_backup": {"queue": "backup"}, 

934 "weblate.utils.tasks.database_backup": {"queue": "backup"}, 

935 "weblate.wladmin.tasks.backup": {"queue": "backup"}, 

936 "weblate.wladmin.tasks.backup_service": {"queue": "backup"}, 

937 "weblate.memory.tasks.*": {"queue": "memory"}, 

938} 

939 

940# CORS allowed origins 

941CORS_ALLOWED_ORIGINS = [] 

942CORS_URLS_REGEX = rf"^{URL_PREFIX}/api/.*$" 

943 

944# Enable plain database backups 

945DATABASE_BACKUP = "plain" 

946 

947# Enable auto updating 

948AUTO_UPDATE = False 

949 

950# PGP commits signing 

951WEBLATE_GPG_IDENTITY = None 

952 

953# Third party services integration 

954MATOMO_SITE_ID = None 

955MATOMO_URL = None 

956GOOGLE_ANALYTICS_ID = None 

957SENTRY_DSN = None 

958SENTRY_ENVIRONMENT = SITE_DOMAIN