2026-01-27 08:24:22 +00:00
|
|
|
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
# See the LICENSE file for details.
|
|
|
|
|
|
2023-11-15 07:01:52 +00:00
|
|
|
"""Development settings"""
|
2024-03-06 15:09:50 +00:00
|
|
|
|
2024-03-06 15:18:30 +00:00
|
|
|
import os
|
|
|
|
|
|
2022-11-29 21:17:42 +00:00
|
|
|
from .common import * # noqa
|
|
|
|
|
|
2023-11-15 07:01:52 +00:00
|
|
|
DEBUG = True
|
2022-11-29 21:17:42 +00:00
|
|
|
|
2023-11-15 07:01:52 +00:00
|
|
|
# Debug Toolbar settings
|
2024-03-18 08:57:02 +00:00
|
|
|
INSTALLED_APPS += ("debug_toolbar",) # noqa
|
|
|
|
|
MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) # noqa
|
2023-05-29 06:41:16 +00:00
|
|
|
|
2023-11-15 07:01:52 +00:00
|
|
|
DEBUG_TOOLBAR_PATCH_SETTINGS = False
|
2023-05-29 06:41:16 +00:00
|
|
|
|
2023-11-15 07:01:52 +00:00
|
|
|
# Only show emails in console don't send it to smtp
|
2025-09-29 13:45:32 +00:00
|
|
|
EMAIL_BACKEND = os.environ.get("EMAIL_BACKEND", "django.core.mail.backends.console.EmailBackend")
|
2022-11-29 21:17:42 +00:00
|
|
|
|
|
|
|
|
CACHES = {
|
|
|
|
|
"default": {
|
2024-03-06 15:09:50 +00:00
|
|
|
"BACKEND": "django_redis.cache.RedisCache",
|
2024-03-18 08:57:02 +00:00
|
|
|
"LOCATION": REDIS_URL, # noqa
|
2024-11-21 12:12:44 +00:00
|
|
|
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
|
2022-11-29 21:17:42 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INTERNAL_IPS = ("127.0.0.1",)
|
|
|
|
|
|
2023-02-21 06:01:43 +00:00
|
|
|
MEDIA_URL = "/uploads/"
|
2024-03-18 08:57:02 +00:00
|
|
|
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads") # noqa
|
2023-02-21 06:01:43 +00:00
|
|
|
|
2024-03-18 08:57:02 +00:00
|
|
|
LOG_DIR = os.path.join(BASE_DIR, "logs") # noqa
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(LOG_DIR):
|
|
|
|
|
os.makedirs(LOG_DIR)
|
|
|
|
|
|
|
|
|
|
LOGGING = {
|
|
|
|
|
"version": 1,
|
2025-04-11 12:29:19 +00:00
|
|
|
"disable_existing_loggers": True,
|
2024-03-18 08:57:02 +00:00
|
|
|
"formatters": {
|
|
|
|
|
"verbose": {
|
|
|
|
|
"format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
|
|
|
|
|
"style": "{",
|
2025-04-11 12:29:19 +00:00
|
|
|
},
|
|
|
|
|
"json": {
|
fix: Add docker pytest runner and fix bugs the suite surfaced (#9138)
* chore(api): add docker compose test runner
Adds docker-compose-test.yml at the repo root that boots an isolated
postgres / valkey / rabbitmq / minio stack with health checks and tmpfs
data dirs, then runs pytest against it and exits. Includes a usage doc
under apps/api/tests/RUNNING_TESTS.md and a pointer in AGENTS.md.
Prereq: ./setup.sh (generates apps/api/.env).
Usage:
docker compose -f docker-compose-test.yml up --build \
--abort-on-container-exit --exit-code-from api-tests
docker compose -f docker-compose-test.yml down -v
* fix(api): correct bugs surfaced by the pytest suite
Five small bugs caught by enabling the pytest contract suite end-to-end.
Each is independently justifiable:
- api/serializers/cycle.py + api/views/cycle.py: CycleCreateSerializer.validate
required project_id in the request body, but the view only ever passes
it through the URL kwarg. Cycle create/update via the public API was
returning 400 "Project ID is required". Read project_id from
serializer context (passed by the view) in addition to body/instance.
- app/views/api.py: ApiTokenEndpoint.get(pk) and patch(pk) did not filter
out is_service=True tokens, so a user could read and modify service
tokens through the user token endpoint. The list mode and delete
already filter is_service=False; aligned the other two.
- bgtasks/work_item_link_task.py: validate_url_ip checked hostname before
scheme, so file:///etc/passwd raised "No hostname found" instead of
the documented "Only HTTP and HTTPS" error. Swapped the order so the
scheme guard matches the docstring intent.
- utils/path_validator.py: get_allowed_hosts used `WEB_URL or APP_BASE_URL`
so when both are configured to different hosts (the standard local
setup: WEB_URL=:8000, APP_BASE_URL=:3000), only one was added to the
allow-list. Redirects to APP_BASE_URL then had their next_path stripped
because the host wasn't allowed. Include every configured base URL.
* chore(api): align pytest tests with current behavior, clear warnings
Test-side fixes paired with the product fixes in the previous commit, plus
deprecation cleanup that drops the test run from 104 warnings to 0.
Tests:
- tests/contract/api/test_cycles.py: project fixture sets cycle_view=True;
the Project model defaults the flag to False, so cycle create/update
always tripped "Cycles are not enabled for this project".
- tests/contract/app/test_authentication.py: next_path uses "/workspaces"
(validate_next_path rejects values without a leading slash and returns
empty, which dropped the path from the redirect URL).
- tests/unit/bg_tasks/test_copy_s3_objects.py: mocked sync_with_external_service
now returns description_json; the task unconditionally writes the value
back to the Issue, and Issue.description_json is NOT NULL on UPDATE.
- tests/unit/utils/test_url.py: three length-limit tests placed the URL at
char 970+ on a single line, which contains_url truncates away as ReDoS
defense (500-char per-line cap). Restructured to keep test intent intact
while staying inside the per-line window.
Warning cleanup (104 → 0):
- settings/common.py: removed USE_L10N=True (deprecated in Django 4.0,
removed in 5.0; default is True).
- celery.py, settings/local.py, settings/production.py: pythonjsonlogger
moved jsonlogger → json; update the import / formatter path.
2026-05-25 19:51:37 +00:00
|
|
|
"()": "pythonjsonlogger.json.JsonFormatter",
|
2025-04-11 12:29:19 +00:00
|
|
|
"fmt": "%(levelname)s %(asctime)s %(module)s %(name)s %(message)s",
|
|
|
|
|
},
|
2024-03-18 08:57:02 +00:00
|
|
|
},
|
|
|
|
|
"handlers": {
|
|
|
|
|
"console": {
|
|
|
|
|
"level": "DEBUG",
|
|
|
|
|
"class": "logging.StreamHandler",
|
2025-04-11 12:29:19 +00:00
|
|
|
"formatter": "json",
|
2024-11-21 12:12:44 +00:00
|
|
|
}
|
2024-03-18 08:57:02 +00:00
|
|
|
},
|
|
|
|
|
"loggers": {
|
2025-04-22 12:11:58 +00:00
|
|
|
"plane.api.request": {
|
|
|
|
|
"level": "INFO",
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"propagate": False,
|
|
|
|
|
},
|
2025-04-11 12:29:19 +00:00
|
|
|
"plane.api": {"level": "INFO", "handlers": ["console"], "propagate": False},
|
|
|
|
|
"plane.worker": {"level": "INFO", "handlers": ["console"], "propagate": False},
|
|
|
|
|
"plane.exception": {
|
|
|
|
|
"level": "ERROR",
|
2024-03-18 08:57:02 +00:00
|
|
|
"handlers": ["console"],
|
|
|
|
|
"propagate": False,
|
|
|
|
|
},
|
2025-04-22 12:11:58 +00:00
|
|
|
"plane.external": {
|
|
|
|
|
"level": "INFO",
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"propagate": False,
|
|
|
|
|
},
|
2026-03-03 14:05:34 +00:00
|
|
|
"plane.authentication": {
|
|
|
|
|
"level": "INFO",
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"propagate": False,
|
|
|
|
|
},
|
2025-11-28 10:46:48 +00:00
|
|
|
"plane.migrations": {
|
|
|
|
|
"level": "INFO",
|
|
|
|
|
"handlers": ["console"],
|
|
|
|
|
"propagate": False,
|
|
|
|
|
},
|
2024-03-18 08:57:02 +00:00
|
|
|
},
|
|
|
|
|
}
|