diff --git a/example.env b/example.env index 2340173..188bbf4 100644 --- a/example.env +++ b/example.env @@ -16,3 +16,4 @@ ALLOWED_HOSTS=localhost BOT_TOKEN=Used-For-Bot-API-Calls +CSRF_TRUSTED_ORIGINS=https://pasted.ir,https://www.pasted.ir diff --git a/pastebinir/settings.py b/pastebinir/settings.py index 94b6eed..e1008f6 100644 --- a/pastebinir/settings.py +++ b/pastebinir/settings.py @@ -9,102 +9,28 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.1/ref/settings/ """ -import os + from pathlib import Path + +import environ + +from cbs import BaseSettings + from dotenv import load_dotenv -from typing import Dict + from scheduler.types import SchedulerConfiguration, Broker, QueueConfiguration +env = environ.Env() + load_dotenv() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent -env = os.environ.get -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.getenv("SECRET_KEY") - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False - -ALLOWED_HOSTS = env("ALLOWED_HOSTS", "*").split(",") - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'website', - 'api', - 'drf_spectacular', - 'rest_framework', - 'rest_framework.authtoken', - 'dj_rest_auth', - 'health_check', # required - 'health_check.db', # stock Django health checkers - 'health_check.cache', - 'health_check.storage', - 'health_check.contrib.migrations', - - 'health_check.contrib.redis', # requires Redis broker - 'scheduler', # django-tasks-scheduler -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -] -ROOT_URLCONF = 'pastebinir.urls' +ROOT_URLCONF = "pastebinir.urls" -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [BASE_DIR / 'templates'] - , - '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', - ], - }, - }, -] - -WSGI_APPLICATION = 'pastebinir.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/5.1/ref/settings/#databases -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql", - "NAME": env("POSTGRES_NAME", default="postgres"), - "USER": env("POSTGRES_USER", default="postgres"), - "PASSWORD": env("POSTGRES_PASSWORD", default="postgres"), - "HOST": env("POSTGRES_HOST", default="localhost"), - "PORT": 5432, - "ATOMIC_REQUESTS": True, - "OPTIONS": { - "pool": {"max_lifetime": 60}, - }, - } -} +WSGI_APPLICATION = "pastebinir.wsgi.application" # Password validation @@ -112,16 +38,16 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -129,150 +55,328 @@ # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True USE_TZ = True REST_FRAMEWORK = { - "DEFAULT_PERMISSION_CLASSES" : [ + "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ], - "DEFAULT_AUTHENTICATION_CLASSES" : [ - "rest_framework.authentication.SessionAuthentication", + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.TokenAuthentication", ], - "DEFAULT_SCHEMA_CLASS" : "drf_spectacular.openapi.AutoSchema", + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } SPECTACULAR_SETTINGS = { - "TITLE" : "Pasted IR Documentation", + "TITLE": "Pasted IR Documentation", "DESCRIPTION": "A simple django based pastebin", - "VERSION": "0.1 Snapshot" + "VERSION": "0.1 Snapshot", } -# Session Security Settings -SESSION_COOKIE_SECURE = True # Only send cookies over HTTPS -SESSION_COOKIE_HTTPONLY = True # Prevent JavaScript access to session cookies -SESSION_COOKIE_SAMESITE = 'Strict' # Prevent CSRF attacks -SESSION_COOKIE_AGE = 3600 # Session expires in 1 hour (3600 seconds) -SESSION_EXPIRE_AT_BROWSER_CLOSE = True # Session expires when browser closes -SESSION_SAVE_EVERY_REQUEST = True # Update session on every request - -# CSRF Security -CSRF_COOKIE_SECURE = True # Only send CSRF cookies over HTTPS -CSRF_COOKIE_HTTPONLY = False # Allow JavaScript access to CSRF token (needed for forms) -CSRF_COOKIE_SAMESITE = 'Lax' # Allow CSRF tokens for same-site requests -CSRF_TRUSTED_ORIGINS = ['https://pasted.ir', 'https://www.pasted.ir'] - -# Security settings for static files -SECURE_BROWSER_XSS_FILTER = True -SECURE_CONTENT_TYPE_NOSNIFF = True -X_FRAME_OPTIONS = 'DENY' - -# Additional security headers -SECURE_HSTS_SECONDS = 31536000 # 1 year -SECURE_HSTS_INCLUDE_SUBDOMAINS = True -SECURE_HSTS_PRELOAD = True -SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin' - -# Rate limiting -RATE_LIMIT_ENABLED = True -RATE_LIMIT_REQUESTS = 100 # requests per hour -RATE_LIMIT_WINDOW = 3600 # 1 hour # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = BASE_DIR / 'staticfiles' +STATIC_URL = "/static/" +STATIC_ROOT = BASE_DIR / "staticfiles" STATICFILES_DIRS = [ - BASE_DIR / 'static', + BASE_DIR / "static", ] # Static files finders STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", ] # Cache configuration -import os # Use Redis if available, otherwise fall back to local memory -if os.environ.get('REDIS_HOST', 'redis') == 'redis': - CACHES = { - 'default': { - 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': 'redis://redis:6379/1', - 'OPTIONS': { - 'CLIENT_CLASS': 'django_redis.client.DefaultClient', - }, - 'TIMEOUT': 300, # 5 minutes default - } - } -else: - # Fallback to local memory cache for development - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': 'unique-snowflake', - 'TIMEOUT': 300, # 5 minutes default - 'OPTIONS': { - 'MAX_ENTRIES': 1000, - } - } +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://redis:6379/1", + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + "TIMEOUT": 300, # 5 minutes default } +} # Cache languages for better performance -LANGUAGE_CACHE_KEY = 'all_languages' +LANGUAGE_CACHE_KEY = "all_languages" LANGUAGE_CACHE_TIMEOUT = 3600 # 1 hour # Scheduled tasks are now defined in website/scheduler_tasks.py - # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # Health Check Configuration HEALTH_CHECK = { - 'DISK_USAGE_MAX': 90, # percent - 'MEMORY_MIN': 100, # in MB - 'SUBSETS': { - 'startup-probe': ['MigrationsHealthCheck', 'DatabaseBackend'], - 'liveness-probe': ['DatabaseBackend', 'CacheBackend'], - 'readiness-probe': ['DatabaseBackend', 'CacheBackend', 'RedisHealthCheck'], + "DISK_USAGE_MAX": 90, # percent + "MEMORY_MIN": 100, # in MB + "SUBSETS": { + "startup-probe": ["MigrationsHealthCheck", "DatabaseBackend"], + "liveness-probe": ["DatabaseBackend", "CacheBackend"], + "readiness-probe": ["DatabaseBackend", "CacheBackend", "RedisHealthCheck"], }, } # Health Check Cache Key -HEALTHCHECK_CACHE_KEY = 'pastedir_healthcheck' +HEALTHCHECK_CACHE_KEY = "pastedir_healthcheck" # Scheduler Configuration SCHEDULER_CONFIG = SchedulerConfiguration( EXECUTIONS_IN_PAGE=20, SCHEDULER_INTERVAL=10, BROKER=Broker.REDIS, - CALLBACK_TIMEOUT=60, # Callback timeout in seconds (success/failure/stopped) + # Callback timeout in seconds (success/failure/stopped) + CALLBACK_TIMEOUT=60, # Default values, can be overriden per task/job - DEFAULT_SUCCESS_TTL=10 * 60, # Time To Live (TTL) in seconds to keep successful job results - DEFAULT_FAILURE_TTL=365 * 24 * 60 * 60, # Time To Live (TTL) in seconds to keep job failure information - DEFAULT_JOB_TTL=10 * 60, # Time To Live (TTL) in seconds to keep job information + # Time To Live (TTL) in seconds to keep successful job results + DEFAULT_SUCCESS_TTL=10 * 60, + # Time To Live (TTL) in seconds to keep job failure information + DEFAULT_FAILURE_TTL=365 * 24 * 60 * 60, + # Time To Live (TTL) in seconds to keep job information + DEFAULT_JOB_TTL=10 * 60, DEFAULT_JOB_TIMEOUT=5 * 60, # timeout (seconds) for a job # General configuration values - DEFAULT_WORKER_TTL=10 * 60, # Time To Live (TTL) in seconds to keep worker information after last heartbeat - DEFAULT_MAINTENANCE_TASK_INTERVAL=10 * 60, # The interval to run maintenance tasks in seconds. 10 minutes. - DEFAULT_JOB_MONITORING_INTERVAL=30, # The interval to monitor jobs in seconds. - SCHEDULER_FALLBACK_PERIOD_SECS=120, # Period (secs) to wait before requiring to reacquire locks + # Time To Live (TTL) in seconds to keep worker information after last heartbeat + DEFAULT_WORKER_TTL=10 * 60, + # The interval to run maintenance tasks in seconds. 10 minutes. + DEFAULT_MAINTENANCE_TASK_INTERVAL=10 * 60, + # The interval to monitor jobs in seconds. + DEFAULT_JOB_MONITORING_INTERVAL=30, + # Period (secs) to wait before requiring to reacquire locks + SCHEDULER_FALLBACK_PERIOD_SECS=120, ) -SCHEDULER_QUEUES: Dict[str, QueueConfiguration] = { - 'default': QueueConfiguration(URL='redis://redis:6379/0'), +SCHEDULER_QUEUES: dict[str, QueueConfiguration] = { + "default": QueueConfiguration(URL="redis://redis:6379/0"), } + + +class Settings(BaseSettings): + SECRET_KEY = env.str("SECRET_KEY") + ALLOWED_HOSTS = ("localhost", "127.0.0.1") + INTERNAL_IPS = ("127.0.0.1",) + SITE_ID = 1 + DEBUG = env.bool("DEBUG", True) + + STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, + } + + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [BASE_DIR / "templates"], + "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", + ], + }, + }, + ] + + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s", + }, + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } + }, + "root": {"level": "INFO", "handlers": ["console"]}, + } + + def MIDDLEWARE(self): + return list( + filter( + None, + [ + "django.middleware.security.SecurityMiddleware", + ( + "whitenoise.middleware.WhiteNoiseMiddleware" + if self.DEBUG + else None + ), + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + ( + "debug_toolbar.middleware.DebugToolbarMiddleware" + if self.DEBUG + else None + ), + ( + "django_browser_reload.middleware.BrowserReloadMiddleware" + if self.DEBUG + else None + ), + ], + ) + ) + + def DATABASES(self): + return { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": env("POSTGRES_NAME", default="postgres"), + "USER": env("POSTGRES_USER", default="postgres"), + "PASSWORD": env("POSTGRES_PASSWORD", default="postgres"), + "HOST": env("POSTGRES_HOST", default="localhost"), + "PORT": 5432, + "ATOMIC_REQUESTS": True, + "OPTIONS": { + "pool": {"max_lifetime": 60}, + }, + } + } + + def INSTALLED_APPS(self): + return list( + filter( + None, + [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "whitenoise.runserver_nostatic" if self.DEBUG else None, + "django.contrib.staticfiles", + "django.contrib.sites", + "django.contrib.sitemaps", + # debug toolbar + "debug_toolbar" if self.DEBUG else None, + # browser reload + "django_browser_reload" if self.DEBUG else None, + # rest + "drf_spectacular", + "rest_framework", + "rest_framework.authtoken", + "dj_rest_auth", + # health check + "health_check", + "health_check.db", + "health_check.cache", + "health_check.storage", + "health_check.contrib.migrations", + "health_check.contrib.redis", + # django-tasks-scheduler + "scheduler", + # local + "website", + "api", + ], + ) + ) + + +class ProdSettings(Settings): + DEBUG = env.bool("DEBUG", False) + ALLOWED_HOSTS = env.list( + "ALLOWED_HOSTS", + ) + SESSION_COOKIE_SECURE = env.bool("DJANGO_SESSION_COOKIE_SECURE", default=True) + SESSION_COOKIE_HTTPONLY = env.bool("DJANGO_SESSION_COOKIE_HTTPONLY", default=True) + + CSRF_TRUSTED_ORIGINS = env.list("DJANGO_TRUSTED_ORIGINS", default=["localhost"]) + CSRF_COOKIE_SECURE = env.bool("DJANGO_CSRF_COOKIE_SECURE", default=True) + CSRF_COOKIE_HTTPONLY = env.bool("DJANGO_CSRF_COOKIE_HTTPONLY", default=True) + + STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, + } + + LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "filters": { + "require_debug_false": {"()": "django.utils.log.RequireDebugFalse"} + }, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s", + }, + }, + "handlers": { + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", + }, + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + }, + "root": {"level": "INFO", "handlers": ["console"]}, + "loggers": { + "django.request": { + "handlers": ["mail_admins"], + "level": "ERROR", + "propagate": True, + }, + "django.security.DisallowedHost": { + "level": "ERROR", + "handlers": ["console", "mail_admins"], + "propagate": True, + }, + }, + } + + +class TestSettings(Settings): + SECRET_KEY = "testsecret" + PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",) + CART_SESSION_KEY = "cart" + + def MIDDLEWARE(self): + return [ + m + for m in super().MIDDLEWARE() + if m != "debug_toolbar.middleware.DebugToolbarMiddleware" + and m != "django_browser_reload.middleware.BrowserReloadMiddleware" + ] + + +__getattr__, __dir__ = Settings.use() diff --git a/pastebinir/settings_dev.py b/pastebinir/settings_dev.py deleted file mode 100644 index 0bcaa3a..0000000 --- a/pastebinir/settings_dev.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Development settings for pastebinir project. -""" -from .settings import * - -# Development-specific settings -DEBUG = True - -# Enable auto-reload for development -USE_TZ = True - -# Disable HTTPS requirements for development -SESSION_COOKIE_SECURE = False -CSRF_COOKIE_SECURE = False - -# Allow all hosts for development -ALLOWED_HOSTS = ['*'] - -# Disable security headers for development -SECURE_BROWSER_XSS_FILTER = False -SECURE_CONTENT_TYPE_NOSNIFF = False -SECURE_HSTS_SECONDS = 0 -SECURE_HSTS_INCLUDE_SUBDOMAINS = False -SECURE_HSTS_PRELOAD = False - -# Enable debug toolbar if available -try: - import debug_toolbar - INSTALLED_APPS += ['debug_toolbar'] - MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] - INTERNAL_IPS = ['127.0.0.1', '::1'] -except ImportError: - pass - -# Development logging -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - }, - }, - 'root': { - 'handlers': ['console'], - 'level': 'INFO', - }, - 'loggers': { - 'django': { - 'handlers': ['console'], - 'level': 'INFO', - 'propagate': False, - }, - }, -} - -# Serve static files in development -STATICFILES_DIRS = [ - BASE_DIR / 'static', -] - -# Disable static files collection in development -STATIC_ROOT = None \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 312aef7..40f1457 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,5 +19,14 @@ dependencies = [ "psycopg[c,pool]>=3.2.9 ; sys_platform != 'win32'", "python-dotenv>=1.1.1", "redis>=6.2.0", + "django-classy-settings>=3.0.7", + "django-environ>=0.12.0", +] + +[dependency-groups] +dev = [ + "django-browser-reload>=1.18.0", + "django-debug-toolbar>=6.0.0", + "whitenoise>=6.9.0", ] diff --git a/uv.lock b/uv.lock index 64fb98c..e1530ad 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11" resolution-markers = [ "sys_platform != 'win32'", @@ -231,6 +231,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/ae/706965237a672434c8b520e89a818e8b047af94e9beb342d0bee405c26c7/django-5.2.4-py3-none-any.whl", hash = "sha256:60c35bd96201b10c6e7a78121bd0da51084733efa303cc19ead021ab179cef5e", size = 8302187, upload-time = "2025-07-02T18:47:35.373Z" }, ] +[[package]] +name = "django-browser-reload" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/41/84eaa4f2b7f764e56e01c4ee49aa12bdea30ae50fd0d3ef7c2690b0c4ec2/django_browser_reload-1.18.0.tar.gz", hash = "sha256:c5f0b134723cbf2a0dc9ae1ee1d38e42db28fe23c74cdee613ba3ef286d04735", size = 14319, upload-time = "2025-02-06T22:14:40.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/9d/1322dc4bce4982d1eadd3a62802c996ae0303aad13d9ad88c1d35025f73d/django_browser_reload-1.18.0-py3-none-any.whl", hash = "sha256:ed4cc2fb83c3bf6c30b54107a1a6736c0b896e62e4eba666d81005b9f2ecf6f8", size = 12230, upload-time = "2025-02-06T22:14:36.87Z" }, +] + +[[package]] +name = "django-classy-settings" +version = "3.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/ca/e2350391f5c59bf976a1dfd6ddd80ee5a6d956abe09a3c31ac9b27cafaf5/django_classy_settings-3.0.7-py3-none-any.whl", hash = "sha256:c2021f1bcebb9770047867eba003ae8ce280f46cf6a27a75cff8f8635ad64b85", size = 7373, upload-time = "2024-10-16T23:51:22.477Z" }, +] + +[[package]] +name = "django-debug-toolbar" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "sqlparse" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/d5/5fc90234532088aeec5faa48d5b09951cc7eab6626030ed427d3bd8cd9bc/django_debug_toolbar-6.0.0.tar.gz", hash = "sha256:6eb9fa6f4a5884bf04004700ffb5a44043f1fff38784447fc52c1633448c8c14", size = 305331, upload-time = "2025-07-25T13:11:48.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/b5/4724a8c18fcc5b09dca7b7a0e70c34208317bb110075ad12484d6588ae91/django_debug_toolbar-6.0.0-py3-none-any.whl", hash = "sha256:0cf2cac5c307b77d6e143c914e5c6592df53ffe34642d93929e5ef095ae56841", size = 266967, upload-time = "2025-07-25T13:11:47.265Z" }, +] + +[[package]] +name = "django-environ" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/04/65d2521842c42f4716225f20d8443a50804920606aec018188bbee30a6b0/django_environ-0.12.0.tar.gz", hash = "sha256:227dc891453dd5bde769c3449cf4a74b6f2ee8f7ab2361c93a07068f4179041a", size = 56804, upload-time = "2025-01-13T17:03:37.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957, upload-time = "2025-01-13T17:03:32.918Z" }, +] + [[package]] name = "django-health-check" version = "3.20.0" @@ -365,6 +411,9 @@ dependencies = [ { name = "cryptography" }, { name = "dj-rest-auth" }, { name = "django" }, + { name = "django-classy-settings" }, + { name = "django-debug-toolbar" }, + { name = "django-environ" }, { name = "django-health-check" }, { name = "django-redis" }, { name = "django-tasks-scheduler" }, @@ -378,12 +427,22 @@ dependencies = [ { name = "redis" }, ] +[package.dev-dependencies] +dev = [ + { name = "django-browser-reload" }, + { name = "django-debug-toolbar" }, + { name = "whitenoise" }, +] + [package.metadata] requires-dist = [ { name = "bcrypt", specifier = ">=4.3.0" }, { name = "cryptography", specifier = ">=45.0.5" }, { name = "dj-rest-auth", specifier = ">=7.0.1" }, { name = "django", specifier = ">=5.2.4" }, + { name = "django-classy-settings", specifier = ">=3.0.7" }, + { name = "django-debug-toolbar", specifier = ">=6.0.0" }, + { name = "django-environ", specifier = ">=0.12.0" }, { name = "django-health-check", specifier = ">=3.20.0" }, { name = "django-redis", specifier = ">=6.0.0" }, { name = "django-tasks-scheduler", specifier = ">=4.0.5" }, @@ -396,6 +455,13 @@ requires-dist = [ { name = "redis", specifier = ">=6.2.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "django-browser-reload", specifier = ">=1.18.0" }, + { name = "django-debug-toolbar", specifier = ">=6.0.0" }, + { name = "whitenoise", specifier = ">=6.9.0" }, +] + [[package]] name = "psycopg" version = "3.2.9" @@ -693,3 +759,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9 wheels = [ { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, ] + +[[package]] +name = "whitenoise" +version = "6.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/cf/c15c2f21aee6b22a9f6fc9be3f7e477e2442ec22848273db7f4eb73d6162/whitenoise-6.9.0.tar.gz", hash = "sha256:8c4a7c9d384694990c26f3047e118c691557481d624f069b7f7752a2f735d609", size = 25920, upload-time = "2025-02-06T22:16:34.957Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b2/2ce9263149fbde9701d352bda24ea1362c154e196d2fda2201f18fc585d7/whitenoise-6.9.0-py3-none-any.whl", hash = "sha256:c8a489049b7ee9889617bb4c274a153f3d979e8f51d2efd0f5b403caf41c57df", size = 20161, upload-time = "2025-02-06T22:16:32.589Z" }, +]