settings.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. __package__ = 'archivebox.core'
  2. import os
  3. import sys
  4. import inspect
  5. from pathlib import Path
  6. from django.utils.crypto import get_random_string
  7. import abx
  8. import archivebox
  9. from archivebox.config import DATA_DIR, PACKAGE_DIR, ARCHIVE_DIR, CONSTANTS # noqa
  10. from archivebox.config.common import SHELL_CONFIG, SERVER_CONFIG # noqa
  11. IS_MIGRATING = 'makemigrations' in sys.argv[:3] or 'migrate' in sys.argv[:3]
  12. IS_TESTING = 'test' in sys.argv[:3] or 'PYTEST_CURRENT_TEST' in os.environ
  13. IS_SHELL = 'shell' in sys.argv[:3] or 'shell_plus' in sys.argv[:3]
  14. IS_GETTING_VERSION_OR_HELP = 'version' in sys.argv or 'help' in sys.argv or '--version' in sys.argv or '--help' in sys.argv
  15. ################################################################################
  16. ### ArchiveBox Plugin Settings
  17. ################################################################################
  18. ALL_PLUGINS = archivebox.ALL_PLUGINS
  19. LOADED_PLUGINS = archivebox.LOADED_PLUGINS
  20. ################################################################################
  21. ### Django Core Settings
  22. ################################################################################
  23. WSGI_APPLICATION = 'core.wsgi.application'
  24. ASGI_APPLICATION = "core.asgi.application"
  25. ROOT_URLCONF = 'core.urls'
  26. LOGIN_URL = '/accounts/login/'
  27. LOGOUT_REDIRECT_URL = os.environ.get('LOGOUT_REDIRECT_URL', '/')
  28. PASSWORD_RESET_URL = '/accounts/password_reset/'
  29. APPEND_SLASH = True
  30. DEBUG = SHELL_CONFIG.DEBUG or ('--debug' in sys.argv)
  31. INSTALLED_APPS = [
  32. 'daphne',
  33. # Django default apps
  34. 'django.contrib.auth',
  35. 'django.contrib.contenttypes',
  36. 'django.contrib.sessions',
  37. 'django.contrib.messages',
  38. 'django.contrib.staticfiles',
  39. 'django.contrib.admin',
  40. # 3rd-party apps from PyPI
  41. 'signal_webhooks', # handles REST API outbound webhooks https://github.com/MrThearMan/django-signal-webhooks
  42. 'django_object_actions', # provides easy Django Admin action buttons on change views https://github.com/crccheck/django-object-actions
  43. # Our ArchiveBox-provided apps
  44. 'config', # ArchiveBox config settings (loaded as a plugin, don't need to add it here)
  45. 'machine', # handles collecting and storing information about the host machine, network interfaces, installed binaries, etc.
  46. 'workers', # handles starting and managing background workers and processes (orchestrators and actors)
  47. 'crawls', # handles Seed, Crawl, and CrawlSchedule models and management
  48. 'personas', # handles Persona and session management
  49. 'core', # core django model with Snapshot, ArchiveResult, etc.
  50. 'api', # Django-Ninja-based Rest API interfaces, config, APIToken model, etc.
  51. # ArchiveBox plugins
  52. *abx.as_list(abx.pm.hook.get_INSTALLED_APPS()), # all plugin django-apps found in archivebox/plugins_* and data/user_plugins,
  53. # 3rd-party apps from PyPI that need to be loaded last
  54. 'admin_data_views', # handles rendering some convenient automatic read-only views of data in Django admin
  55. 'django_extensions', # provides Django Debug Toolbar (and other non-debug helpers)
  56. 'django_huey', # provides multi-queue support for django huey https://github.com/gaiacoop/django-huey
  57. 'bx_django_utils', # needed for huey_monitor https://github.com/boxine/bx_django_utils
  58. 'huey_monitor', # adds an admin UI for monitoring background huey tasks https://github.com/boxine/django-huey-monitor
  59. # load plugins last so all other apps are already .ready() when we call plugins.ready()
  60. 'abx',
  61. ]
  62. MIDDLEWARE = [
  63. 'core.middleware.TimezoneMiddleware',
  64. 'django.middleware.security.SecurityMiddleware',
  65. 'django.contrib.sessions.middleware.SessionMiddleware',
  66. 'django.middleware.common.CommonMiddleware',
  67. 'django.middleware.csrf.CsrfViewMiddleware',
  68. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  69. 'core.middleware.ReverseProxyAuthMiddleware',
  70. 'django.contrib.messages.middleware.MessageMiddleware',
  71. 'core.middleware.CacheControlMiddleware',
  72. *abx.as_list(abx.pm.hook.get_MIDDLEWARES()),
  73. ]
  74. ################################################################################
  75. ### Authentication Settings
  76. ################################################################################
  77. # AUTH_USER_MODEL = 'auth.User' # cannot be easily changed unfortunately
  78. AUTHENTICATION_BACKENDS = [
  79. 'django.contrib.auth.backends.RemoteUserBackend',
  80. 'django.contrib.auth.backends.ModelBackend',
  81. *abx.as_list(abx.pm.hook.get_AUTHENTICATION_BACKENDS()),
  82. ]
  83. # from ..plugins_auth.ldap.settings import LDAP_CONFIG
  84. # if LDAP_CONFIG.LDAP_ENABLED:
  85. # AUTH_LDAP_BIND_DN = LDAP_CONFIG.LDAP_BIND_DN
  86. # AUTH_LDAP_SERVER_URI = LDAP_CONFIG.LDAP_SERVER_URI
  87. # AUTH_LDAP_BIND_PASSWORD = LDAP_CONFIG.LDAP_BIND_PASSWORD
  88. # AUTH_LDAP_USER_ATTR_MAP = LDAP_CONFIG.LDAP_USER_ATTR_MAP
  89. # AUTH_LDAP_USER_SEARCH = LDAP_CONFIG.AUTH_LDAP_USER_SEARCH
  90. # AUTHENTICATION_BACKENDS = LDAP_CONFIG.AUTHENTICATION_BACKENDS
  91. ################################################################################
  92. ### Staticfile and Template Settings
  93. ################################################################################
  94. STATIC_URL = '/static/'
  95. TEMPLATES_DIR_NAME = 'templates'
  96. CUSTOM_TEMPLATES_ENABLED = os.path.isdir(CONSTANTS.CUSTOM_TEMPLATES_DIR) and os.access(CONSTANTS.CUSTOM_TEMPLATES_DIR, os.R_OK)
  97. STATICFILES_DIRS = [
  98. *([str(CONSTANTS.CUSTOM_TEMPLATES_DIR / 'static')] if CUSTOM_TEMPLATES_ENABLED else []),
  99. # *[
  100. # str(plugin_dir / 'static')
  101. # for plugin_dir in PLUGIN_DIRS.values()
  102. # if (plugin_dir / 'static').is_dir()
  103. # ],
  104. *abx.as_list(abx.pm.hook.get_STATICFILES_DIRS()),
  105. str(PACKAGE_DIR / TEMPLATES_DIR_NAME / 'static'),
  106. ]
  107. TEMPLATE_DIRS = [
  108. *([str(CONSTANTS.CUSTOM_TEMPLATES_DIR)] if CUSTOM_TEMPLATES_ENABLED else []),
  109. # *[
  110. # str(plugin_dir / 'templates')
  111. # for plugin_dir in PLUGIN_DIRS.values()
  112. # if (plugin_dir / 'templates').is_dir()
  113. # ],
  114. *abx.as_list(abx.pm.hook.get_TEMPLATE_DIRS()),
  115. str(PACKAGE_DIR / TEMPLATES_DIR_NAME / 'core'),
  116. str(PACKAGE_DIR / TEMPLATES_DIR_NAME / 'admin'),
  117. str(PACKAGE_DIR / TEMPLATES_DIR_NAME),
  118. ]
  119. TEMPLATES = [
  120. {
  121. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  122. 'DIRS': TEMPLATE_DIRS,
  123. 'APP_DIRS': True,
  124. 'OPTIONS': {
  125. 'context_processors': [
  126. 'django.template.context_processors.debug',
  127. 'django.template.context_processors.request',
  128. 'django.contrib.auth.context_processors.auth',
  129. 'django.contrib.messages.context_processors.messages',
  130. ],
  131. },
  132. },
  133. ]
  134. ################################################################################
  135. ### External Service Settings
  136. ################################################################################
  137. # CACHE_DB_FILENAME = 'cache.sqlite3'
  138. # CACHE_DB_PATH = CONSTANTS.CACHE_DIR / CACHE_DB_FILENAME
  139. # CACHE_DB_TABLE = 'django_cache'
  140. DATABASE_NAME = os.environ.get("ARCHIVEBOX_DATABASE_NAME", str(CONSTANTS.DATABASE_FILE))
  141. SQLITE_CONNECTION_OPTIONS = {
  142. "ENGINE": "django.db.backends.sqlite3",
  143. "TIME_ZONE": CONSTANTS.TIMEZONE,
  144. "OPTIONS": {
  145. # https://gcollazo.com/optimal-sqlite-settings-for-django/
  146. # https://litestream.io/tips/#busy-timeout
  147. # https://docs.djangoproject.com/en/5.1/ref/databases/#setting-pragma-options
  148. "timeout": 10,
  149. "check_same_thread": False,
  150. "transaction_mode": "IMMEDIATE",
  151. "init_command": (
  152. "PRAGMA foreign_keys=ON;"
  153. "PRAGMA journal_mode = WAL;"
  154. "PRAGMA synchronous = NORMAL;"
  155. "PRAGMA temp_store = MEMORY;"
  156. "PRAGMA mmap_size = 134217728;"
  157. "PRAGMA journal_size_limit = 67108864;"
  158. "PRAGMA cache_size = 2000;"
  159. ),
  160. },
  161. }
  162. DATABASES = {
  163. "default": {
  164. "NAME": DATABASE_NAME,
  165. **SQLITE_CONNECTION_OPTIONS,
  166. },
  167. "queue": {
  168. "NAME": CONSTANTS.QUEUE_DATABASE_FILE,
  169. **SQLITE_CONNECTION_OPTIONS,
  170. },
  171. # "filestore": {
  172. # "NAME": CONSTANTS.FILESTORE_DATABASE_FILE,
  173. # **SQLITE_CONNECTION_OPTIONS,
  174. # },
  175. # 'cache': {
  176. # 'NAME': CACHE_DB_PATH,
  177. # **SQLITE_CONNECTION_OPTIONS,
  178. # },
  179. }
  180. MIGRATION_MODULES = {'signal_webhooks': None}
  181. # as much as I'd love this to be a UUID or ULID field, it's not supported yet as of Django 5.0
  182. DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
  183. HUEY = {
  184. "huey_class": "huey.SqliteHuey",
  185. "filename": CONSTANTS.QUEUE_DATABASE_FILENAME,
  186. "name": "commands",
  187. "results": True,
  188. "store_none": True,
  189. "immediate": False,
  190. "utc": True,
  191. "consumer": {
  192. "workers": 1,
  193. "worker_type": "thread",
  194. "initial_delay": 0.1, # Smallest polling interval, same as -d.
  195. "backoff": 1.15, # Exponential backoff using this rate, -b.
  196. "max_delay": 10.0, # Max possible polling interval, -m.
  197. "scheduler_interval": 1, # Check schedule every second, -s.
  198. "periodic": True, # Enable crontab feature.
  199. "check_worker_health": True, # Enable worker health checks.
  200. "health_check_interval": 1, # Check worker health every second.
  201. },
  202. }
  203. # https://huey.readthedocs.io/en/latest/contrib.html#setting-things-up
  204. # https://github.com/gaiacoop/django-huey
  205. DJANGO_HUEY = {
  206. "default": "commands",
  207. "queues": {
  208. HUEY["name"]: HUEY.copy(),
  209. # more registered here at plugin import-time by BaseQueue.register()
  210. **abx.as_dict(abx.pm.hook.get_DJANGO_HUEY_QUEUES(QUEUE_DATABASE_NAME=CONSTANTS.QUEUE_DATABASE_FILENAME)),
  211. },
  212. }
  213. class HueyDBRouter:
  214. """
  215. A router to store all the Huey result k:v / Huey Monitor models in the queue.sqlite3 database.
  216. We keep the databases separate because the queue database receives many more reads/writes per second
  217. and we want to avoid single-write lock contention with the main database. Also all the in-progress task
  218. data is ephemeral/not-important-long-term. This makes it easier to for the user to clear non-critical
  219. temp data by just deleting queue.sqlite3 and leaving index.sqlite3.
  220. """
  221. route_app_labels = {"huey_monitor", "django_huey", "djhuey"}
  222. db_name = "queue"
  223. def db_for_read(self, model, **hints):
  224. if model._meta.app_label in self.route_app_labels:
  225. return self.db_name
  226. return 'default'
  227. def db_for_write(self, model, **hints):
  228. if model._meta.app_label in self.route_app_labels:
  229. return self.db_name
  230. return 'default'
  231. def allow_relation(self, obj1, obj2, **hints):
  232. if obj1._meta.app_label in self.route_app_labels or obj2._meta.app_label in self.route_app_labels:
  233. return obj1._meta.app_label == obj2._meta.app_label
  234. return None
  235. def allow_migrate(self, db, app_label, model_name=None, **hints):
  236. if app_label in self.route_app_labels:
  237. return db == self.db_name
  238. return db == "default"
  239. # class FilestoreDBRouter:
  240. # """
  241. # A router to store all the File models in the filestore.sqlite3 database.
  242. # This data just mirrors what is in the file system, so we want to keep it in a separate database
  243. # from the main index database to avoid contention.
  244. # """
  245. # route_app_labels = {"filestore"}
  246. # db_name = "filestore"
  247. # def db_for_read(self, model, **hints):
  248. # if model._meta.app_label in self.route_app_labels:
  249. # return self.db_name
  250. # return 'default'
  251. # def db_for_write(self, model, **hints):
  252. # if model._meta.app_label in self.route_app_labels:
  253. # return self.db_name
  254. # return 'default'
  255. # def allow_relation(self, obj1, obj2, **hints):
  256. # if obj1._meta.app_label in self.route_app_labels or obj2._meta.app_label in self.route_app_labels:
  257. # return obj1._meta.app_label == obj2._meta.app_label
  258. # return None
  259. # def allow_migrate(self, db, app_label, model_name=None, **hints):
  260. # if app_label in self.route_app_labels:
  261. # return db == self.db_name
  262. # return db == "default"
  263. DATABASE_ROUTERS = ['core.settings.HueyDBRouter']
  264. CACHES = {
  265. 'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'},
  266. # 'sqlite': {'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'cache'},
  267. # 'dummy': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
  268. # 'filebased': {"BACKEND": "django.core.cache.backends.filebased.FileBasedCache", "LOCATION": CACHE_DIR / 'cache_filebased'},
  269. }
  270. EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
  271. STORAGES = {
  272. "default": {
  273. "BACKEND": "django.core.files.storage.FileSystemStorage",
  274. },
  275. "staticfiles": {
  276. "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
  277. },
  278. "archive": {
  279. "BACKEND": "django.core.files.storage.FileSystemStorage",
  280. "OPTIONS": {
  281. "base_url": "/archive/",
  282. "location": ARCHIVE_DIR,
  283. },
  284. },
  285. # "snapshots": {
  286. # "BACKEND": "django.core.files.storage.FileSystemStorage",
  287. # "OPTIONS": {
  288. # "base_url": "/snapshots/",
  289. # "location": CONSTANTS.SNAPSHOTS_DIR,
  290. # },
  291. # },
  292. # "personas": {
  293. # "BACKEND": "django.core.files.storage.FileSystemStorage",
  294. # "OPTIONS": {
  295. # "base_url": "/personas/",
  296. # "location": PERSONAS_DIR,
  297. # },
  298. # },
  299. }
  300. CHANNEL_LAYERS = {
  301. "default": {
  302. "BACKEND": "channels.layers.InMemoryChannelLayer"
  303. }
  304. }
  305. ################################################################################
  306. ### Security Settings
  307. ################################################################################
  308. SECRET_KEY = SERVER_CONFIG.SECRET_KEY or get_random_string(50, 'abcdefghijklmnopqrstuvwxyz0123456789_')
  309. ALLOWED_HOSTS = SERVER_CONFIG.ALLOWED_HOSTS.split(',')
  310. CSRF_TRUSTED_ORIGINS = list(set(SERVER_CONFIG.CSRF_TRUSTED_ORIGINS.split(',')))
  311. # automatically fix case when user sets ALLOWED_HOSTS (e.g. to archivebox.example.com)
  312. # but forgets to add https://archivebox.example.com to CSRF_TRUSTED_ORIGINS
  313. for hostname in ALLOWED_HOSTS:
  314. https_endpoint = f'https://{hostname}'
  315. if hostname != '*' and https_endpoint not in CSRF_TRUSTED_ORIGINS:
  316. print(f'[!] WARNING: {https_endpoint} from ALLOWED_HOSTS should be added to CSRF_TRUSTED_ORIGINS')
  317. CSRF_TRUSTED_ORIGINS.append(https_endpoint)
  318. SECURE_BROWSER_XSS_FILTER = True
  319. SECURE_CONTENT_TYPE_NOSNIFF = True
  320. SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'
  321. CSRF_COOKIE_SECURE = False
  322. SESSION_COOKIE_SECURE = False
  323. SESSION_COOKIE_HTTPONLY = True
  324. SESSION_COOKIE_DOMAIN = None
  325. SESSION_COOKIE_AGE = 1209600 # 2 weeks
  326. SESSION_EXPIRE_AT_BROWSER_CLOSE = False
  327. SESSION_SAVE_EVERY_REQUEST = False
  328. SESSION_ENGINE = "django.contrib.sessions.backends.db"
  329. AUTH_PASSWORD_VALIDATORS = [
  330. {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
  331. {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
  332. {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
  333. {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
  334. ]
  335. DATA_UPLOAD_MAX_NUMBER_FIELDS = None
  336. DATA_UPLOAD_MAX_MEMORY_SIZE = 26_214_400 # 25MB
  337. ################################################################################
  338. ### Shell Settings
  339. ################################################################################
  340. SHELL_PLUS = 'ipython'
  341. SHELL_PLUS_PRINT_SQL = False
  342. IPYTHON_ARGUMENTS = ['--no-confirm-exit', '--no-banner']
  343. IPYTHON_KERNEL_DISPLAY_NAME = 'ArchiveBox Django Shell'
  344. if IS_SHELL:
  345. os.environ['PYTHONSTARTUP'] = str(PACKAGE_DIR / 'misc' / 'shell_welcome_message.py')
  346. ################################################################################
  347. ### Internationalization & Localization Settings
  348. ################################################################################
  349. LANGUAGE_CODE = 'en-us'
  350. USE_I18N = True
  351. USE_TZ = True
  352. DATETIME_FORMAT = 'Y-m-d h:i:s A'
  353. SHORT_DATETIME_FORMAT = 'Y-m-d h:i:s A'
  354. TIME_ZONE = CONSTANTS.TIMEZONE # django convention is TIME_ZONE, archivebox config uses TIMEZONE, they are equivalent
  355. from django.conf.locale.en import formats as en_formats # type: ignore
  356. en_formats.DATETIME_FORMAT = DATETIME_FORMAT # monkey patch en_format default with our preferred format
  357. en_formats.SHORT_DATETIME_FORMAT = SHORT_DATETIME_FORMAT
  358. ################################################################################
  359. ### Logging Settings
  360. ################################################################################
  361. from .settings_logging import SETTINGS_LOGGING, LOGS_DIR, ERROR_LOG
  362. LOGGING = SETTINGS_LOGGING
  363. ################################################################################
  364. ### REST API Outbound Webhooks settings
  365. ################################################################################
  366. # Add default webhook configuration to the User model
  367. SIGNAL_WEBHOOKS_CUSTOM_MODEL = 'api.models.OutboundWebhook'
  368. SIGNAL_WEBHOOKS = {
  369. "HOOKS": {
  370. # ... is a special sigil value that means "use the default autogenerated hooks"
  371. "django.contrib.auth.models.User": ...,
  372. "core.models.Snapshot": ...,
  373. "core.models.ArchiveResult": ...,
  374. "core.models.Tag": ...,
  375. "api.models.APIToken": ...,
  376. },
  377. }
  378. ################################################################################
  379. ### Admin Data View Settings
  380. ################################################################################
  381. ADMIN_DATA_VIEWS = {
  382. "NAME": "Environment",
  383. "URLS": [
  384. {
  385. "route": "config/",
  386. "view": "core.views.live_config_list_view",
  387. "name": "Configuration",
  388. "items": {
  389. "route": "<str:key>/",
  390. "view": "core.views.live_config_value_view",
  391. "name": "config_val",
  392. },
  393. },
  394. {
  395. "route": "binaries/",
  396. "view": "archivebox.config.views.binaries_list_view",
  397. "name": "Dependencies",
  398. "items": {
  399. "route": "<str:key>/",
  400. "view": "archivebox.config.views.binary_detail_view",
  401. "name": "binary",
  402. },
  403. },
  404. {
  405. "route": "plugins/",
  406. "view": "archivebox.config.views.plugins_list_view",
  407. "name": "Plugins",
  408. "items": {
  409. "route": "<str:key>/",
  410. "view": "archivebox.config.views.plugin_detail_view",
  411. "name": "plugin",
  412. },
  413. },
  414. {
  415. "route": "workers/",
  416. "view": "archivebox.config.views.worker_list_view",
  417. "name": "Workers",
  418. "items": {
  419. "route": "<str:key>/",
  420. "view": "archivebox.config.views.worker_detail_view",
  421. "name": "worker",
  422. },
  423. },
  424. {
  425. "route": "logs/",
  426. "view": "archivebox.config.views.log_list_view",
  427. "name": "Logs",
  428. "items": {
  429. "route": "<str:key>/",
  430. "view": "archivebox.config.views.log_detail_view",
  431. "name": "log",
  432. },
  433. },
  434. *abx.as_list(abx.pm.hook.get_ADMIN_DATA_VIEWS_URLS()),
  435. ],
  436. }
  437. ################################################################################
  438. ### Debug Settings
  439. ################################################################################
  440. # only enable debug toolbar when in DEBUG mode with --nothreading (it doesnt work in multithreaded mode)
  441. DEBUG_TOOLBAR = False
  442. DEBUG_TOOLBAR = DEBUG_TOOLBAR and DEBUG and ('--nothreading' in sys.argv) and ('--reload' not in sys.argv)
  443. if DEBUG_TOOLBAR:
  444. try:
  445. import debug_toolbar # noqa
  446. DEBUG_TOOLBAR = True
  447. except ImportError:
  448. DEBUG_TOOLBAR = False
  449. if DEBUG_TOOLBAR:
  450. INSTALLED_APPS = [*INSTALLED_APPS, 'debug_toolbar']
  451. INTERNAL_IPS = ['0.0.0.0', '127.0.0.1', '*']
  452. DEBUG_TOOLBAR_CONFIG = {
  453. "SHOW_TOOLBAR_CALLBACK": lambda request: True,
  454. "RENDER_PANELS": True,
  455. }
  456. DEBUG_TOOLBAR_PANELS = [
  457. 'debug_toolbar.panels.history.HistoryPanel',
  458. 'debug_toolbar.panels.versions.VersionsPanel',
  459. 'debug_toolbar.panels.timer.TimerPanel',
  460. 'debug_toolbar.panels.settings.SettingsPanel',
  461. 'debug_toolbar.panels.headers.HeadersPanel',
  462. 'debug_toolbar.panels.request.RequestPanel',
  463. 'debug_toolbar.panels.sql.SQLPanel',
  464. 'debug_toolbar.panels.staticfiles.StaticFilesPanel',
  465. # 'debug_toolbar.panels.templates.TemplatesPanel',
  466. 'debug_toolbar.panels.cache.CachePanel',
  467. 'debug_toolbar.panels.signals.SignalsPanel',
  468. 'debug_toolbar.panels.logging.LoggingPanel',
  469. 'debug_toolbar.panels.redirects.RedirectsPanel',
  470. 'debug_toolbar.panels.profiling.ProfilingPanel',
  471. 'djdt_flamegraph.FlamegraphPanel',
  472. ]
  473. MIDDLEWARE = [*MIDDLEWARE, 'debug_toolbar.middleware.DebugToolbarMiddleware']
  474. if DEBUG:
  475. from django_autotyping.typing import AutotypingSettingsDict
  476. INSTALLED_APPS += ['django_autotyping']
  477. AUTOTYPING: AutotypingSettingsDict = {
  478. "STUBS_GENERATION": {
  479. "LOCAL_STUBS_DIR": PACKAGE_DIR / "typings",
  480. }
  481. }
  482. # https://github.com/bensi94/Django-Requests-Tracker (improved version of django-debug-toolbar)
  483. # Must delete archivebox/templates/admin to use because it relies on some things we override
  484. # visit /__requests_tracker__/ to access
  485. DEBUG_REQUESTS_TRACKER = True
  486. DEBUG_REQUESTS_TRACKER = DEBUG_REQUESTS_TRACKER and DEBUG
  487. if DEBUG_REQUESTS_TRACKER:
  488. import requests_tracker
  489. INSTALLED_APPS += ["requests_tracker"]
  490. MIDDLEWARE += ["requests_tracker.middleware.requests_tracker_middleware"]
  491. INTERNAL_IPS = ["127.0.0.1", "10.0.2.2", "0.0.0.0", "*"]
  492. TEMPLATE_DIRS.insert(0, str(Path(inspect.getfile(requests_tracker)).parent / "templates"))
  493. REQUESTS_TRACKER_CONFIG = {
  494. "TRACK_SQL": True,
  495. "ENABLE_STACKTRACES": False,
  496. "IGNORE_PATHS_PATTERNS": (
  497. r".*/favicon\.ico",
  498. r".*\.png",
  499. r"/admin/jsi18n/",
  500. ),
  501. "IGNORE_SQL_PATTERNS": (
  502. r"^SELECT .* FROM django_migrations WHERE app = 'requests_tracker'",
  503. r"^SELECT .* FROM django_migrations WHERE app = 'auth'",
  504. ),
  505. }
  506. # # https://docs.pydantic.dev/logfire/integrations/django/ (similar to DataDog / NewRelic / etc.)
  507. # DEBUG_LOGFIRE = False
  508. # DEBUG_LOGFIRE = DEBUG_LOGFIRE and os.access(DATA_DIR / '.logfire', os.W_OK) and (DATA_DIR / '.logfire').is_dir()
  509. # For usage with https://www.jetadmin.io/integrations/django
  510. # INSTALLED_APPS += ['jet_django']
  511. # JET_PROJECT = 'archivebox'
  512. # JET_TOKEN = 'some-api-token-here'
  513. # import ipdb; ipdb.set_trace()