| 1 | # ruff: noqa: F401, E402
|
|---|
| 2 | """
|
|---|
| 3 | Django settings for the NomNom test project.
|
|---|
| 4 |
|
|---|
| 5 | Manually maintained, similar to the settings.py.jinja2 template in convention-template.
|
|---|
| 6 |
|
|---|
| 7 | For more information on this file, see
|
|---|
| 8 | https://docs.djangoproject.com/en/5.0/topics/settings/
|
|---|
| 9 |
|
|---|
| 10 | For the full list of settings and their values, see
|
|---|
| 11 | https://docs.djangoproject.com/en/5.0/ref/settings/
|
|---|
| 12 |
|
|---|
| 13 | To enable this, set DJANGO_SETTINGS_MODULE=nomnom_dev.settings
|
|---|
| 14 | """
|
|---|
| 15 |
|
|---|
| 16 | import os
|
|---|
| 17 | import subprocess
|
|---|
| 18 | from pathlib import Path
|
|---|
| 19 |
|
|---|
| 20 | import bleach.sanitizer
|
|---|
| 21 | import djp
|
|---|
| 22 | import structlog
|
|---|
| 23 |
|
|---|
| 24 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|---|
| 25 | BASE_DIR = Path(__file__).resolve().parent.parent
|
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 | # the dev server settings perform some additional environment surgery to ensure we have the current
|
|---|
| 29 | # ports for redis, mailcatcher, and the DB
|
|---|
| 30 | def get_docker_port(service, port):
|
|---|
| 31 | """
|
|---|
| 32 | Get the port for a service in the docker-compose file.
|
|---|
| 33 | """
|
|---|
| 34 | res = subprocess.run(
|
|---|
| 35 | f"docker compose port '{service}' {port}",
|
|---|
| 36 | capture_output=True,
|
|---|
| 37 | check=True,
|
|---|
| 38 | shell=True,
|
|---|
| 39 | )
|
|---|
| 40 | port = res.stdout.decode("utf-8").strip().split(":")[-1]
|
|---|
| 41 | return int(port)
|
|---|
| 42 |
|
|---|
| 43 |
|
|---|
| 44 | os.environ["NOM_DB_PORT"] = str(get_docker_port("db", "5432"))
|
|---|
| 45 | os.environ["NOM_REDIS_PORT"] = str(get_docker_port("redis", "6379"))
|
|---|
| 46 | os.environ["NOM_EMAIL_PORT"] = str(get_docker_port("mailcatcher", "1025"))
|
|---|
| 47 |
|
|---|
| 48 | # import the system configuration from the application
|
|---|
| 49 | from nomnom.convention import system_configuration as cfg
|
|---|
| 50 |
|
|---|
| 51 | # Quick-start development settings - unsuitable for production
|
|---|
| 52 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
|---|
| 53 |
|
|---|
| 54 | # SECURITY WARNING: keep the secret key used in production secret!
|
|---|
| 55 | SECRET_KEY = cfg.secret_key
|
|---|
| 56 |
|
|---|
| 57 | # SECURITY WARNING: don't run with debug turned on in production!
|
|---|
| 58 | DEBUG = cfg.debug
|
|---|
| 59 | TEMPLATE_DEBUG = cfg.debug
|
|---|
| 60 |
|
|---|
| 61 |
|
|---|
| 62 | class InvalidStringShowWarning(str):
|
|---|
| 63 | def __mod__(self, other):
|
|---|
| 64 | logger = structlog.get_logger(__name__)
|
|---|
| 65 | logger.warning(
|
|---|
| 66 | f"In template, undefined variable or unknown value for: '{other}'"
|
|---|
| 67 | )
|
|---|
| 68 | return ""
|
|---|
| 69 |
|
|---|
| 70 | def __bool__(self): # if using Python 2, use __nonzero__ instead
|
|---|
| 71 | # make the template tag `default` use its fallback value
|
|---|
| 72 | return False
|
|---|
| 73 |
|
|---|
| 74 |
|
|---|
| 75 | ALLOWED_HOSTS = cfg.allowed_hosts
|
|---|
| 76 |
|
|---|
| 77 | CSRF_TRUSTED_ORIGINS = [f"https://{h}" for h in ALLOWED_HOSTS] if ALLOWED_HOSTS else []
|
|---|
| 78 |
|
|---|
| 79 | # Application definition
|
|---|
| 80 |
|
|---|
| 81 | INSTALLED_APPS = [
|
|---|
| 82 | "nomnom.apps.NomnomAdminConfig",
|
|---|
| 83 | "django.contrib.auth",
|
|---|
| 84 | "django.contrib.contenttypes",
|
|---|
| 85 | "django.contrib.humanize",
|
|---|
| 86 | "django.contrib.sessions",
|
|---|
| 87 | "django.contrib.messages",
|
|---|
| 88 | "django.contrib.sites",
|
|---|
| 89 | # use whitenoise to serve static files, instead of django's builtin
|
|---|
| 90 | "whitenoise.runserver_nostatic",
|
|---|
| 91 | "django.contrib.staticfiles",
|
|---|
| 92 | # deferred tasks
|
|---|
| 93 | "django_celery_results",
|
|---|
| 94 | "django_celery_beat",
|
|---|
| 95 | # debug helper
|
|---|
| 96 | "django_extensions",
|
|---|
| 97 | "django_browser_reload",
|
|---|
| 98 | # debugging
|
|---|
| 99 | "debug_toolbar",
|
|---|
| 100 | # to render markdown to HTML in templates
|
|---|
| 101 | "markdownify.apps.MarkdownifyConfig",
|
|---|
| 102 | # OAuth login
|
|---|
| 103 | "social_django",
|
|---|
| 104 | # Admin filtering enhancements
|
|---|
| 105 | "admin_auto_filters",
|
|---|
| 106 | # Admin forms
|
|---|
| 107 | "django_admin_action_forms",
|
|---|
| 108 | # Admin audit logging
|
|---|
| 109 | "logentry_admin",
|
|---|
| 110 | # Theming
|
|---|
| 111 | "django_bootstrap5",
|
|---|
| 112 | "bootstrap",
|
|---|
| 113 | "fontawesome",
|
|---|
| 114 | # Fonts
|
|---|
| 115 | "django_google_fonts",
|
|---|
| 116 | # HTMX support
|
|---|
| 117 | "django_htmx",
|
|---|
| 118 | # A healthcheck
|
|---|
| 119 | "watchman",
|
|---|
| 120 | # Markdown field support
|
|---|
| 121 | "markdownfield",
|
|---|
| 122 | # Feature flags
|
|---|
| 123 | "waffle",
|
|---|
| 124 | # Structured logging
|
|---|
| 125 | "django_structlog",
|
|---|
| 126 | # the convention theme; this MUST come before the nominate app, so that its templates can
|
|---|
| 127 | # override the nominate ones.
|
|---|
| 128 | "nomnom_dev",
|
|---|
| 129 | "nomnom.base",
|
|---|
| 130 | # The nominating and voting app
|
|---|
| 131 | "nomnom.nominate",
|
|---|
| 132 | "nomnom.canonicalize",
|
|---|
| 133 | # Advisory Votes
|
|---|
| 134 | "nomnom.advise",
|
|---|
| 135 | # The hugo packet app
|
|---|
| 136 | "nomnom.hugopacket",
|
|---|
| 137 | # Convention admin utilities and seeding commands
|
|---|
| 138 | "nomnom.convention_admin",
|
|---|
| 139 | ]
|
|---|
| 140 |
|
|---|
| 141 | SITE_ID = 1
|
|---|
| 142 |
|
|---|
| 143 | NOMNOM_ALLOW_USERNAME_LOGIN_FOR_MEMBERS = cfg.allow_username_login
|
|---|
| 144 |
|
|---|
| 145 | AUTHENTICATION_BACKENDS = [
|
|---|
| 146 | # NOTE: the nomnom.nominate.apps.AppConfig.ready() hook will install handlers in this, as the first
|
|---|
| 147 | # set. Any handler in here will be superseded by those.
|
|---|
| 148 | #
|
|---|
| 149 | # Uncomment following if you want to access the admin
|
|---|
| 150 | "django.contrib.auth.backends.ModelBackend",
|
|---|
| 151 | ]
|
|---|
| 152 |
|
|---|
| 153 | MIDDLEWARE = [
|
|---|
| 154 | "debug_toolbar.middleware.DebugToolbarMiddleware",
|
|---|
| 155 | "django.middleware.security.SecurityMiddleware",
|
|---|
| 156 | "whitenoise.middleware.WhiteNoiseMiddleware",
|
|---|
| 157 | "django.contrib.sessions.middleware.SessionMiddleware",
|
|---|
| 158 | "django.middleware.common.CommonMiddleware",
|
|---|
| 159 | "django.middleware.csrf.CsrfViewMiddleware",
|
|---|
| 160 | "django.contrib.auth.middleware.AuthenticationMiddleware",
|
|---|
| 161 | "django.contrib.messages.middleware.MessageMiddleware",
|
|---|
| 162 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|---|
| 163 | "django_browser_reload.middleware.BrowserReloadMiddleware",
|
|---|
| 164 | "social_django.middleware.SocialAuthExceptionMiddleware",
|
|---|
| 165 | "django_htmx.middleware.HtmxMiddleware",
|
|---|
| 166 | "nomnom.middleware.HtmxMessageMiddleware",
|
|---|
| 167 | "waffle.middleware.WaffleMiddleware",
|
|---|
| 168 | "django_structlog.middlewares.RequestMiddleware",
|
|---|
| 169 | ]
|
|---|
| 170 |
|
|---|
| 171 | DJANGO_STRUCTLOG_CELERY_ENABLED = True
|
|---|
| 172 |
|
|---|
| 173 | ROOT_URLCONF = "nomnom_dev.urls"
|
|---|
| 174 |
|
|---|
| 175 | TEMPLATES = [
|
|---|
| 176 | {
|
|---|
| 177 | "BACKEND": "django.template.backends.django.DjangoTemplates",
|
|---|
| 178 | "DIRS": [],
|
|---|
| 179 | "APP_DIRS": True,
|
|---|
| 180 | "OPTIONS": {
|
|---|
| 181 | "context_processors": [
|
|---|
| 182 | "django.template.context_processors.debug",
|
|---|
| 183 | "django.template.context_processors.request",
|
|---|
| 184 | "django.contrib.auth.context_processors.auth",
|
|---|
| 185 | "django.contrib.messages.context_processors.messages",
|
|---|
| 186 | "social_django.context_processors.backends",
|
|---|
| 187 | "social_django.context_processors.login_redirect",
|
|---|
| 188 | "nomnom.nominate.context_processors.site",
|
|---|
| 189 | "nomnom.nominate.context_processors.inject_login_form",
|
|---|
| 190 | ],
|
|---|
| 191 | "string_if_invalid": InvalidStringShowWarning("%s"),
|
|---|
| 192 | },
|
|---|
| 193 | },
|
|---|
| 194 | ]
|
|---|
| 195 |
|
|---|
| 196 | WSGI_APPLICATION = "nomnom_dev.wsgi.application"
|
|---|
| 197 |
|
|---|
| 198 |
|
|---|
| 199 | # Database
|
|---|
| 200 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
|---|
| 201 |
|
|---|
| 202 | DATABASES = {
|
|---|
| 203 | "default": {
|
|---|
| 204 | "ENGINE": "django.db.backends.postgresql",
|
|---|
| 205 | "NAME": cfg.db.name,
|
|---|
| 206 | "USER": cfg.db.user,
|
|---|
| 207 | "PASSWORD": cfg.db.password,
|
|---|
| 208 | "HOST": cfg.db.host,
|
|---|
| 209 | "PORT": str(cfg.db.port),
|
|---|
| 210 | "DISABLE_SERVER_SIDE_CURSORS": True,
|
|---|
| 211 | }
|
|---|
| 212 | }
|
|---|
| 213 |
|
|---|
| 214 | # reuse some of those DB connections
|
|---|
| 215 | if not DEBUG:
|
|---|
| 216 | CONN_HEALTH_CHECKS = True
|
|---|
| 217 | CONN_MAX_AGE = 600
|
|---|
| 218 |
|
|---|
| 219 | CACHES = {
|
|---|
| 220 | "default": {
|
|---|
| 221 | "BACKEND": "django.core.cache.backends.redis.RedisCache",
|
|---|
| 222 | "LOCATION": f"redis://{cfg.redis.host}:{cfg.redis.port}",
|
|---|
| 223 | },
|
|---|
| 224 | }
|
|---|
| 225 |
|
|---|
| 226 | # Password validation
|
|---|
| 227 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
|---|
| 228 |
|
|---|
| 229 | AUTH_PASSWORD_VALIDATORS = [
|
|---|
| 230 | {
|
|---|
| 231 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
|---|
| 232 | },
|
|---|
| 233 | {
|
|---|
| 234 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
|---|
| 235 | },
|
|---|
| 236 | {
|
|---|
| 237 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
|---|
| 238 | },
|
|---|
| 239 | {
|
|---|
| 240 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
|---|
| 241 | },
|
|---|
| 242 | ]
|
|---|
| 243 |
|
|---|
| 244 | LOGOUT_REDIRECT_URL = "index"
|
|---|
| 245 |
|
|---|
| 246 | # we are using postgres, so this is recommended in the docs.
|
|---|
| 247 | SOCIAL_AUTH_JSONFIELD_ENABLED = True
|
|---|
| 248 |
|
|---|
| 249 | # If using social authentication, configure it here. See the social_django documentation for
|
|---|
| 250 | # details.
|
|---|
| 251 |
|
|---|
| 252 | # Common social auth settings
|
|---|
| 253 | # Can't use the backend-specific one because of https://github.com/python-social-auth/social-core/issues/875
|
|---|
| 254 | # SOCIAL_AUTH_CLYDE_LOGIN_ERROR_URL = "nominate:login_error"
|
|---|
| 255 | SOCIAL_AUTH_LOGIN_ERROR_URL = "election:login_error"
|
|---|
| 256 |
|
|---|
| 257 | SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ["username", "first_name", "email"]
|
|---|
| 258 |
|
|---|
| 259 | # This is a probably-okay social auth pipeline, but you may need to adjust it for your needs.
|
|---|
| 260 | # See the social_django documentation for details.
|
|---|
| 261 | SOCIAL_AUTH_PIPELINE = [
|
|---|
| 262 | "social_core.pipeline.social_auth.social_details",
|
|---|
| 263 | "social_core.pipeline.social_auth.social_uid",
|
|---|
| 264 | "social_core.pipeline.social_auth.auth_allowed",
|
|---|
| 265 | "social_core.pipeline.social_auth.social_user",
|
|---|
| 266 | "social_core.pipeline.user.get_username",
|
|---|
| 267 | "social_core.pipeline.user.create_user",
|
|---|
| 268 | "social_core.pipeline.social_auth.associate_user",
|
|---|
| 269 | "social_core.pipeline.social_auth.load_extra_data",
|
|---|
| 270 | "social_core.pipeline.user.user_details",
|
|---|
| 271 | "nomnom.nominate.social_auth.pipeline.get_wsfs_permissions",
|
|---|
| 272 | "nomnom.nominate.social_auth.pipeline.set_user_wsfs_membership",
|
|---|
| 273 | "nomnom.nominate.social_auth.pipeline.normalize_date_fields",
|
|---|
| 274 | "nomnom.nominate.social_auth.pipeline.restrict_wsfs_permissions_by_date",
|
|---|
| 275 | "nomnom.nominate.social_auth.pipeline.add_election_permissions",
|
|---|
| 276 | ]
|
|---|
| 277 | # Internationalization
|
|---|
| 278 | # https://docs.djangoproject.com/en/5.0/topics/i18n/
|
|---|
| 279 |
|
|---|
| 280 | LANGUAGE_CODE = "en-us"
|
|---|
| 281 |
|
|---|
| 282 | TIME_ZONE = "UTC"
|
|---|
| 283 |
|
|---|
| 284 | USE_I18N = True
|
|---|
| 285 |
|
|---|
| 286 | USE_TZ = True
|
|---|
| 287 |
|
|---|
| 288 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|---|
| 289 | USE_X_FORWARDED_HOST = True
|
|---|
| 290 | USE_X_FORWARDED_PORT = True
|
|---|
| 291 |
|
|---|
| 292 | # Static files (CSS, JavaScript, Images)
|
|---|
| 293 | # https://docs.djangoproject.com/en/5.0/howto/static-files/
|
|---|
| 294 |
|
|---|
| 295 | STATIC_URL = "static/"
|
|---|
| 296 | STATIC_ROOT = cfg.static_file_root
|
|---|
| 297 | GOOGLE_FONTS_DIR = BASE_DIR / "nomnom_dev" / "static" / "google_fonts"
|
|---|
| 298 | STATICFILES_DIRS = [GOOGLE_FONTS_DIR]
|
|---|
| 299 |
|
|---|
| 300 | # Default primary key field type
|
|---|
| 301 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
|---|
| 302 |
|
|---|
| 303 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|---|
| 304 |
|
|---|
| 305 | # Async tasks
|
|---|
| 306 | CELERY_RESULT_BACKEND = "django-db"
|
|---|
| 307 | CELERY_CACHE_BACKEND = "default"
|
|---|
| 308 | CELERY_BROKER_URL = f"redis://{cfg.redis.host}:{cfg.redis.port}"
|
|---|
| 309 | CELERY_TIMEZONE = "America/Los_Angeles"
|
|---|
| 310 | CELERY_TASK_TRACK_STARTED = True
|
|---|
| 311 | CELERY_TASK_TIME_LIMIT = 30 * 60
|
|---|
| 312 | CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
|---|
| 313 |
|
|---|
| 314 | # Presentation
|
|---|
| 315 | ADMIN_MANAGED_ATTRIBUTES = bleach.sanitizer.ALLOWED_ATTRIBUTES.copy()
|
|---|
| 316 | ADMIN_MANAGED_ATTRIBUTES.update(
|
|---|
| 317 | {
|
|---|
| 318 | "span": bleach.sanitizer.ALLOWED_ATTRIBUTES.get("span", []) + ["lang"],
|
|---|
| 319 | "p": bleach.sanitizer.ALLOWED_ATTRIBUTES.get("p", []) + ["lang"],
|
|---|
| 320 | "div": bleach.sanitizer.ALLOWED_ATTRIBUTES.get("div", []) + ["lang"],
|
|---|
| 321 | }
|
|---|
| 322 | )
|
|---|
| 323 | ADMIN_ALERT_ATTRIBUTES = ADMIN_MANAGED_ATTRIBUTES.copy()
|
|---|
| 324 | ADMIN_ALERT_ATTRIBUTES["div"] += ["class", "role"]
|
|---|
| 325 |
|
|---|
| 326 | MARKDOWNIFY = {
|
|---|
| 327 | "default": {
|
|---|
| 328 | "WHITELIST_TAGS": bleach.sanitizer.ALLOWED_TAGS | {"p", "h4", "h5"},
|
|---|
| 329 | },
|
|---|
| 330 | "admin-content": {
|
|---|
| 331 | "WHITELIST_TAGS": bleach.sanitizer.ALLOWED_TAGS | {"p", "h4", "h5", "span"},
|
|---|
| 332 | "WHITELIST_ATTRS": ADMIN_MANAGED_ATTRIBUTES,
|
|---|
| 333 | },
|
|---|
| 334 | "admin-alert": {
|
|---|
| 335 | "WHITELIST_TAGS": bleach.sanitizer.ALLOWED_TAGS
|
|---|
| 336 | | {"p", "h4", "h5", "span", "div"},
|
|---|
| 337 | "WHITELIST_ATTRS": ADMIN_ALERT_ATTRIBUTES,
|
|---|
| 338 | },
|
|---|
| 339 | "admin-label": {
|
|---|
| 340 | # no block-level elements
|
|---|
| 341 | "WHITELIST_TAGS": bleach.sanitizer.ALLOWED_TAGS
|
|---|
| 342 | | {"span"} - {"blockquote", "ol", "li", "ul"}
|
|---|
| 343 | | {"s"},
|
|---|
| 344 | "WHITELIST_ATTRS": ADMIN_MANAGED_ATTRIBUTES,
|
|---|
| 345 | },
|
|---|
| 346 | }
|
|---|
| 347 |
|
|---|
| 348 | MARKDOWN_EXTENSIONS = [
|
|---|
| 349 | "pymdownx.tilde",
|
|---|
| 350 | ]
|
|---|
| 351 |
|
|---|
| 352 |
|
|---|
| 353 | BOOTSTRAP5 = {
|
|---|
| 354 | "field_renderers": {
|
|---|
| 355 | "default": "django_bootstrap5.renderers.FieldRenderer",
|
|---|
| 356 | "blank-safe": "nomnom.nominate.renderers.BlankSafeFieldRenderer",
|
|---|
| 357 | },
|
|---|
| 358 | }
|
|---|
| 359 |
|
|---|
| 360 | GOOGLE_FONTS = [
|
|---|
| 361 | "Roboto",
|
|---|
| 362 | "Roboto Slab",
|
|---|
| 363 | "Gruppo",
|
|---|
| 364 | ]
|
|---|
| 365 |
|
|---|
| 366 | # Email
|
|---|
| 367 | EMAIL_HOST = cfg.email.host
|
|---|
| 368 | EMAIL_PORT = cfg.email.port
|
|---|
| 369 | EMAIL_HOST_USER = cfg.email.host_user
|
|---|
| 370 | EMAIL_HOST_PASSWORD = cfg.email.host_password
|
|---|
| 371 | EMAIL_USE_TLS = cfg.email.use_tls
|
|---|
| 372 |
|
|---|
| 373 | # Structlog configuration - must come before LOGGING
|
|---|
| 374 | structlog.configure(
|
|---|
| 375 | processors=[
|
|---|
| 376 | structlog.contextvars.merge_contextvars,
|
|---|
| 377 | structlog.stdlib.filter_by_level,
|
|---|
| 378 | structlog.processors.TimeStamper(fmt="iso"),
|
|---|
| 379 | structlog.stdlib.add_logger_name,
|
|---|
| 380 | structlog.stdlib.add_log_level,
|
|---|
| 381 | structlog.stdlib.PositionalArgumentsFormatter(),
|
|---|
| 382 | structlog.processors.StackInfoRenderer(),
|
|---|
| 383 | structlog.processors.UnicodeDecoder(),
|
|---|
| 384 | structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
|---|
| 385 | ],
|
|---|
| 386 | logger_factory=structlog.stdlib.LoggerFactory(),
|
|---|
| 387 | wrapper_class=structlog.stdlib.BoundLogger,
|
|---|
| 388 | cache_logger_on_first_use=True,
|
|---|
| 389 | )
|
|---|
| 390 |
|
|---|
| 391 | LOGGING = {
|
|---|
| 392 | "version": 1,
|
|---|
| 393 | "disable_existing_loggers": False,
|
|---|
| 394 | "filters": {
|
|---|
| 395 | "require_debug_true": {
|
|---|
| 396 | "()": "django.utils.log.RequireDebugTrue",
|
|---|
| 397 | },
|
|---|
| 398 | },
|
|---|
| 399 | "formatters": {
|
|---|
| 400 | "plain_console": {
|
|---|
| 401 | "()": "structlog.stdlib.ProcessorFormatter",
|
|---|
| 402 | "processors": [
|
|---|
| 403 | structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
|---|
| 404 | structlog.dev.ConsoleRenderer(colors=True),
|
|---|
| 405 | ],
|
|---|
| 406 | "foreign_pre_chain": [
|
|---|
| 407 | structlog.contextvars.merge_contextvars,
|
|---|
| 408 | structlog.processors.TimeStamper(fmt="iso"),
|
|---|
| 409 | structlog.stdlib.add_log_level,
|
|---|
| 410 | structlog.stdlib.add_logger_name,
|
|---|
| 411 | ],
|
|---|
| 412 | },
|
|---|
| 413 | "json": {
|
|---|
| 414 | "()": "structlog.stdlib.ProcessorFormatter",
|
|---|
| 415 | "processors": [
|
|---|
| 416 | structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
|---|
| 417 | structlog.processors.JSONRenderer(),
|
|---|
| 418 | ],
|
|---|
| 419 | "foreign_pre_chain": [
|
|---|
| 420 | structlog.contextvars.merge_contextvars,
|
|---|
| 421 | structlog.processors.TimeStamper(fmt="iso"),
|
|---|
| 422 | structlog.stdlib.add_log_level,
|
|---|
| 423 | structlog.stdlib.add_logger_name,
|
|---|
| 424 | ],
|
|---|
| 425 | },
|
|---|
| 426 | },
|
|---|
| 427 | "handlers": {
|
|---|
| 428 | "console": {
|
|---|
| 429 | "level": "INFO",
|
|---|
| 430 | "class": "logging.StreamHandler",
|
|---|
| 431 | "formatter": "plain_console" if DEBUG else "json",
|
|---|
| 432 | },
|
|---|
| 433 | "debug_console": {
|
|---|
| 434 | "level": "DEBUG",
|
|---|
| 435 | "filters": ["require_debug_true"],
|
|---|
| 436 | "class": "logging.StreamHandler",
|
|---|
| 437 | "formatter": "plain_console",
|
|---|
| 438 | },
|
|---|
| 439 | "json_file": {
|
|---|
| 440 | "level": "DEBUG",
|
|---|
| 441 | "class": "logging.handlers.RotatingFileHandler",
|
|---|
| 442 | "filename": BASE_DIR / ".local/logs" / "json.log",
|
|---|
| 443 | "maxBytes": 10485760, # 10MB
|
|---|
| 444 | "backupCount": 5,
|
|---|
| 445 | "formatter": "json",
|
|---|
| 446 | },
|
|---|
| 447 | },
|
|---|
| 448 | "loggers": {
|
|---|
| 449 | "django.db.backends": {
|
|---|
| 450 | "level": os.environ.get("DJANGO_DB_LOG_LEVEL", "INFO"),
|
|---|
| 451 | "handlers": ["debug_console"],
|
|---|
| 452 | },
|
|---|
| 453 | "django_structlog": {
|
|---|
| 454 | "handlers": ["console", "json_file"],
|
|---|
| 455 | "level": "INFO",
|
|---|
| 456 | },
|
|---|
| 457 | "django.server": {
|
|---|
| 458 | "handlers": ["console", "json_file"],
|
|---|
| 459 | "level": "INFO",
|
|---|
| 460 | "propagate": False,
|
|---|
| 461 | },
|
|---|
| 462 | "django.request": {
|
|---|
| 463 | "handlers": ["console", "json_file"],
|
|---|
| 464 | "level": "INFO",
|
|---|
| 465 | "propagate": False,
|
|---|
| 466 | },
|
|---|
| 467 | # Root logger - catches all logs not matched by more specific loggers
|
|---|
| 468 | "": {
|
|---|
| 469 | "handlers": ["console", "json_file"],
|
|---|
| 470 | "level": "DEBUG" if DEBUG else "INFO",
|
|---|
| 471 | },
|
|---|
| 472 | },
|
|---|
| 473 | }
|
|---|
| 474 |
|
|---|
| 475 |
|
|---|
| 476 | # Sentry
|
|---|
| 477 | if cfg.sentry_sdk.dsn is not None:
|
|---|
| 478 | # settings.py
|
|---|
| 479 | import sentry_sdk
|
|---|
| 480 |
|
|---|
| 481 | sentry_sdk.init(
|
|---|
| 482 | dsn=cfg.sentry_sdk.dsn,
|
|---|
| 483 | # Set traces_sample_rate to 1.0 to capture 100%
|
|---|
| 484 | # of transactions for performance monitoring.
|
|---|
| 485 | traces_sample_rate=1.0,
|
|---|
| 486 | # Set profiles_sample_rate to 1.0 to profile 100%
|
|---|
| 487 | # of sampled transactions.
|
|---|
| 488 | # We recommend adjusting this value in production.
|
|---|
| 489 | profiles_sample_rate=1.0,
|
|---|
| 490 | # Our environment
|
|---|
| 491 | environment=cfg.sentry_sdk.environment,
|
|---|
| 492 | # include the user and client IP
|
|---|
| 493 | send_default_pii=True,
|
|---|
| 494 | )
|
|---|
| 495 |
|
|---|
| 496 | if cfg.debug:
|
|---|
| 497 | import icecream
|
|---|
| 498 |
|
|---|
| 499 | icecream.install()
|
|---|
| 500 |
|
|---|
| 501 | if cfg.debug:
|
|---|
| 502 | INTERNAL_IPS = [
|
|---|
| 503 | "127.0.0.1",
|
|---|
| 504 | ]
|
|---|
| 505 |
|
|---|
| 506 | # Seed data can come from here:
|
|---|
| 507 | FIXTURE_DIRS = [BASE_DIR / "seed"]
|
|---|
| 508 |
|
|---|
| 509 | DEBUG_TOOLBAR_ENABLED = True
|
|---|
| 510 | DEBUG_PROPAGATE_EXCEPTIONS = True
|
|---|
| 511 | try:
|
|---|
| 512 | from .settings_override import * # noqa: F403
|
|---|
| 513 | except ImportError:
|
|---|
| 514 | ...
|
|---|
| 515 |
|
|---|
| 516 | djp.settings(globals())
|
|---|