Files
HSAP/vendor/cvat/patches/base.py

825 lines
28 KiB
Python
Raw Normal View History

# Copyright (C) 2018-2022 Intel Corporation
# Copyright (C) CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT
"""
Django settings for CVAT project.
Generated by 'django-admin startproject' using Django 2.0.1.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import os
import sys
import tempfile
import urllib
from datetime import timedelta
from enum import Enum, IntEnum
from pathlib import Path
from attr.converters import to_bool
from corsheaders.defaults import default_headers
from django.core.exceptions import ImproperlyConfigured
from logstash_async.constants import constants as logstash_async_constants
from cvat import __version__
from cvat.apps.iam.password_validation import DEFAULT_MIN_PASSWORD_LENGTH
# Build paths inside the project like this: BASE_DIR / ...
BASE_DIR = Path(__file__).parents[2]
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
INTERNAL_IPS = ["127.0.0.1"]
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "")
def generate_secret_key():
"""
Creates secret_key.py in such a way that multiple processes calling
this will all end up with the same key (assuming that they share the
same "keys" directory).
"""
from django.utils.crypto import get_random_string
keys_dir = BASE_DIR / "keys"
keys_dir.mkdir(exist_ok=True)
secret_key_fname = "secret_key.py" # nosec
with tempfile.NamedTemporaryFile(mode="wt", dir=keys_dir, prefix=secret_key_fname + ".") as f:
chars = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"
f.write("SECRET_KEY = '{}'\n".format(get_random_string(50, chars)))
# Make sure the file contents are written before we link to it
# from the final location.
f.flush()
try:
os.link(f.name, keys_dir / secret_key_fname)
except FileExistsError:
# Somebody else created the secret key first.
# Discard ours and use theirs.
pass
if not SECRET_KEY:
sys.path.append(os.fspath(BASE_DIR))
try:
from keys.secret_key import SECRET_KEY # pylint: disable=unused-import
except ModuleNotFoundError:
generate_secret_key()
from keys.secret_key import SECRET_KEY
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django_rq",
"django_sendfile",
"dj_rest_auth",
"dj_rest_auth.registration",
"dj_pagination",
"django_filters",
"rest_framework",
"rest_framework.authtoken",
"rest_framework_api_key",
"drf_spectacular",
"django.contrib.sites",
"allauth",
"allauth.account",
"corsheaders",
"allauth.socialaccount",
"health_check",
"health_check.cache",
"health_check.db",
"health_check.contrib.psutil",
"cvat.apps.iam",
"cvat.apps.dataset_manager",
"cvat.apps.organizations",
"cvat.apps.engine",
"cvat.apps.dataset_repo",
"cvat.apps.lambda_manager",
"cvat.apps.webhooks",
"cvat.apps.health",
"cvat.apps.events",
"cvat.apps.quality_control",
"cvat.apps.redis_handler",
"cvat.apps.consensus",
"cvat.apps.access_tokens",
]
SITE_ID = 1
def parse_num_proxies(value: str | None) -> int | None:
if value in (None, ""):
return None
try:
num_proxies = int(value)
except (TypeError, ValueError):
raise ImproperlyConfigured("CVAT_NUM_PROXIES must be an integer")
if num_proxies < 0:
raise ImproperlyConfigured("CVAT_NUM_PROXIES must be a non-negative integer")
return num_proxies
REST_FRAMEWORK = {
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser",
],
"DEFAULT_RENDERER_CLASSES": [
"cvat.apps.engine.renderers.CVATAPIRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.AllowAny",
],
"DEFAULT_AUTHENTICATION_CLASSES": [
"cvat.apps.iam.no_auth.NoAuthAuthentication",
],
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning",
"ALLOWED_VERSIONS": ("2.0"),
"DEFAULT_VERSION": "2.0",
"VERSION_PARAM": "version",
"DEFAULT_PAGINATION_CLASS": "cvat.apps.engine.pagination.CustomPagination",
"PAGE_SIZE": 10,
"DEFAULT_FILTER_BACKENDS": (
"cvat.apps.engine.filters.SimpleFilter",
"cvat.apps.engine.filters.SearchFilter",
"cvat.apps.engine.filters.OrderingFilter",
"cvat.apps.engine.filters.JsonLogicFilter",
"cvat.apps.iam.filters.OrganizationFilterBackend",
),
"SEARCH_PARAM": "search",
# Disable default handling of the 'format' query parameter by REST framework
"URL_FORMAT_OVERRIDE": "scheme",
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/minute",
# dj-rest-auth views define this scope. Keep them unthrottled by default.
"dj_rest_auth": None,
},
"NUM_PROXIES": parse_num_proxies(os.getenv("CVAT_NUM_PROXIES", "0")),
"DEFAULT_METADATA_CLASS": "rest_framework.metadata.SimpleMetadata",
"DEFAULT_SCHEMA_CLASS": "cvat.apps.iam.schema.CustomAutoSchema",
"EXCEPTION_HANDLER": "cvat.apps.events.handlers.handle_viewset_exception",
}
REST_AUTH = {
"REGISTER_SERIALIZER": "cvat.apps.iam.serializers.RegisterSerializerEx",
"LOGIN_SERIALIZER": "cvat.apps.iam.serializers.LoginSerializerEx",
"PASSWORD_RESET_SERIALIZER": "cvat.apps.iam.serializers.PasswordResetSerializerEx",
# Define password-setting serializers explicitly so CVAT controls length limits
# instead of inheriting hardcoded third-party defaults.
"PASSWORD_RESET_CONFIRM_SERIALIZER": "cvat.apps.iam.serializers.PasswordResetConfirmSerializerEx",
"PASSWORD_CHANGE_SERIALIZER": "cvat.apps.iam.serializers.PasswordChangeSerializerEx",
"OLD_PASSWORD_FIELD_ENABLED": True,
}
ANALYTICS_ENABLED = to_bool(os.getenv("CVAT_ANALYTICS", False))
if ANALYTICS_ENABLED:
INSTALLED_APPS += ["cvat.apps.log_viewer"]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"cvat.apps.iam.middleware.SessionRefreshMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
# FIXME
# 'corsheaders.middleware.CorsPostCsrfMiddleware',
"django.contrib.auth.middleware.AuthenticationMiddleware",
"cvat.apps.iam.no_auth_middleware.NoAuthMiddleware",
"django.middleware.gzip.GZipMiddleware",
"cvat.apps.engine.middleware.RequestTrackingMiddleware",
"cvat.apps.engine.middleware.LastActivityMiddleware",
"crum.CurrentRequestUserMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"dj_pagination.middleware.PaginationMiddleware",
"cvat.apps.iam.middleware.ContextMiddleware",
"allauth.account.middleware.AccountMiddleware",
]
# HSAP iframe 嵌入标注画布(跨端口 8787 → 8080
X_FRAME_OPTIONS = "ALLOWALL"
UI_URL = ""
ROOT_URLCONF = "cvat.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
# IAM settings
IAM_TYPE = "BASIC"
IAM_BASE_EXCEPTION = None # a class which will be used by IAM to report errors
IAM_DEFAULT_ROLE = "user"
IAM_ADMIN_ROLE = "admin"
# Index in the list below corresponds to the priority (0 has highest priority)
IAM_ROLES = [IAM_ADMIN_ROLE, "user", "worker"]
IAM_OPA_URL = os.getenv("CVAT_OPA_URL", "http://opa:8181")
IAM_OPA_DATA_URL = f"{IAM_OPA_URL}/v1/data"
LOGIN_URL = "rest_login"
LOGIN_REDIRECT_URL = "/"
OBJECTS_NOT_RELATED_WITH_ORG = [
"user",
"lambda_function",
"lambda_request",
"server",
"request",
"access_token",
]
# ORG settings
ORG_INVITATION_CONFIRM = "No"
ORG_INVITATION_EXPIRY_DAYS = 7
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]
# https://github.com/pennersr/django-allauth
ACCOUNT_EMAIL_VERIFICATION = "none"
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
# set UI url to redirect after a successful e-mail confirmation
# changed from '/auth/login' to '/auth/email-confirmation' for email confirmation message
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = "/auth/email-confirmation"
ACCOUNT_EMAIL_VERIFICATION_SENT_REDIRECT_URL = "/auth/email-verification-sent"
INCORRECT_EMAIL_CONFIRMATION_URL = "/auth/incorrect-email-confirmation"
# Django-RQ
# https://github.com/rq/django-rq
class CVAT_QUEUES(Enum):
IMPORT_DATA = "import"
EXPORT_DATA = "export"
AUTO_ANNOTATION = "annotation"
WEBHOOKS = "webhooks"
NOTIFICATIONS = "notifications"
QUALITY_REPORTS = "quality_reports"
CLEANING = "cleaning"
CHUNKS = "chunks"
CONSENSUS = "consensus"
redis_inmem_host = os.getenv("CVAT_REDIS_INMEM_HOST", "localhost")
redis_inmem_port = os.getenv("CVAT_REDIS_INMEM_PORT", 6379)
redis_inmem_password = os.getenv("CVAT_REDIS_INMEM_PASSWORD", "")
class REDIS_INMEM_DATABASES(IntEnum):
RQ = 0
CACHE = 1
REDIS_INMEM_SETTINGS = {
"HOST": redis_inmem_host,
"PORT": redis_inmem_port,
"DB": REDIS_INMEM_DATABASES.RQ,
"PASSWORD": redis_inmem_password,
}
RQ_QUEUES = {
CVAT_QUEUES.IMPORT_DATA.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "4h",
# custom fields
"PARSED_JOB_ID_CLASS": "cvat.apps.engine.rq.ImportRequestId",
},
CVAT_QUEUES.EXPORT_DATA.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "4h",
# custom fields
"PARSED_JOB_ID_CLASS": "cvat.apps.engine.rq.ExportRequestId",
},
CVAT_QUEUES.AUTO_ANNOTATION.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "24h",
},
CVAT_QUEUES.WEBHOOKS.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "1h",
},
CVAT_QUEUES.NOTIFICATIONS.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "1h",
},
CVAT_QUEUES.QUALITY_REPORTS.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "1h",
# custom fields
"PARSED_JOB_ID_CLASS": "cvat.apps.quality_control.rq.QualityRequestId",
},
CVAT_QUEUES.CLEANING.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "2h",
},
CVAT_QUEUES.CHUNKS.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "5m",
},
CVAT_QUEUES.CONSENSUS.value: {
**REDIS_INMEM_SETTINGS,
"DEFAULT_TIMEOUT": "1h",
# custom fields
"PARSED_JOB_ID_CLASS": "cvat.apps.consensus.rq.ConsensusRequestId",
},
}
NUCLIO = {
"SCHEME": os.getenv("CVAT_NUCLIO_SCHEME", "http"),
"HOST": os.getenv("CVAT_NUCLIO_HOST", "localhost"),
"PORT": int(os.getenv("CVAT_NUCLIO_PORT", 8070)),
"DEFAULT_TIMEOUT": int(os.getenv("CVAT_NUCLIO_DEFAULT_TIMEOUT", 120)),
"FUNCTION_NAMESPACE": os.getenv("CVAT_NUCLIO_FUNCTION_NAMESPACE", "nuclio"),
"INVOKE_METHOD": os.getenv(
"CVAT_NUCLIO_INVOKE_METHOD",
default="dashboard" if "KUBERNETES_SERVICE_HOST" in os.environ else "direct",
),
}
assert NUCLIO["INVOKE_METHOD"] in {"dashboard", "direct"}
RQ_SHOW_ADMIN_LINK = True
RQ_EXCEPTION_HANDLERS = [
"cvat.apps.engine.views.rq_exception_handler",
"cvat.apps.events.handlers.handle_rq_exception",
]
PERIODIC_RQ_JOBS = [
{
"queue": CVAT_QUEUES.CLEANING.value,
"id": "clean_up_sessions",
"func": "cvat.apps.iam.utils.clean_up_sessions",
"cron_string": "0 0 * * *",
},
{
"queue": CVAT_QUEUES.CLEANING.value,
"id": "cron_export_cache_directory_cleanup",
"func": "cvat.apps.dataset_manager.cron.cleanup_export_cache_directory",
# Run twice a day (at midnight and at noon)
"cron_string": "0 0,12 * * *",
},
{
"queue": CVAT_QUEUES.CLEANING.value,
"id": "cron_tmp_directory_cleanup",
"func": "cvat.apps.dataset_manager.cron.cleanup_tmp_directory",
# Run once a day
"cron_string": "0 18 * * *",
},
{
"queue": CVAT_QUEUES.CLEANING.value,
"id": "clear_unusable_access_tokens",
"func": "cvat.apps.access_tokens.cron.clear_unusable_access_tokens",
"cron_string": "0 0 * * 0",
},
]
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {"min_length": DEFAULT_MIN_PASSWORD_LENGTH},
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
{
"NAME": "cvat.apps.iam.password_validation.MaximumLengthPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = os.getenv("TZ", "Etc/UTC")
USE_I18N = True
USE_TZ = True
CSRF_COOKIE_NAME = "csrftoken"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "static"
STATIC_ROOT.mkdir(parents=True, exist_ok=True)
# Make sure to update other config files when updating these directories
DATA_ROOT = BASE_DIR / "data"
MEDIA_DATA_ROOT = DATA_ROOT / "data"
MEDIA_DATA_ROOT.mkdir(parents=True, exist_ok=True)
CACHE_ROOT = DATA_ROOT / "cache"
CACHE_ROOT.mkdir(parents=True, exist_ok=True)
EXPORT_CACHE_ROOT = CACHE_ROOT / "export"
EXPORT_CACHE_ROOT.mkdir(parents=True, exist_ok=True)
EVENTS_LOCAL_DB_ROOT = BASE_DIR / "events"
EVENTS_LOCAL_DB_ROOT.mkdir(parents=True, exist_ok=True)
EVENTS_LOCAL_DB_FILE = Path(
EVENTS_LOCAL_DB_ROOT,
os.getenv("CVAT_EVENTS_LOCAL_DB_FILENAME", "events.db"),
)
EVENTS_LOCAL_DB_FILE.touch(exist_ok=True)
JOBS_ROOT = DATA_ROOT / "jobs"
JOBS_ROOT.mkdir(parents=True, exist_ok=True)
TASKS_ROOT = DATA_ROOT / "tasks"
TASKS_ROOT.mkdir(parents=True, exist_ok=True)
PROJECTS_ROOT = DATA_ROOT / "projects"
PROJECTS_ROOT.mkdir(parents=True, exist_ok=True)
ASSETS_ROOT = DATA_ROOT / "assets"
ASSETS_ROOT.mkdir(parents=True, exist_ok=True)
SHARE_ROOT = BASE_DIR / "share"
SHARE_ROOT.mkdir(parents=True, exist_ok=True)
LOGS_ROOT = BASE_DIR / "logs"
LOGS_ROOT.mkdir(parents=True, exist_ok=True)
MIGRATIONS_LOGS_ROOT = LOGS_ROOT / "migrations"
MIGRATIONS_LOGS_ROOT.mkdir(parents=True, exist_ok=True)
CLOUD_STORAGE_ROOT = DATA_ROOT / "storages"
CLOUD_STORAGE_ROOT.mkdir(parents=True, exist_ok=True)
TMP_FILES_ROOT = DATA_ROOT / "tmp"
TMP_FILES_ROOT.mkdir(parents=True, exist_ok=True)
IGNORE_TMP_FOLDER_CLEANUP_ERRORS = True
# logging is known to be unreliable with RQ when using async transports
vector_log_handler = os.getenv("VECTOR_EVENT_HANDLER", "AsynchronousLogstashHandler")
logstash_async_constants.QUEUED_EVENTS_FLUSH_INTERVAL = 2.0
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"vector": {"format": "%(message)s"},
"standard": {"format": "[%(asctime)s] %(levelname)s %(name)s: %(message)s"},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"filters": [],
"formatter": "standard",
},
"server_file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"filename": LOGS_ROOT / "cvat_server.log",
"formatter": "standard",
"maxBytes": 1024 * 1024 * 50, # 50 MB
"backupCount": 5,
},
"dataset_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"filename": LOGS_ROOT / "cvat_server_dataset.log",
"formatter": "standard",
"maxBytes": 1024 * 1024 * 50, # 50 MB
"backupCount": 3,
},
"vector": {
"level": "INFO",
"class": f"logstash_async.handler.{vector_log_handler}",
"formatter": "vector",
"transport": "logstash_async.transport.HttpTransport",
"ssl_enable": False,
"ssl_verify": False,
"host": os.getenv("DJANGO_LOG_SERVER_HOST", "localhost"),
"port": os.getenv("DJANGO_LOG_SERVER_PORT", 8282),
"version": 1,
"message_type": "django",
"database_path": EVENTS_LOCAL_DB_FILE,
},
},
"root": {
"handlers": ["console", "server_file"],
},
"loggers": {
"cvat": {
"level": os.getenv("DJANGO_LOG_LEVEL", "DEBUG"),
},
"dataset_logger": {
"handlers": ["dataset_handler"],
},
"django": {
"level": "INFO",
},
"vector": {
"handlers": [],
"level": "INFO",
# set True for debug
"propagate": False,
},
},
}
CVAT_LOG_IMPORT_ERRORS = to_bool(os.getenv("CVAT_LOG_IMPORT_ERRORS", False))
if os.getenv("DJANGO_LOG_SERVER_HOST"):
LOGGING["loggers"]["vector"]["handlers"] += ["vector"]
DATA_UPLOAD_MAX_MEMORY_SIZE = 100 * 1024 * 1024 # 100 MB
DATA_UPLOAD_MAX_NUMBER_FIELDS = None # this django check disabled
DATA_UPLOAD_MAX_NUMBER_FILES = None
redis_ondisk_host = os.getenv("CVAT_REDIS_ONDISK_HOST", "localhost")
# The default port is not Redis's default port (6379).
# This is so that a developer can run both in-mem Redis and on-disk Kvrocks on their machine
# without running into a port conflict.
redis_ondisk_port = os.getenv("CVAT_REDIS_ONDISK_PORT", 6666)
redis_ondisk_password = os.getenv("CVAT_REDIS_ONDISK_PASSWORD", "")
# Sets the timeout for the expiration of data chunk in redis_ondisk
CVAT_CHUNK_CACHE_TTL = 3600 * 24 # 1 day
# Sets the timeout for the expiration of preview image in redis_ondisk
CVAT_PREVIEW_CACHE_TTL = 3600 * 24 * 7 # 7 days
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": f"redis://:{urllib.parse.quote(redis_inmem_password)}@{redis_inmem_host}:{redis_inmem_port}/{REDIS_INMEM_DATABASES.CACHE}",
},
"media": {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": f"redis://:{urllib.parse.quote(redis_ondisk_password)}@{redis_ondisk_host}:{redis_ondisk_port}",
"TIMEOUT": CVAT_CHUNK_CACHE_TTL,
},
}
USE_CACHE = True
CORS_ALLOW_HEADERS = list(default_headers) + [
# tus upload protocol headers
"upload-offset",
"upload-length",
"tus-version",
"tus-resumable",
# extended upload protocol headers
"upload-start",
"upload-finish",
"upload-multiple",
"x-organization",
]
CORS_EXPOSE_HEADERS = [
"Content-Range",
]
TUS_MAX_FILE_SIZE = 26843545600 # 25gb
# This setting makes request secure if X-Forwarded-Proto: 'https' header is specified by our proxy
# More about forwarded headers - https://doc.traefik.io/traefik/getting-started/faq/#what-are-the-forwarded-headers-when-proxying-http-requests
# How django uses X-Forwarded-Proto - https://docs.djangoproject.com/en/2.2/ref/settings/#secure-proxy-ssl-header
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
# Forwarded host - https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-USE_X_FORWARDED_HOST
# Is used in TUS uploads to provide correct upload endpoint
USE_X_FORWARDED_HOST = True
# Django-sendfile requires to set SENDFILE_ROOT
# https://github.com/moggers87/django-sendfile2
SENDFILE_ROOT = BASE_DIR
CVAT_DOCS_URL = "https://docs.cvat.ai/docs/"
SPECTACULAR_SETTINGS = {
"TITLE": "CVAT REST API",
"DESCRIPTION": "REST API for Computer Vision Annotation Tool (CVAT)",
# Statically set schema version. May also be an empty string. When used together with
# view versioning, will become '0.0.0 (v2)' for 'v2' versioned requests.
# Set VERSION to None if only the request version should be rendered.
"VERSION": __version__,
"CONTACT": {
"name": "CVAT.ai team",
"url": "https://github.com/cvat-ai/cvat",
"email": "support@cvat.ai",
},
"LICENSE": {
"name": "MIT License",
"url": "https://en.wikipedia.org/wiki/MIT_License",
},
"SERVE_PUBLIC": True,
"SERVE_PERMISSIONS": ["rest_framework.permissions.AllowAny"],
# https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
"SWAGGER_UI_SETTINGS": {
"deepLinking": True,
"displayOperationId": True,
"displayRequestDuration": True,
"filter": True,
"showExtensions": True,
},
"TOS": "https://www.google.com/policies/terms/",
"EXTERNAL_DOCS": {
"description": "CVAT documentation",
"url": CVAT_DOCS_URL,
},
# OTHER SETTINGS
# https://drf-spectacular.readthedocs.io/en/latest/settings.html
#
# TODO: Our current implementation does not suppose this.
# Need to reconsider this later. It happens, for example,
# in TaskSerializer for data-originated fields - they can be empty.
# https://github.com/tfranzel/drf-spectacular/issues/54
"COMPONENT_NO_READ_ONLY_REQUIRED": True,
# Required for correct file upload type (bytes)
"COMPONENT_SPLIT_REQUEST": True,
"ENUM_NAME_OVERRIDES": {
"LabelType": "cvat.apps.engine.models.LabelType",
"ShapeType": "cvat.apps.engine.models.ShapeType",
"OperationStatus": "cvat.apps.engine.models.StateChoice",
"ChunkType": "cvat.apps.engine.models.DataChoice",
"MediaType": "cvat.apps.engine.models.MediaType",
"Dimension": "cvat.apps.engine.models.DimensionType",
"StorageMethod": "cvat.apps.engine.models.StorageMethodChoice",
"JobStatus": "cvat.apps.engine.models.StatusChoice",
"JobStage": "cvat.apps.engine.models.StageChoice",
"JobType": "cvat.apps.engine.models.JobType",
"TaskMode": "cvat.apps.engine.models.TaskMode",
"StorageType": "cvat.apps.engine.models.StorageChoice",
"SortingMethod": "cvat.apps.engine.models.SortingMethod",
"WebhookType": "cvat.apps.webhooks.models.WebhookTypeChoice",
"WebhookContentType": "cvat.apps.webhooks.models.WebhookContentTypeChoice",
"RequestStatus": "cvat.apps.redis_handler.serializers.RequestStatus",
"ValidationMode": "cvat.apps.engine.models.ValidationMode",
"FrameSelectionMethod": "cvat.apps.engine.models.JobFrameSelectionMethod",
"AnnotationConflictType": "cvat.apps.quality_control.models.AnnotationConflictType",
"AnnotationConflictSeverity": "cvat.apps.quality_control.models.AnnotationConflictSeverity",
"AnnotationConflictAnnotationType": "cvat.apps.quality_control.models.AnnotationType",
"MismatchingAnnotationKind": "cvat.apps.quality_control.models.MismatchingAnnotationKind",
"QualityTargetMetric": "cvat.apps.quality_control.models.QualityTargetMetricType",
"QualityPointSizeBase": "cvat.apps.quality_control.models.PointSizeBase",
"QualityReportTarget": "cvat.apps.quality_control.models.QualityReportTarget",
},
# Coercion of {pk} to {id} is controlled by SCHEMA_COERCE_PATH_PK. Additionally,
# some libraries (e.g. drf-nested-routers) use "_pk" suffixed path variables.
# This setting globally coerces path variables like "{user_pk}" to "{user_id}".
"SCHEMA_COERCE_PATH_PK_SUFFIX": True,
"SCHEMA_PATH_PREFIX": "/api",
"SCHEMA_PATH_PREFIX_TRIM": False,
"GENERIC_ADDITIONAL_PROPERTIES": None,
}
# set similar UI restrictions
# https://github.com/cvat-ai/cvat/blob/bad1dc2799afbb22222faaecc7336d999f4cc3fe/cvat-ui/src/utils/validation-patterns.ts#L26
ACCOUNT_USERNAME_MIN_LENGTH = 5
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
ACCOUNT_ADAPTER = "cvat.apps.iam.adapters.DefaultAccountAdapterEx"
CVAT_HOST = os.getenv("CVAT_HOST", "localhost")
CVAT_BASE_URL = os.getenv("CVAT_BASE_URL", f"http://{CVAT_HOST}:8080").rstrip("/")
CLICKHOUSE = {
"events": {
"NAME": os.getenv("CLICKHOUSE_DB", "cvat"),
"HOST": os.getenv("CLICKHOUSE_HOST", "localhost"),
"PORT": os.getenv("CLICKHOUSE_PORT", 8123),
"USER": os.getenv("CLICKHOUSE_USER", "user"),
"PASSWORD": os.getenv("CLICKHOUSE_PASSWORD", "user"),
}
}
if (postgres_password_file := os.getenv("CVAT_POSTGRES_PASSWORD_FILE")) is not None:
if "CVAT_POSTGRES_PASSWORD" in os.environ:
raise ImproperlyConfigured(
"The CVAT_POSTGRES_PASSWORD and CVAT_POSTGRES_PASSWORD_FILE"
" environment variables must not be set at the same time"
)
postgres_password = Path(postgres_password_file).read_text(encoding="UTF-8").rstrip("\n")
else:
postgres_password = os.getenv("CVAT_POSTGRES_PASSWORD", "")
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": os.getenv("CVAT_POSTGRES_HOST", "cvat_db"),
"NAME": os.getenv("CVAT_POSTGRES_DBNAME", "cvat"),
"USER": os.getenv("CVAT_POSTGRES_USER", "root"),
"PASSWORD": postgres_password,
"PORT": os.getenv("CVAT_POSTGRES_PORT", 5432),
"OPTIONS": {
"application_name": os.getenv("CVAT_POSTGRES_APPLICATION_NAME", "cvat"),
},
}
}
BUCKET_CONTENT_MAX_PAGE_SIZE = 500
IMPORT_CACHE_FAILED_TTL = timedelta(days=30)
IMPORT_CACHE_SUCCESS_TTL = timedelta(hours=1)
IMPORT_CACHE_CLEAN_DELAY = timedelta(hours=12)
ASSET_MAX_SIZE_MB = 10
ASSET_SUPPORTED_TYPES = ("image/jpeg", "image/png", "image/webp", "image/gif", "application/pdf")
ASSET_MAX_IMAGE_SIZE = 1920
ASSET_MAX_COUNT_PER_GUIDE = 150
SMOKESCREEN_ENABLED = to_bool(os.getenv("SMOKESCREEN_ENABLED", True))
# By default, email backend is django.core.mail.backends.smtp.EmailBackend
# But it won't work without additional configuration, so we set it to None
# to check configuration and throw ImproperlyConfigured if thats a case
EMAIL_BACKEND = None
ONE_RUNNING_JOB_IN_QUEUE_PER_USER = to_bool(os.getenv("ONE_RUNNING_JOB_IN_QUEUE_PER_USER", False))
# How many chunks can be prepared simultaneously during task creation in case the cache is not used
CVAT_CONCURRENT_CHUNK_PROCESSING = int(os.getenv("CVAT_CONCURRENT_CHUNK_PROCESSING", 1))
from cvat.rq_patching import patch_rq
patch_rq()
CLOUD_DATA_DOWNLOADING_MAX_THREADS_NUMBER_PER_CPU = 4
# Indicates the maximum number of days a file or directory is retained in the temporary directory
TMP_FILE_OR_DIR_RETENTION_DAYS = 3
LOGO_FILENAME = "logo.svg"
ABOUT_INFO = {
"subtitle": "Open Data Annotation Platform",
}
if ONE_RUNNING_JOB_IN_QUEUE_PER_USER:
PERIODIC_RQ_JOBS.append(
{
"queue": CVAT_QUEUES.CLEANING.value,
"id": "cleanup_deferred_job_registry",
"func": "cvat.apps.redis_handler.cron.cleanup_deferred_job_registry",
"cron_string": "0 8 * * *",
}
)
USER_LAST_ACTIVITY_UPDATE_MIN_INTERVAL = timedelta(days=1)
# Health check settings
HEALTH_CHECK = {"DISK_USAGE_MAX": int(os.getenv("CVAT_HEALTH_DISK_USAGE_MAX", 90))}