settings.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. __package__ = 'archivebox.core'
  2. import os
  3. import sys
  4. import re
  5. import logging
  6. import inspect
  7. import tempfile
  8. from typing import Any, Dict
  9. from pathlib import Path
  10. from django.utils.crypto import get_random_string
  11. from ..config import CONFIG
  12. from ..config_stubs import AttrDict
  13. assert isinstance(CONFIG, AttrDict)
  14. IS_MIGRATING = 'makemigrations' in sys.argv[:3] or 'migrate' in sys.argv[:3]
  15. IS_TESTING = 'test' in sys.argv[:3] or 'PYTEST_CURRENT_TEST' in os.environ
  16. IS_SHELL = 'shell' in sys.argv[:3] or 'shell_plus' in sys.argv[:3]
  17. ################################################################################
  18. ### ArchiveBox Plugin Settings
  19. ################################################################################
  20. BUILTIN_PLUGINS_DIR = CONFIG.PACKAGE_DIR / 'builtin_plugins' # /app/archivebox/builtin_plugins
  21. USERDATA_PLUGINS_DIR = CONFIG.OUTPUT_DIR / 'user_plugins' # /data/user_plugins
  22. def find_plugins_in_dir(plugins_dir, prefix: str) -> Dict[str, Path]:
  23. return {
  24. f'{prefix}.{plugin_entrypoint.parent.name}': plugin_entrypoint.parent
  25. for plugin_entrypoint in sorted(plugins_dir.glob('*/apps.py'))
  26. }
  27. INSTALLED_PLUGINS = {
  28. **find_plugins_in_dir(BUILTIN_PLUGINS_DIR, prefix='builtin_plugins'),
  29. **find_plugins_in_dir(USERDATA_PLUGINS_DIR, prefix='user_plugins'),
  30. }
  31. ### Plugins Globals (filled by plugantic.apps.load_plugins() after Django startup)
  32. PLUGINS = AttrDict({})
  33. HOOKS = AttrDict({})
  34. CONFIGS = AttrDict({})
  35. BINPROVIDERS = AttrDict({})
  36. BINARIES = AttrDict({})
  37. EXTRACTORS = AttrDict({})
  38. REPLAYERS = AttrDict({})
  39. CHECKS = AttrDict({})
  40. ADMINDATAVIEWS = AttrDict({})
  41. PLUGIN_KEYS = AttrDict({
  42. 'CONFIGS': CONFIGS,
  43. 'BINPROVIDERS': BINPROVIDERS,
  44. 'BINARIES': BINARIES,
  45. 'EXTRACTORS': EXTRACTORS,
  46. 'REPLAYERS': REPLAYERS,
  47. 'CHECKS': CHECKS,
  48. 'ADMINDATAVIEWS': ADMINDATAVIEWS,
  49. })
  50. ################################################################################
  51. ### Django Core Settings
  52. ################################################################################
  53. WSGI_APPLICATION = 'core.wsgi.application'
  54. ASGI_APPLICATION = "core.asgi.application"
  55. ROOT_URLCONF = 'core.urls'
  56. LOGIN_URL = '/accounts/login/'
  57. LOGOUT_REDIRECT_URL = os.environ.get('LOGOUT_REDIRECT_URL', '/')
  58. PASSWORD_RESET_URL = '/accounts/password_reset/'
  59. APPEND_SLASH = True
  60. DEBUG = CONFIG.DEBUG or ('--debug' in sys.argv)
  61. INSTALLED_APPS = [
  62. # Django default apps
  63. 'django.contrib.auth',
  64. 'django.contrib.contenttypes',
  65. 'django.contrib.sessions',
  66. 'django.contrib.messages',
  67. 'django.contrib.staticfiles',
  68. 'django.contrib.admin',
  69. # 3rd-party apps from PyPI
  70. 'django_jsonform', # handles rendering Pydantic models to Django HTML widgets/forms
  71. 'signal_webhooks', # handles REST API outbound webhooks
  72. # our own apps
  73. 'abid_utils', # handles ABID ID creation, handling, and models
  74. 'plugantic', # ArchiveBox plugin API definition + finding/registering/calling interface
  75. 'core', # core django model with Snapshot, ArchiveResult, etc.
  76. 'api', # Django-Ninja-based Rest API interfaces, config, APIToken model, etc.
  77. 'pkg', # ArchiveBox runtime package management interface for subdependencies
  78. # ArchiveBox plugins
  79. *INSTALLED_PLUGINS.keys(), # all plugin django-apps found in archivebox/builtin_plugins and data/user_plugins
  80. # 3rd-party apps from PyPI that need to be loaded last
  81. 'admin_data_views', # handles rendering some convenient automatic read-only views of data in Django admin
  82. 'django_extensions', # provides Django Debug Toolbar (and other non-debug helpers)
  83. ]
  84. MIDDLEWARE = [
  85. 'core.middleware.TimezoneMiddleware',
  86. 'django.middleware.security.SecurityMiddleware',
  87. 'django.contrib.sessions.middleware.SessionMiddleware',
  88. 'django.middleware.common.CommonMiddleware',
  89. 'django.middleware.csrf.CsrfViewMiddleware',
  90. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  91. 'core.middleware.ReverseProxyAuthMiddleware',
  92. 'django.contrib.messages.middleware.MessageMiddleware',
  93. 'core.middleware.CacheControlMiddleware',
  94. ]
  95. ################################################################################
  96. ### Authentication Settings
  97. ################################################################################
  98. # AUTH_USER_MODEL = 'auth.User' # cannot be easily changed unfortunately
  99. AUTHENTICATION_BACKENDS = [
  100. 'django.contrib.auth.backends.RemoteUserBackend',
  101. 'django.contrib.auth.backends.ModelBackend',
  102. ]
  103. if CONFIG.LDAP:
  104. try:
  105. import ldap
  106. from django_auth_ldap.config import LDAPSearch
  107. global AUTH_LDAP_SERVER_URI
  108. global AUTH_LDAP_BIND_DN
  109. global AUTH_LDAP_BIND_PASSWORD
  110. global AUTH_LDAP_USER_SEARCH
  111. global AUTH_LDAP_USER_ATTR_MAP
  112. AUTH_LDAP_SERVER_URI = CONFIG.LDAP_SERVER_URI
  113. AUTH_LDAP_BIND_DN = CONFIG.LDAP_BIND_DN
  114. AUTH_LDAP_BIND_PASSWORD = CONFIG.LDAP_BIND_PASSWORD
  115. assert AUTH_LDAP_SERVER_URI and CONFIG.LDAP_USERNAME_ATTR and CONFIG.LDAP_USER_FILTER, 'LDAP_* config options must all be set if LDAP=True'
  116. AUTH_LDAP_USER_SEARCH = LDAPSearch(
  117. CONFIG.LDAP_USER_BASE,
  118. ldap.SCOPE_SUBTREE,
  119. '(&(' + CONFIG.LDAP_USERNAME_ATTR + '=%(user)s)' + CONFIG.LDAP_USER_FILTER + ')',
  120. )
  121. AUTH_LDAP_USER_ATTR_MAP = {
  122. 'username': CONFIG.LDAP_USERNAME_ATTR,
  123. 'first_name': CONFIG.LDAP_FIRSTNAME_ATTR,
  124. 'last_name': CONFIG.LDAP_LASTNAME_ATTR,
  125. 'email': CONFIG.LDAP_EMAIL_ATTR,
  126. }
  127. AUTHENTICATION_BACKENDS = [
  128. 'django.contrib.auth.backends.ModelBackend',
  129. 'django_auth_ldap.backend.LDAPBackend',
  130. ]
  131. except ModuleNotFoundError:
  132. sys.stderr.write('[X] Error: Found LDAP=True config but LDAP packages not installed. You may need to run: pip install archivebox[ldap]\n\n')
  133. # dont hard exit here. in case the user is just running "archivebox version" or "archivebox help", we still want those to work despite broken ldap
  134. # sys.exit(1)
  135. ################################################################################
  136. ### Staticfile and Template Settings
  137. ################################################################################
  138. STATIC_URL = '/static/'
  139. STATICFILES_DIRS = [
  140. *([str(CONFIG.CUSTOM_TEMPLATES_DIR / 'static')] if CONFIG.CUSTOM_TEMPLATES_DIR else []),
  141. str(Path(CONFIG.PACKAGE_DIR) / CONFIG.TEMPLATES_DIR_NAME / 'static'),
  142. ]
  143. TEMPLATE_DIRS = [
  144. *([str(CONFIG.CUSTOM_TEMPLATES_DIR)] if CONFIG.CUSTOM_TEMPLATES_DIR else []),
  145. str(Path(CONFIG.PACKAGE_DIR) / CONFIG.TEMPLATES_DIR_NAME / 'core'),
  146. str(Path(CONFIG.PACKAGE_DIR) / CONFIG.TEMPLATES_DIR_NAME / 'admin'),
  147. str(Path(CONFIG.PACKAGE_DIR) / CONFIG.TEMPLATES_DIR_NAME),
  148. ]
  149. TEMPLATES = [
  150. {
  151. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  152. 'DIRS': TEMPLATE_DIRS,
  153. 'APP_DIRS': True,
  154. 'OPTIONS': {
  155. 'context_processors': [
  156. 'django.template.context_processors.debug',
  157. 'django.template.context_processors.request',
  158. 'django.contrib.auth.context_processors.auth',
  159. 'django.contrib.messages.context_processors.messages',
  160. ],
  161. },
  162. },
  163. ]
  164. ################################################################################
  165. ### External Service Settings
  166. ################################################################################
  167. CACHE_DB_FILENAME = 'cache.sqlite3'
  168. CACHE_DB_PATH = CONFIG.CACHE_DIR / CACHE_DB_FILENAME
  169. CACHE_DB_TABLE = 'django_cache'
  170. DATABASE_FILE = Path(CONFIG.OUTPUT_DIR) / CONFIG.SQL_INDEX_FILENAME
  171. DATABASE_NAME = os.environ.get("ARCHIVEBOX_DATABASE_NAME", str(DATABASE_FILE))
  172. DATABASES = {
  173. 'default': {
  174. 'ENGINE': 'django.db.backends.sqlite3',
  175. 'NAME': DATABASE_NAME,
  176. 'OPTIONS': {
  177. 'timeout': 60,
  178. 'check_same_thread': False,
  179. },
  180. 'TIME_ZONE': CONFIG.TIMEZONE,
  181. # DB setup is sometimes modified at runtime by setup_django() in config.py
  182. },
  183. # 'cache': {
  184. # 'ENGINE': 'django.db.backends.sqlite3',
  185. # 'NAME': CACHE_DB_PATH,
  186. # 'OPTIONS': {
  187. # 'timeout': 60,
  188. # 'check_same_thread': False,
  189. # },
  190. # 'TIME_ZONE': CONFIG.TIMEZONE,
  191. # },
  192. }
  193. MIGRATION_MODULES = {'signal_webhooks': None}
  194. # as much as I'd love this to be a UUID or ULID field, it's not supported yet as of Django 5.0
  195. DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
  196. CACHES = {
  197. 'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'},
  198. # 'sqlite': {'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'cache'},
  199. # 'dummy': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'},
  200. # 'filebased': {"BACKEND": "django.core.cache.backends.filebased.FileBasedCache", "LOCATION": CACHE_DIR / 'cache_filebased'},
  201. }
  202. EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
  203. STORAGES = {
  204. "default": {
  205. "BACKEND": "django.core.files.storage.FileSystemStorage",
  206. },
  207. "staticfiles": {
  208. "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
  209. },
  210. "archive": {
  211. "BACKEND": "django.core.files.storage.FileSystemStorage",
  212. "OPTIONS": {
  213. "base_url": "/archive/",
  214. "location": CONFIG.ARCHIVE_DIR,
  215. },
  216. },
  217. # "personas": {
  218. # "BACKEND": "django.core.files.storage.FileSystemStorage",
  219. # "OPTIONS": {
  220. # "base_url": "/personas/",
  221. # "location": PERSONAS_DIR,
  222. # },
  223. # },
  224. }
  225. ################################################################################
  226. ### Security Settings
  227. ################################################################################
  228. SECRET_KEY = CONFIG.SECRET_KEY or get_random_string(50, 'abcdefghijklmnopqrstuvwxyz0123456789_')
  229. ALLOWED_HOSTS = CONFIG.ALLOWED_HOSTS.split(',')
  230. CSRF_TRUSTED_ORIGINS = list(set(CONFIG.CSRF_TRUSTED_ORIGINS.split(',')))
  231. # automatically fix case when user sets ALLOWED_HOSTS (e.g. to archivebox.example.com)
  232. # but forgets to add https://archivebox.example.com to CSRF_TRUSTED_ORIGINS
  233. for hostname in ALLOWED_HOSTS:
  234. https_endpoint = f'https://{hostname}'
  235. if hostname != '*' and https_endpoint not in CSRF_TRUSTED_ORIGINS:
  236. print(f'[!] WARNING: {https_endpoint} from ALLOWED_HOSTS should be added to CSRF_TRUSTED_ORIGINS')
  237. CSRF_TRUSTED_ORIGINS.append(https_endpoint)
  238. SECURE_BROWSER_XSS_FILTER = True
  239. SECURE_CONTENT_TYPE_NOSNIFF = True
  240. SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'
  241. CSRF_COOKIE_SECURE = False
  242. SESSION_COOKIE_SECURE = False
  243. SESSION_COOKIE_HTTPONLY = True
  244. SESSION_COOKIE_DOMAIN = None
  245. SESSION_COOKIE_AGE = 1209600 # 2 weeks
  246. SESSION_EXPIRE_AT_BROWSER_CLOSE = False
  247. SESSION_SAVE_EVERY_REQUEST = False
  248. SESSION_ENGINE = "django.contrib.sessions.backends.db"
  249. AUTH_PASSWORD_VALIDATORS = [
  250. {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
  251. {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
  252. {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
  253. {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
  254. ]
  255. DATA_UPLOAD_MAX_NUMBER_FIELDS = None
  256. ################################################################################
  257. ### Shell Settings
  258. ################################################################################
  259. SHELL_PLUS = 'ipython'
  260. SHELL_PLUS_PRINT_SQL = False
  261. IPYTHON_ARGUMENTS = ['--no-confirm-exit', '--no-banner']
  262. IPYTHON_KERNEL_DISPLAY_NAME = 'ArchiveBox Django Shell'
  263. if IS_SHELL:
  264. os.environ['PYTHONSTARTUP'] = str(Path(CONFIG.PACKAGE_DIR) / 'core' / 'welcome_message.py')
  265. ################################################################################
  266. ### Internationalization & Localization Settings
  267. ################################################################################
  268. LANGUAGE_CODE = 'en-us'
  269. USE_I18N = True
  270. USE_TZ = True
  271. DATETIME_FORMAT = 'Y-m-d h:i:s A'
  272. SHORT_DATETIME_FORMAT = 'Y-m-d h:i:s A'
  273. TIME_ZONE = CONFIG.TIMEZONE # django convention is TIME_ZONE, archivebox config uses TIMEZONE, they are equivalent
  274. from django.conf.locale.en import formats as en_formats # type: ignore
  275. en_formats.DATETIME_FORMAT = DATETIME_FORMAT
  276. en_formats.SHORT_DATETIME_FORMAT = SHORT_DATETIME_FORMAT
  277. ################################################################################
  278. ### Logging Settings
  279. ################################################################################
  280. IGNORABLE_404_URLS = [
  281. re.compile(r'apple-touch-icon.*\.png$'),
  282. re.compile(r'favicon\.ico$'),
  283. re.compile(r'robots\.txt$'),
  284. re.compile(r'.*\.(css|js)\.map$'),
  285. ]
  286. IGNORABLE_200_URLS = [
  287. re.compile(r'.*"GET /static/.* HTTP/.*" 2|3.+', re.I | re.M),
  288. re.compile(r'.*"GET /admin/jsi18n/ HTTP/1.1" 200 .+', re.I | re.M),
  289. ]
  290. class NoisyRequestsFilter(logging.Filter):
  291. def filter(self, record) -> bool:
  292. logline = record.getMessage()
  293. # ignore harmless 404s for the patterns in IGNORABLE_404_URLS
  294. for ignorable_url_pattern in IGNORABLE_404_URLS:
  295. ignorable_log_pattern = re.compile(f'"GET /.*/?{ignorable_url_pattern.pattern[:-1]} HTTP/.*" (200|30.|404) .+$', re.I | re.M)
  296. if ignorable_log_pattern.match(logline):
  297. return False
  298. ignorable_log_pattern = re.compile(f'Not Found: /.*/?{ignorable_url_pattern.pattern}', re.I | re.M)
  299. if ignorable_log_pattern.match(logline):
  300. return False
  301. # ignore staticfile requests that 200 or 30*
  302. for ignorable_url_pattern in IGNORABLE_200_URLS:
  303. if ignorable_log_pattern.match(logline):
  304. return False
  305. return True
  306. ERROR_LOG = tempfile.NamedTemporaryFile().name
  307. if CONFIG.LOGS_DIR.exists():
  308. ERROR_LOG = (CONFIG.LOGS_DIR / 'errors.log')
  309. else:
  310. # historically too many edge cases here around creating log dir w/ correct permissions early on
  311. # if there's an issue on startup, we trash the log and let user figure it out via stdout/stderr
  312. print(f'[!] WARNING: data/logs dir does not exist. Logging to temp file: {ERROR_LOG}')
  313. LOGGING = {
  314. 'version': 1,
  315. 'disable_existing_loggers': False,
  316. 'handlers': {
  317. "console": {
  318. "level": "DEBUG",
  319. "filters": [],
  320. 'formatter': 'simple',
  321. "class": "logging.StreamHandler",
  322. 'filters': ['noisyrequestsfilter'],
  323. },
  324. 'logfile': {
  325. 'level': 'ERROR',
  326. 'class': 'logging.handlers.RotatingFileHandler',
  327. 'filename': ERROR_LOG,
  328. 'maxBytes': 1024 * 1024 * 25, # 25 MB
  329. 'backupCount': 10,
  330. 'formatter': 'verbose',
  331. 'filters': ['noisyrequestsfilter'],
  332. },
  333. # "mail_admins": {
  334. # "level": "ERROR",
  335. # "filters": ["require_debug_false"],
  336. # "class": "django.utils.log.AdminEmailHandler",
  337. # },
  338. },
  339. 'filters': {
  340. 'noisyrequestsfilter': {
  341. '()': NoisyRequestsFilter,
  342. },
  343. "require_debug_false": {
  344. "()": "django.utils.log.RequireDebugFalse",
  345. },
  346. "require_debug_true": {
  347. "()": "django.utils.log.RequireDebugTrue",
  348. },
  349. },
  350. 'formatters': {
  351. 'verbose': {
  352. 'format': '{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}',
  353. 'style': '{',
  354. },
  355. 'simple': {
  356. 'format': '{name} {message}',
  357. 'style': '{',
  358. },
  359. "django.server": {
  360. "()": "django.utils.log.ServerFormatter",
  361. "format": "[{server_time}] {message}",
  362. "style": "{",
  363. },
  364. },
  365. 'loggers': {
  366. 'api': {
  367. 'handlers': ['console', 'logfile'],
  368. 'level': 'DEBUG',
  369. },
  370. 'checks': {
  371. 'handlers': ['console', 'logfile'],
  372. 'level': 'DEBUG',
  373. },
  374. 'core': {
  375. 'handlers': ['console', 'logfile'],
  376. 'level': 'DEBUG',
  377. },
  378. 'builtin_plugins': {
  379. 'handlers': ['console', 'logfile'],
  380. 'level': 'DEBUG',
  381. },
  382. 'django': {
  383. 'handlers': ['console', 'logfile'],
  384. 'level': 'INFO',
  385. 'filters': ['noisyrequestsfilter'],
  386. },
  387. 'django.server': {
  388. 'handlers': ['console', 'logfile'],
  389. 'level': 'INFO',
  390. 'filters': ['noisyrequestsfilter'],
  391. 'propagate': False,
  392. "formatter": "django.server",
  393. },
  394. 'django.request': {
  395. 'handlers': ['console', 'logfile'],
  396. 'level': 'INFO',
  397. 'filters': ['noisyrequestsfilter'],
  398. 'propagate': False,
  399. "formatter": "django.server",
  400. },
  401. },
  402. }
  403. ################################################################################
  404. ### REST API Outbound Webhooks settings
  405. ################################################################################
  406. # Add default webhook configuration to the User model
  407. SIGNAL_WEBHOOKS_CUSTOM_MODEL = 'api.models.OutboundWebhook'
  408. SIGNAL_WEBHOOKS = {
  409. "HOOKS": {
  410. # ... is a special sigil value that means "use the default autogenerated hooks"
  411. "django.contrib.auth.models.User": ...,
  412. "core.models.Snapshot": ...,
  413. "core.models.ArchiveResult": ...,
  414. "core.models.Tag": ...,
  415. "api.models.APIToken": ...,
  416. },
  417. }
  418. ################################################################################
  419. ### Admin Data View Settings
  420. ################################################################################
  421. ADMIN_DATA_VIEWS = {
  422. "NAME": "Environment",
  423. "URLS": [
  424. {
  425. "route": "config/",
  426. "view": "core.views.live_config_list_view",
  427. "name": "Configuration",
  428. "items": {
  429. "route": "<str:key>/",
  430. "view": "core.views.live_config_value_view",
  431. "name": "config_val",
  432. },
  433. },
  434. {
  435. "route": "binaries/",
  436. "view": "plugantic.views.binaries_list_view",
  437. "name": "Binaries",
  438. "items": {
  439. "route": "<str:key>/",
  440. "view": "plugantic.views.binary_detail_view",
  441. "name": "binary",
  442. },
  443. },
  444. {
  445. "route": "plugins/",
  446. "view": "plugantic.views.plugins_list_view",
  447. "name": "Plugins",
  448. "items": {
  449. "route": "<str:key>/",
  450. "view": "plugantic.views.plugin_detail_view",
  451. "name": "plugin",
  452. },
  453. },
  454. ],
  455. }
  456. ################################################################################
  457. ### Debug Settings
  458. ################################################################################
  459. # only enable debug toolbar when in DEBUG mode with --nothreading (it doesnt work in multithreaded mode)
  460. DEBUG_TOOLBAR = False
  461. DEBUG_TOOLBAR = DEBUG_TOOLBAR and DEBUG and ('--nothreading' in sys.argv) and ('--reload' not in sys.argv)
  462. if DEBUG_TOOLBAR:
  463. try:
  464. import debug_toolbar # noqa
  465. DEBUG_TOOLBAR = True
  466. except ImportError:
  467. DEBUG_TOOLBAR = False
  468. if DEBUG_TOOLBAR:
  469. INSTALLED_APPS = [*INSTALLED_APPS, 'debug_toolbar']
  470. INTERNAL_IPS = ['0.0.0.0', '127.0.0.1', '*']
  471. DEBUG_TOOLBAR_CONFIG = {
  472. "SHOW_TOOLBAR_CALLBACK": lambda request: True,
  473. "RENDER_PANELS": True,
  474. }
  475. DEBUG_TOOLBAR_PANELS = [
  476. 'debug_toolbar.panels.history.HistoryPanel',
  477. 'debug_toolbar.panels.versions.VersionsPanel',
  478. 'debug_toolbar.panels.timer.TimerPanel',
  479. 'debug_toolbar.panels.settings.SettingsPanel',
  480. 'debug_toolbar.panels.headers.HeadersPanel',
  481. 'debug_toolbar.panels.request.RequestPanel',
  482. 'debug_toolbar.panels.sql.SQLPanel',
  483. 'debug_toolbar.panels.staticfiles.StaticFilesPanel',
  484. # 'debug_toolbar.panels.templates.TemplatesPanel',
  485. 'debug_toolbar.panels.cache.CachePanel',
  486. 'debug_toolbar.panels.signals.SignalsPanel',
  487. 'debug_toolbar.panels.logging.LoggingPanel',
  488. 'debug_toolbar.panels.redirects.RedirectsPanel',
  489. 'debug_toolbar.panels.profiling.ProfilingPanel',
  490. 'djdt_flamegraph.FlamegraphPanel',
  491. ]
  492. MIDDLEWARE = [*MIDDLEWARE, 'debug_toolbar.middleware.DebugToolbarMiddleware']
  493. if DEBUG:
  494. from django_autotyping.typing import AutotypingSettingsDict
  495. INSTALLED_APPS += ['django_autotyping']
  496. AUTOTYPING: AutotypingSettingsDict = {
  497. "STUBS_GENERATION": {
  498. "LOCAL_STUBS_DIR": Path(CONFIG.PACKAGE_DIR) / "typings",
  499. }
  500. }
  501. # https://github.com/bensi94/Django-Requests-Tracker (improved version of django-debug-toolbar)
  502. # Must delete archivebox/templates/admin to use because it relies on some things we override
  503. # visit /__requests_tracker__/ to access
  504. DEBUG_REQUESTS_TRACKER = True
  505. DEBUG_REQUESTS_TRACKER = DEBUG_REQUESTS_TRACKER and DEBUG
  506. if DEBUG_REQUESTS_TRACKER:
  507. import requests_tracker
  508. INSTALLED_APPS += ["requests_tracker"]
  509. MIDDLEWARE += ["requests_tracker.middleware.requests_tracker_middleware"]
  510. INTERNAL_IPS = ["127.0.0.1", "10.0.2.2", "0.0.0.0", "*"]
  511. TEMPLATE_DIRS.insert(0, str(Path(inspect.getfile(requests_tracker)).parent / "templates"))
  512. REQUESTS_TRACKER_CONFIG = {
  513. "TRACK_SQL": True,
  514. "ENABLE_STACKTRACES": False,
  515. "IGNORE_PATHS_PATTERNS": (
  516. r".*/favicon\.ico",
  517. r".*\.png",
  518. r"/admin/jsi18n/",
  519. ),
  520. "IGNORE_SQL_PATTERNS": (
  521. r"^SELECT .* FROM django_migrations WHERE app = 'requests_tracker'",
  522. r"^SELECT .* FROM django_migrations WHERE app = 'auth'",
  523. ),
  524. }
  525. # https://docs.pydantic.dev/logfire/integrations/django/ (similar to DataDog / NewRelic / etc.)
  526. DEBUG_LOGFIRE = False
  527. DEBUG_LOGFIRE = DEBUG_LOGFIRE and (Path(CONFIG.OUTPUT_DIR) / '.logfire').is_dir()
  528. # For usage with https://www.jetadmin.io/integrations/django
  529. # INSTALLED_APPS += ['jet_django']
  530. # JET_PROJECT = 'archivebox'
  531. # JET_TOKEN = 'some-api-token-here'