# 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))}