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
« 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
5# mypy: disable-error-code="var-annotated"
6from __future__ import annotations
8import os
9import platform
10from logging.handlers import SysLogHandler
12from weblate.api.spectacular import (
13 get_drf_settings,
14 get_drf_standardized_errors_settings,
15 get_spectacular_settings,
16)
18# Title of site to use
19SITE_TITLE = "Weblate"
21# Site domain
22SITE_DOMAIN = ""
24# Whether site uses https
25ENABLE_HTTPS = False
27# Site URL
28SITE_URL = "{}://{}".format("https" if ENABLE_HTTPS else "http", SITE_DOMAIN)
30#
31# Django settings for Weblate project.
32#
34DEBUG = True
36ADMINS: tuple[tuple[str, str], ...] = (
37 # ("Your Name", "your_email@example.com"),
38)
40MANAGERS = ADMINS
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}
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"
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"
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"
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)
146SITE_ID = 1
148# If you set this to False, Django will make some optimizations so as not
149# to load the internationalization machinery.
150USE_I18N = True
152# If you set this to False, Django will not use timezone-aware datetimes.
153USE_TZ = True
155# Type of automatic primary key, introduced in Django 3.2
156DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
158# URL prefix to use, please see documentation for more details
159URL_PREFIX = ""
161# Absolute filesystem path to the directory that will hold user-uploaded files.
162MEDIA_ROOT = os.path.join(DATA_DIR, "media")
164# URL that handles the media served from MEDIA_ROOT. Make sure to use a
165# trailing slash.
166MEDIA_URL = f"{URL_PREFIX}/media/"
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")
173# URL prefix for static files.
174STATIC_URL = f"{URL_PREFIX}/static/"
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)
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)
191# Make this unique, and don't share it with anybody.
192# You can generate it using weblate-generate-secret-key
193SECRET_KEY = ""
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]
214# GitHub username and token for sending pull requests.
215# Please see the documentation for more details.
216GITHUB_CREDENTIALS = {}
218# Azure DevOps username and token for sending pull requests.
219# Please see the documentation for more details.
220AZURE_DEVOPS_CREDENTIALS = {}
222# GitLab username and token for sending merge requests.
223# Please see the documentation for more details.
224GITLAB_CREDENTIALS = {}
226# Bitbucket username and token for sending merge requests.
227# Please see the documentation for more details.
228BITBUCKETSERVER_CREDENTIALS = {}
230# Bitbucket username, app-password and workspace for sending merge requests.
231# Please see the documentation for more details.
232BITBUCKETCLOUD_CREDENTIALS = {}
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)
248# Custom user model
249AUTH_USER_MODEL = "weblate_auth.User"
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"
258# Social auth backends setup
259SOCIAL_AUTH_GITHUB_KEY = ""
260SOCIAL_AUTH_GITHUB_SECRET = ""
261SOCIAL_AUTH_GITHUB_SCOPE = ["user:email"]
263SOCIAL_AUTH_GITHUB_ORG_KEY = ""
264SOCIAL_AUTH_GITHUB_ORG_SECRET = ""
265SOCIAL_AUTH_GITHUB_ORG_NAME = ""
267SOCIAL_AUTH_GITHUB_TEAM_KEY = ""
268SOCIAL_AUTH_GITHUB_TEAM_SECRET = ""
269SOCIAL_AUTH_GITHUB_TEAM_ID = ""
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 = ""
277SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY = ""
278SOCIAL_AUTH_BITBUCKET_OAUTH2_SECRET = ""
279SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY = True
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"}
286SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ""
287SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ""
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)
328# Custom authentication strategy
329SOCIAL_AUTH_STRATEGY = "weblate.accounts.strategy.WeblateStrategy"
331# Raise exceptions so that we can handle them later
332SOCIAL_AUTH_RAISE_EXCEPTIONS = True
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"
343# Value higher than 0 enables validation using zxcvbn
344PASSWORD_MINIMAL_STRENGTH = 0
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]
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 )
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]
383# Allow new user registrations
384REGISTRATION_OPEN = True
386# Shortcut for login required setting
387REQUIRE_LOGIN = False
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]
407ROOT_URLCONF = "weblate.urls"
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]
462# django_zxcvbn_password_validator integration
463if PASSWORD_MINIMAL_STRENGTH > 0:
464 INSTALLED_APPS.append("django_zxcvbn_password_validator")
466# Custom exception reporter to include some details
467DEFAULT_EXCEPTION_REPORTER_FILTER = "weblate.trans.debug.WeblateExceptionReporterFilter"
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
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
488DEFAULT_LOG = ["console" if DEBUG or not HAVE_SYSLOG else "syslog"]
489DEFAULT_LOGLEVEL = "DEBUG" if DEBUG else "INFO"
491# GELF TCP integration (Graylog)
492WEBLATE_LOG_GELF_HOST = None
494if WEBLATE_LOG_GELF_HOST:
495 DEFAULT_LOG.append("gelf")
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}
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 }
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 }
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
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
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"
650# Some security headers
651SECURE_BROWSER_XSS_FILTER = True
652X_FRAME_OPTIONS = "DENY"
653SECURE_CONTENT_TYPE_NOSNIFF = True
655# Optionally enable HSTS
656SECURE_HSTS_SECONDS = 31536000 if ENABLE_HTTPS else 0
657SECURE_HSTS_PRELOAD = ENABLE_HTTPS
658SECURE_HSTS_INCLUDE_SUBDOMAINS = ENABLE_HTTPS
660# HTTPS detection behind reverse proxy
661SECURE_PROXY_SSL_HEADER = None
663# URL of login
664LOGIN_URL = f"{URL_PREFIX}/accounts/login/"
666# URL of logout
667LOGOUT_URL = f"{URL_PREFIX}/accounts/logout/"
669# Default location for login
670LOGIN_REDIRECT_URL = f"{URL_PREFIX}/"
672# Opt-in for Django 6.0 default
673FORMS_URLFIELD_ASSUME_HTTPS = True
675# Anonymous user name
676ANONYMOUS_USER_NAME = "anonymous"
678# Reverse proxy settings
679IP_PROXY_HEADER = "HTTP_X_FORWARDED_FOR"
680IP_BEHIND_REVERSE_PROXY = False
681IP_PROXY_OFFSET = -1
683# Sending HTML in mails
684EMAIL_SEND_HTML = True
686# Subject of emails includes site title
687EMAIL_SUBJECT_PREFIX = f"[{SITE_TITLE}] "
689# Enable remote hooks
690ENABLE_HOOKS = True
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
697# Use simple language codes for default language/country combinations
698SIMPLIFY_LANGUAGES = True
700# Render forms using bootstrap
701CRISPY_ALLOWED_TEMPLATE_PACKS = ["bootstrap3", "bootstrap5"]
702CRISPY_TEMPLATE_PACK = "bootstrap3"
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# )
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# )
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# )
825# E-mail address that error messages come from.
826SERVER_EMAIL = "noreply@example.com"
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"
832# List of URLs your site is supposed to serve
833ALLOWED_HOSTS = ["*"]
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}
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"
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)
874# Fonts CDN URL
875FONTS_CDN_URL = None
877# Django compressor offline mode
878COMPRESS_OFFLINE = False
879COMPRESS_OFFLINE_CONTEXT = "weblate.utils.compress.offline_context"
880COMPRESS_CSS_HASHING_METHOD = "content"
882# Require login for all URLs
883if REQUIRE_LOGIN:
884 LOGIN_REQUIRED_URLS = (r"/(.*)$",)
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# )
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]
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
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}
940# CORS allowed origins
941CORS_ALLOWED_ORIGINS = []
942CORS_URLS_REGEX = rf"^{URL_PREFIX}/api/.*$"
944# Enable plain database backups
945DATABASE_BACKUP = "plain"
947# Enable auto updating
948AUTO_UPDATE = False
950# PGP commits signing
951WEBLATE_GPG_IDENTITY = None
953# Third party services integration
954MATOMO_SITE_ID = None
955MATOMO_URL = None
956GOOGLE_ANALYTICS_ID = None
957SENTRY_DSN = None
958SENTRY_ENVIRONMENT = SITE_DOMAIN