settings.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. import importlib
  2. import logging
  3. import os
  4. import platform
  5. import re
  6. import socket
  7. import warnings
  8. from urllib.parse import urlsplit
  9. from django.contrib.messages import constants as messages
  10. from django.core.exceptions import ImproperlyConfigured, ValidationError
  11. from django.core.validators import URLValidator
  12. #
  13. # Environment setup
  14. #
  15. VERSION = '3.0-beta1'
  16. # Hostname
  17. HOSTNAME = platform.node()
  18. # Set the base directory two levels up
  19. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  20. # Validate Python version
  21. if platform.python_version_tuple() < ('3', '7'):
  22. raise RuntimeError(
  23. f"NetBox requires Python 3.7 or higher (current: Python {platform.python_version()})"
  24. )
  25. #
  26. # Configuration import
  27. #
  28. # Import configuration parameters
  29. try:
  30. from netbox import configuration
  31. except ModuleNotFoundError as e:
  32. if getattr(e, 'name') == 'configuration':
  33. raise ImproperlyConfigured(
  34. "Configuration file is not present. Please define netbox/netbox/configuration.py per the documentation."
  35. )
  36. raise
  37. # Enforce required configuration parameters
  38. for parameter in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY', 'REDIS']:
  39. if not hasattr(configuration, parameter):
  40. raise ImproperlyConfigured(
  41. "Required parameter {} is missing from configuration.py.".format(parameter)
  42. )
  43. # Set required parameters
  44. ALLOWED_HOSTS = getattr(configuration, 'ALLOWED_HOSTS')
  45. DATABASE = getattr(configuration, 'DATABASE')
  46. REDIS = getattr(configuration, 'REDIS')
  47. SECRET_KEY = getattr(configuration, 'SECRET_KEY')
  48. # Set optional parameters
  49. ADMINS = getattr(configuration, 'ADMINS', [])
  50. ALLOWED_URL_SCHEMES = getattr(configuration, 'ALLOWED_URL_SCHEMES', (
  51. 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
  52. ))
  53. BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', '')
  54. BANNER_LOGIN = getattr(configuration, 'BANNER_LOGIN', '')
  55. BANNER_TOP = getattr(configuration, 'BANNER_TOP', '')
  56. BASE_PATH = getattr(configuration, 'BASE_PATH', '')
  57. if BASE_PATH:
  58. BASE_PATH = BASE_PATH.strip('/') + '/' # Enforce trailing slash only
  59. CACHE_TIMEOUT = getattr(configuration, 'CACHE_TIMEOUT', 900)
  60. CHANGELOG_RETENTION = getattr(configuration, 'CHANGELOG_RETENTION', 90)
  61. CORS_ORIGIN_ALLOW_ALL = getattr(configuration, 'CORS_ORIGIN_ALLOW_ALL', False)
  62. CORS_ORIGIN_REGEX_WHITELIST = getattr(configuration, 'CORS_ORIGIN_REGEX_WHITELIST', [])
  63. CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', [])
  64. CUSTOM_VALIDATORS = getattr(configuration, 'CUSTOM_VALIDATORS', {})
  65. DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y')
  66. DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
  67. DEBUG = getattr(configuration, 'DEBUG', False)
  68. DEVELOPER = getattr(configuration, 'DEVELOPER', False)
  69. DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs'))
  70. EMAIL = getattr(configuration, 'EMAIL', {})
  71. ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False)
  72. EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', [])
  73. HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None)
  74. INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
  75. LOGGING = getattr(configuration, 'LOGGING', {})
  76. LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
  77. LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None)
  78. MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False)
  79. MAPS_URL = getattr(configuration, 'MAPS_URL', 'https://maps.google.com/?q=')
  80. MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000)
  81. MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/')
  82. METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False)
  83. NAPALM_ARGS = getattr(configuration, 'NAPALM_ARGS', {})
  84. NAPALM_PASSWORD = getattr(configuration, 'NAPALM_PASSWORD', '')
  85. NAPALM_TIMEOUT = getattr(configuration, 'NAPALM_TIMEOUT', 30)
  86. NAPALM_USERNAME = getattr(configuration, 'NAPALM_USERNAME', '')
  87. PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
  88. PLUGINS = getattr(configuration, 'PLUGINS', [])
  89. PLUGINS_CONFIG = getattr(configuration, 'PLUGINS_CONFIG', {})
  90. PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
  91. RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = getattr(configuration, 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 22)
  92. RACK_ELEVATION_DEFAULT_UNIT_WIDTH = getattr(configuration, 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH', 220)
  93. REMOTE_AUTH_AUTO_CREATE_USER = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_USER', False)
  94. REMOTE_AUTH_BACKEND = getattr(configuration, 'REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
  95. REMOTE_AUTH_DEFAULT_GROUPS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_GROUPS', [])
  96. REMOTE_AUTH_DEFAULT_PERMISSIONS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_PERMISSIONS', {})
  97. REMOTE_AUTH_ENABLED = getattr(configuration, 'REMOTE_AUTH_ENABLED', False)
  98. REMOTE_AUTH_HEADER = getattr(configuration, 'REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER')
  99. RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None)
  100. RELEASE_CHECK_TIMEOUT = getattr(configuration, 'RELEASE_CHECK_TIMEOUT', 24 * 3600)
  101. REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/')
  102. RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300)
  103. SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/')
  104. SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None)
  105. SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid')
  106. SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d')
  107. SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
  108. SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
  109. STORAGE_BACKEND = getattr(configuration, 'STORAGE_BACKEND', None)
  110. STORAGE_CONFIG = getattr(configuration, 'STORAGE_CONFIG', {})
  111. TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a')
  112. TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC')
  113. # Validate update repo URL and timeout
  114. if RELEASE_CHECK_URL:
  115. validator = URLValidator(
  116. message=(
  117. "RELEASE_CHECK_URL must be a valid API URL. Example: "
  118. "https://api.github.com/repos/netbox-community/netbox"
  119. )
  120. )
  121. try:
  122. validator(RELEASE_CHECK_URL)
  123. except ValidationError as err:
  124. raise ImproperlyConfigured(str(err))
  125. # Enforce a minimum cache timeout for update checks
  126. if RELEASE_CHECK_TIMEOUT < 3600:
  127. raise ImproperlyConfigured("RELEASE_CHECK_TIMEOUT has to be at least 3600 seconds (1 hour)")
  128. #
  129. # Database
  130. #
  131. # Only PostgreSQL is supported
  132. if METRICS_ENABLED:
  133. DATABASE.update({
  134. 'ENGINE': 'django_prometheus.db.backends.postgresql'
  135. })
  136. else:
  137. DATABASE.update({
  138. 'ENGINE': 'django.db.backends.postgresql'
  139. })
  140. DATABASES = {
  141. 'default': DATABASE,
  142. }
  143. #
  144. # Media storage
  145. #
  146. if STORAGE_BACKEND is not None:
  147. DEFAULT_FILE_STORAGE = STORAGE_BACKEND
  148. # django-storages
  149. if STORAGE_BACKEND.startswith('storages.'):
  150. try:
  151. import storages.utils
  152. except ModuleNotFoundError as e:
  153. if getattr(e, 'name') == 'storages':
  154. raise ImproperlyConfigured(
  155. f"STORAGE_BACKEND is set to {STORAGE_BACKEND} but django-storages is not present. It can be "
  156. f"installed by running 'pip install django-storages'."
  157. )
  158. raise e
  159. # Monkey-patch django-storages to fetch settings from STORAGE_CONFIG
  160. def _setting(name, default=None):
  161. if name in STORAGE_CONFIG:
  162. return STORAGE_CONFIG[name]
  163. return globals().get(name, default)
  164. storages.utils.setting = _setting
  165. if STORAGE_CONFIG and STORAGE_BACKEND is None:
  166. warnings.warn(
  167. "STORAGE_CONFIG has been set in configuration.py but STORAGE_BACKEND is not defined. STORAGE_CONFIG will be "
  168. "ignored."
  169. )
  170. #
  171. # Redis
  172. #
  173. # Background task queuing
  174. if 'tasks' not in REDIS:
  175. raise ImproperlyConfigured(
  176. "REDIS section in configuration.py is missing the 'tasks' subsection."
  177. )
  178. TASKS_REDIS = REDIS['tasks']
  179. TASKS_REDIS_HOST = TASKS_REDIS.get('HOST', 'localhost')
  180. TASKS_REDIS_PORT = TASKS_REDIS.get('PORT', 6379)
  181. TASKS_REDIS_SENTINELS = TASKS_REDIS.get('SENTINELS', [])
  182. TASKS_REDIS_USING_SENTINEL = all([
  183. isinstance(TASKS_REDIS_SENTINELS, (list, tuple)),
  184. len(TASKS_REDIS_SENTINELS) > 0
  185. ])
  186. TASKS_REDIS_SENTINEL_SERVICE = TASKS_REDIS.get('SENTINEL_SERVICE', 'default')
  187. TASKS_REDIS_SENTINEL_TIMEOUT = TASKS_REDIS.get('SENTINEL_TIMEOUT', 10)
  188. TASKS_REDIS_PASSWORD = TASKS_REDIS.get('PASSWORD', '')
  189. TASKS_REDIS_DATABASE = TASKS_REDIS.get('DATABASE', 0)
  190. TASKS_REDIS_SSL = TASKS_REDIS.get('SSL', False)
  191. TASKS_REDIS_SKIP_TLS_VERIFY = TASKS_REDIS.get('INSECURE_SKIP_TLS_VERIFY', False)
  192. # Caching
  193. if 'caching' not in REDIS:
  194. raise ImproperlyConfigured(
  195. "REDIS section in configuration.py is missing caching subsection."
  196. )
  197. CACHING_REDIS = REDIS['caching']
  198. CACHING_REDIS_HOST = CACHING_REDIS.get('HOST', 'localhost')
  199. CACHING_REDIS_PORT = CACHING_REDIS.get('PORT', 6379)
  200. CACHING_REDIS_SENTINELS = CACHING_REDIS.get('SENTINELS', [])
  201. CACHING_REDIS_USING_SENTINEL = all([
  202. isinstance(CACHING_REDIS_SENTINELS, (list, tuple)),
  203. len(CACHING_REDIS_SENTINELS) > 0
  204. ])
  205. CACHING_REDIS_SENTINEL_SERVICE = CACHING_REDIS.get('SENTINEL_SERVICE', 'default')
  206. CACHING_REDIS_PASSWORD = CACHING_REDIS.get('PASSWORD', '')
  207. CACHING_REDIS_DATABASE = CACHING_REDIS.get('DATABASE', 0)
  208. CACHING_REDIS_SSL = CACHING_REDIS.get('SSL', False)
  209. CACHING_REDIS_SKIP_TLS_VERIFY = CACHING_REDIS.get('INSECURE_SKIP_TLS_VERIFY', False)
  210. #
  211. # Sessions
  212. #
  213. if LOGIN_TIMEOUT is not None:
  214. # Django default is 1209600 seconds (14 days)
  215. SESSION_COOKIE_AGE = LOGIN_TIMEOUT
  216. if SESSION_FILE_PATH is not None:
  217. SESSION_ENGINE = 'django.contrib.sessions.backends.file'
  218. #
  219. # Email
  220. #
  221. EMAIL_HOST = EMAIL.get('SERVER')
  222. EMAIL_HOST_USER = EMAIL.get('USERNAME')
  223. EMAIL_HOST_PASSWORD = EMAIL.get('PASSWORD')
  224. EMAIL_PORT = EMAIL.get('PORT', 25)
  225. EMAIL_SSL_CERTFILE = EMAIL.get('SSL_CERTFILE')
  226. EMAIL_SSL_KEYFILE = EMAIL.get('SSL_KEYFILE')
  227. EMAIL_SUBJECT_PREFIX = '[NetBox] '
  228. EMAIL_USE_SSL = EMAIL.get('USE_SSL', False)
  229. EMAIL_USE_TLS = EMAIL.get('USE_TLS', False)
  230. EMAIL_TIMEOUT = EMAIL.get('TIMEOUT', 10)
  231. SERVER_EMAIL = EMAIL.get('FROM_EMAIL')
  232. #
  233. # Django
  234. #
  235. INSTALLED_APPS = [
  236. 'django.contrib.admin',
  237. 'django.contrib.auth',
  238. 'django.contrib.contenttypes',
  239. 'django.contrib.sessions',
  240. 'django.contrib.messages',
  241. 'django.contrib.staticfiles',
  242. 'django.contrib.humanize',
  243. 'cacheops',
  244. 'corsheaders',
  245. 'debug_toolbar',
  246. 'django_filters',
  247. 'django_tables2',
  248. 'django_prometheus',
  249. 'mptt',
  250. 'rest_framework',
  251. 'taggit',
  252. 'timezone_field',
  253. 'circuits',
  254. 'dcim',
  255. 'ipam',
  256. 'extras',
  257. 'tenancy',
  258. 'users',
  259. 'utilities',
  260. 'virtualization',
  261. 'django_rq', # Must come after extras to allow overriding management commands
  262. 'drf_yasg',
  263. ]
  264. # Middleware
  265. MIDDLEWARE = [
  266. 'debug_toolbar.middleware.DebugToolbarMiddleware',
  267. 'django_prometheus.middleware.PrometheusBeforeMiddleware',
  268. 'corsheaders.middleware.CorsMiddleware',
  269. 'django.contrib.sessions.middleware.SessionMiddleware',
  270. 'django.middleware.common.CommonMiddleware',
  271. 'django.middleware.csrf.CsrfViewMiddleware',
  272. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  273. 'django.contrib.messages.middleware.MessageMiddleware',
  274. 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  275. 'django.middleware.security.SecurityMiddleware',
  276. 'netbox.middleware.ExceptionHandlingMiddleware',
  277. 'netbox.middleware.RemoteUserMiddleware',
  278. 'netbox.middleware.LoginRequiredMiddleware',
  279. 'netbox.middleware.APIVersionMiddleware',
  280. 'netbox.middleware.ObjectChangeMiddleware',
  281. 'django_prometheus.middleware.PrometheusAfterMiddleware',
  282. ]
  283. ROOT_URLCONF = 'netbox.urls'
  284. TEMPLATES_DIR = BASE_DIR + '/templates'
  285. TEMPLATES = [
  286. {
  287. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  288. 'DIRS': [TEMPLATES_DIR],
  289. 'APP_DIRS': True,
  290. 'OPTIONS': {
  291. 'context_processors': [
  292. 'django.template.context_processors.debug',
  293. 'django.template.context_processors.request',
  294. 'django.template.context_processors.media',
  295. 'django.contrib.auth.context_processors.auth',
  296. 'django.contrib.messages.context_processors.messages',
  297. 'netbox.context_processors.settings_and_registry',
  298. ],
  299. },
  300. },
  301. ]
  302. # Set up authentication backends
  303. AUTHENTICATION_BACKENDS = [
  304. REMOTE_AUTH_BACKEND,
  305. 'netbox.authentication.ObjectPermissionBackend',
  306. ]
  307. # Internationalization
  308. LANGUAGE_CODE = 'en-us'
  309. USE_I18N = True
  310. USE_TZ = True
  311. # WSGI
  312. WSGI_APPLICATION = 'netbox.wsgi.application'
  313. SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
  314. USE_X_FORWARDED_HOST = True
  315. X_FRAME_OPTIONS = 'SAMEORIGIN'
  316. # Static files (CSS, JavaScript, Images)
  317. STATIC_ROOT = BASE_DIR + '/static'
  318. STATIC_URL = '/{}static/'.format(BASE_PATH)
  319. STATICFILES_DIRS = (
  320. os.path.join(BASE_DIR, "project-static", "dist"),
  321. os.path.join(BASE_DIR, "project-static", "img"),
  322. )
  323. # Media
  324. MEDIA_URL = '/{}media/'.format(BASE_PATH)
  325. # Disable default limit of 1000 fields per request. Needed for bulk deletion of objects. (Added in Django 1.10.)
  326. DATA_UPLOAD_MAX_NUMBER_FIELDS = None
  327. # Messages
  328. MESSAGE_TAGS = {
  329. messages.ERROR: 'danger',
  330. }
  331. # Authentication URLs
  332. LOGIN_URL = '/{}login/'.format(BASE_PATH)
  333. CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
  334. DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
  335. # Exclude potentially sensitive models from wildcard view exemption. These may still be exempted
  336. # by specifying the model individually in the EXEMPT_VIEW_PERMISSIONS configuration parameter.
  337. EXEMPT_EXCLUDE_MODELS = (
  338. ('auth', 'group'),
  339. ('auth', 'user'),
  340. ('users', 'objectpermission'),
  341. )
  342. #
  343. # Caching
  344. #
  345. if CACHING_REDIS_USING_SENTINEL:
  346. CACHEOPS_SENTINEL = {
  347. 'locations': CACHING_REDIS_SENTINELS,
  348. 'service_name': CACHING_REDIS_SENTINEL_SERVICE,
  349. 'db': CACHING_REDIS_DATABASE,
  350. 'password': CACHING_REDIS_PASSWORD,
  351. }
  352. else:
  353. CACHEOPS_REDIS = {
  354. 'host': CACHING_REDIS_HOST,
  355. 'port': CACHING_REDIS_PORT,
  356. 'db': CACHING_REDIS_DATABASE,
  357. 'password': CACHING_REDIS_PASSWORD,
  358. 'ssl': CACHING_REDIS_SSL,
  359. 'ssl_cert_reqs': None if CACHING_REDIS_SKIP_TLS_VERIFY else 'required',
  360. }
  361. if not CACHE_TIMEOUT:
  362. CACHEOPS_ENABLED = False
  363. else:
  364. CACHEOPS_ENABLED = True
  365. CACHEOPS_DEFAULTS = {
  366. 'timeout': CACHE_TIMEOUT
  367. }
  368. CACHEOPS = {
  369. 'auth.user': {'ops': 'get', 'timeout': 60 * 15},
  370. 'auth.*': {'ops': ('fetch', 'get')},
  371. 'auth.permission': {'ops': 'all'},
  372. 'circuits.*': {'ops': 'all'},
  373. 'dcim.inventoryitem': None, # MPTT models are exempt due to raw SQL
  374. 'dcim.region': None, # MPTT models are exempt due to raw SQL
  375. 'dcim.location': None, # MPTT models are exempt due to raw SQL
  376. 'dcim.*': {'ops': 'all'},
  377. 'ipam.*': {'ops': 'all'},
  378. 'extras.*': {'ops': 'all'},
  379. 'users.*': {'ops': 'all'},
  380. 'tenancy.tenantgroup': None, # MPTT models are exempt due to raw SQL
  381. 'tenancy.*': {'ops': 'all'},
  382. 'virtualization.*': {'ops': 'all'},
  383. }
  384. CACHEOPS_DEGRADE_ON_FAILURE = True
  385. #
  386. # Django Prometheus
  387. #
  388. PROMETHEUS_EXPORT_MIGRATIONS = False
  389. #
  390. # Django filters
  391. #
  392. FILTERS_NULL_CHOICE_LABEL = 'None'
  393. FILTERS_NULL_CHOICE_VALUE = 'null'
  394. #
  395. # Django REST framework (API)
  396. #
  397. REST_FRAMEWORK_VERSION = VERSION.rsplit('.', 1)[0] # Use major.minor as API version
  398. REST_FRAMEWORK = {
  399. 'ALLOWED_VERSIONS': [REST_FRAMEWORK_VERSION],
  400. 'COERCE_DECIMAL_TO_STRING': False,
  401. 'DEFAULT_AUTHENTICATION_CLASSES': (
  402. 'rest_framework.authentication.SessionAuthentication',
  403. 'netbox.api.authentication.TokenAuthentication',
  404. ),
  405. 'DEFAULT_FILTER_BACKENDS': (
  406. 'django_filters.rest_framework.DjangoFilterBackend',
  407. ),
  408. 'DEFAULT_METADATA_CLASS': 'netbox.api.metadata.BulkOperationMetadata',
  409. 'DEFAULT_PAGINATION_CLASS': 'netbox.api.pagination.OptionalLimitOffsetPagination',
  410. 'DEFAULT_PERMISSION_CLASSES': (
  411. 'netbox.api.authentication.TokenPermissions',
  412. ),
  413. 'DEFAULT_RENDERER_CLASSES': (
  414. 'rest_framework.renderers.JSONRenderer',
  415. 'netbox.api.renderers.FormlessBrowsableAPIRenderer',
  416. ),
  417. 'DEFAULT_VERSION': REST_FRAMEWORK_VERSION,
  418. 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
  419. 'PAGE_SIZE': PAGINATE_COUNT,
  420. 'SCHEMA_COERCE_METHOD_NAMES': {
  421. # Default mappings
  422. 'retrieve': 'read',
  423. 'destroy': 'delete',
  424. # Custom operations
  425. 'bulk_destroy': 'bulk_delete',
  426. },
  427. 'VIEW_NAME_FUNCTION': 'utilities.api.get_view_name',
  428. }
  429. #
  430. # drf_yasg (OpenAPI/Swagger)
  431. #
  432. SWAGGER_SETTINGS = {
  433. 'DEFAULT_AUTO_SCHEMA_CLASS': 'utilities.custom_inspectors.NetBoxSwaggerAutoSchema',
  434. 'DEFAULT_FIELD_INSPECTORS': [
  435. 'utilities.custom_inspectors.CustomFieldsDataFieldInspector',
  436. 'utilities.custom_inspectors.JSONFieldInspector',
  437. 'utilities.custom_inspectors.NullableBooleanFieldInspector',
  438. 'utilities.custom_inspectors.ChoiceFieldInspector',
  439. 'utilities.custom_inspectors.SerializedPKRelatedFieldInspector',
  440. 'drf_yasg.inspectors.CamelCaseJSONFilter',
  441. 'drf_yasg.inspectors.ReferencingSerializerInspector',
  442. 'drf_yasg.inspectors.RelatedFieldInspector',
  443. 'drf_yasg.inspectors.ChoiceFieldInspector',
  444. 'drf_yasg.inspectors.FileFieldInspector',
  445. 'drf_yasg.inspectors.DictFieldInspector',
  446. 'drf_yasg.inspectors.SerializerMethodFieldInspector',
  447. 'drf_yasg.inspectors.SimpleFieldInspector',
  448. 'drf_yasg.inspectors.StringDefaultFieldInspector',
  449. ],
  450. 'DEFAULT_FILTER_INSPECTORS': [
  451. 'drf_yasg.inspectors.CoreAPICompatInspector',
  452. ],
  453. 'DEFAULT_INFO': 'netbox.urls.openapi_info',
  454. 'DEFAULT_MODEL_DEPTH': 1,
  455. 'DEFAULT_PAGINATOR_INSPECTORS': [
  456. 'utilities.custom_inspectors.NullablePaginatorInspector',
  457. 'drf_yasg.inspectors.DjangoRestResponsePagination',
  458. 'drf_yasg.inspectors.CoreAPICompatInspector',
  459. ],
  460. 'SECURITY_DEFINITIONS': {
  461. 'Bearer': {
  462. 'type': 'apiKey',
  463. 'name': 'Authorization',
  464. 'in': 'header',
  465. }
  466. },
  467. 'VALIDATOR_URL': None,
  468. }
  469. #
  470. # Django RQ (Webhooks backend)
  471. #
  472. if TASKS_REDIS_USING_SENTINEL:
  473. RQ_PARAMS = {
  474. 'SENTINELS': TASKS_REDIS_SENTINELS,
  475. 'MASTER_NAME': TASKS_REDIS_SENTINEL_SERVICE,
  476. 'DB': TASKS_REDIS_DATABASE,
  477. 'PASSWORD': TASKS_REDIS_PASSWORD,
  478. 'SOCKET_TIMEOUT': None,
  479. 'CONNECTION_KWARGS': {
  480. 'socket_connect_timeout': TASKS_REDIS_SENTINEL_TIMEOUT
  481. },
  482. }
  483. else:
  484. RQ_PARAMS = {
  485. 'HOST': TASKS_REDIS_HOST,
  486. 'PORT': TASKS_REDIS_PORT,
  487. 'DB': TASKS_REDIS_DATABASE,
  488. 'PASSWORD': TASKS_REDIS_PASSWORD,
  489. 'SSL': TASKS_REDIS_SSL,
  490. 'SSL_CERT_REQS': None if TASKS_REDIS_SKIP_TLS_VERIFY else 'required',
  491. 'DEFAULT_TIMEOUT': RQ_DEFAULT_TIMEOUT,
  492. }
  493. RQ_QUEUES = {
  494. 'default': RQ_PARAMS, # Webhooks
  495. 'check_releases': RQ_PARAMS,
  496. }
  497. #
  498. # NetBox internal settings
  499. #
  500. # Pagination
  501. PER_PAGE_DEFAULTS = [
  502. 25, 50, 100, 250, 500, 1000
  503. ]
  504. if PAGINATE_COUNT not in PER_PAGE_DEFAULTS:
  505. PER_PAGE_DEFAULTS.append(PAGINATE_COUNT)
  506. PER_PAGE_DEFAULTS = sorted(PER_PAGE_DEFAULTS)
  507. #
  508. # Plugins
  509. #
  510. for plugin_name in PLUGINS:
  511. # Import plugin module
  512. try:
  513. plugin = importlib.import_module(plugin_name)
  514. except ModuleNotFoundError as e:
  515. if getattr(e, 'name') == plugin_name:
  516. raise ImproperlyConfigured(
  517. "Unable to import plugin {}: Module not found. Check that the plugin module has been installed within the "
  518. "correct Python environment.".format(plugin_name)
  519. )
  520. raise e
  521. # Determine plugin config and add to INSTALLED_APPS.
  522. try:
  523. plugin_config = plugin.config
  524. INSTALLED_APPS.append("{}.{}".format(plugin_config.__module__, plugin_config.__name__))
  525. except AttributeError:
  526. raise ImproperlyConfigured(
  527. "Plugin {} does not provide a 'config' variable. This should be defined in the plugin's __init__.py file "
  528. "and point to the PluginConfig subclass.".format(plugin_name)
  529. )
  530. # Validate user-provided configuration settings and assign defaults
  531. if plugin_name not in PLUGINS_CONFIG:
  532. PLUGINS_CONFIG[plugin_name] = {}
  533. plugin_config.validate(PLUGINS_CONFIG[plugin_name], VERSION)
  534. # Add middleware
  535. plugin_middleware = plugin_config.middleware
  536. if plugin_middleware and type(plugin_middleware) in (list, tuple):
  537. MIDDLEWARE.extend(plugin_middleware)
  538. # Apply cacheops config
  539. if type(plugin_config.caching_config) is not dict:
  540. raise ImproperlyConfigured(
  541. "Plugin {} caching_config must be a dictionary.".format(plugin_name)
  542. )
  543. CACHEOPS.update({
  544. "{}.{}".format(plugin_name, key): value for key, value in plugin_config.caching_config.items()
  545. })