`. Add '*' to this list to exempt all models.
EXEMPT_VIEW_PERMISSIONS = [
@@ -175,17 +158,6 @@ LOGIN_REQUIRED = False
# re-authenticate. (Default: 1209600 [14 days])
LOGIN_TIMEOUT = None
-# Setting this to True will display a "maintenance mode" banner at the top of every page.
-MAINTENANCE_MODE = False
-
-# The URL to use when mapping physical addresses or GPS coordinates
-MAPS_URL = 'https://maps.google.com/?q='
-
-# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
-# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
-# all objects by specifying "?limit=0".
-MAX_PAGE_SIZE = 1000
-
# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
# the default value of this setting is derived from the installed location.
# MEDIA_ROOT = '/opt/netbox/netbox/media'
@@ -203,20 +175,6 @@ MAX_PAGE_SIZE = 1000
# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
METRICS_ENABLED = False
-# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
-NAPALM_USERNAME = ''
-NAPALM_PASSWORD = ''
-
-# NAPALM timeout (in seconds). (Default: 30)
-NAPALM_TIMEOUT = 30
-
-# NAPALM optional arguments (see https://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
-# be provided as a dictionary.
-NAPALM_ARGS = {}
-
-# Determine how many objects to display per page within a list. (Default: 50)
-PAGINATE_COUNT = 50
-
# Enable installed plugins. Add the name of each plugin to the list.
PLUGINS = []
@@ -229,14 +187,6 @@ PLUGINS = []
# }
# }
-# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
-# prefer IPv4 instead.
-PREFER_IPV4 = False
-
-# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
-RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = 22
-RACK_ELEVATION_DEFAULT_UNIT_WIDTH = 220
-
# Remote authentication support
REMOTE_AUTH_ENABLED = False
REMOTE_AUTH_BACKEND = 'netbox.authentication.RemoteUserBackend'
diff --git a/netbox/netbox/context_processors.py b/netbox/netbox/context_processors.py
index d6dd67d99..74178ceb4 100644
--- a/netbox/netbox/context_processors.py
+++ b/netbox/netbox/context_processors.py
@@ -1,6 +1,7 @@
from django.conf import settings as django_settings
from extras.registry import registry
+from netbox.config import get_config
def settings_and_registry(request):
@@ -9,6 +10,7 @@ def settings_and_registry(request):
"""
return {
'settings': django_settings,
+ 'config': get_config(),
'registry': registry,
'preferences': request.user.config if request.user.is_authenticated else {},
}
diff --git a/netbox/netbox/middleware.py b/netbox/netbox/middleware.py
index a8f989a2a..8d03c6aee 100644
--- a/netbox/netbox/middleware.py
+++ b/netbox/netbox/middleware.py
@@ -11,11 +11,12 @@ from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from extras.context_managers import change_logging
+from netbox.config import clear_config
from netbox.views import server_error
from utilities.api import is_api_request, rest_api_server_error
-class LoginRequiredMiddleware(object):
+class LoginRequiredMiddleware:
"""
If LOGIN_REQUIRED is True, redirect all non-authenticated users to the login page.
"""
@@ -114,7 +115,7 @@ class RemoteUserMiddleware(RemoteUserMiddleware_):
return groups
-class ObjectChangeMiddleware(object):
+class ObjectChangeMiddleware:
"""
This middleware performs three functions in response to an object being created, updated, or deleted:
@@ -144,7 +145,7 @@ class ObjectChangeMiddleware(object):
return response
-class APIVersionMiddleware(object):
+class APIVersionMiddleware:
"""
If the request is for an API endpoint, include the API version as a response header.
"""
@@ -159,7 +160,20 @@ class APIVersionMiddleware(object):
return response
-class ExceptionHandlingMiddleware(object):
+class DynamicConfigMiddleware:
+ """
+ Store the cached NetBox configuration in thread-local storage for the duration of the request.
+ """
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+ clear_config()
+ return response
+
+
+class ExceptionHandlingMiddleware:
"""
Intercept certain exceptions which are likely indicative of installation issues and provide helpful instructions
to the user.
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
index 279b8c453..45475ef9a 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -11,6 +11,8 @@ from django.contrib.messages import constants as messages
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.validators import URLValidator
+from netbox.config import PARAMS
+
#
# Environment setup
@@ -68,14 +70,8 @@ DATABASE = getattr(configuration, 'DATABASE')
REDIS = getattr(configuration, 'REDIS')
SECRET_KEY = getattr(configuration, 'SECRET_KEY')
-# Set optional parameters
+# Set static config parameters
ADMINS = getattr(configuration, 'ADMINS', [])
-ALLOWED_URL_SCHEMES = getattr(configuration, 'ALLOWED_URL_SCHEMES', (
- 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
-))
-BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', '')
-BANNER_LOGIN = getattr(configuration, 'BANNER_LOGIN', '')
-BANNER_TOP = getattr(configuration, 'BANNER_TOP', '')
BASE_PATH = getattr(configuration, 'BASE_PATH', '')
if BASE_PATH:
BASE_PATH = BASE_PATH.strip('/') + '/' # Enforce trailing slash only
@@ -90,30 +86,19 @@ DEBUG = getattr(configuration, 'DEBUG', False)
DEVELOPER = getattr(configuration, 'DEVELOPER', False)
DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs'))
EMAIL = getattr(configuration, 'EMAIL', {})
-ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False)
EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', [])
GRAPHQL_ENABLED = getattr(configuration, 'GRAPHQL_ENABLED', True)
HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', None)
INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
LOGGING = getattr(configuration, 'LOGGING', {})
+LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False)
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None)
-MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False)
-MAPS_URL = getattr(configuration, 'MAPS_URL', 'https://maps.google.com/?q=')
-MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000)
MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/')
METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False)
-NAPALM_ARGS = getattr(configuration, 'NAPALM_ARGS', {})
-NAPALM_PASSWORD = getattr(configuration, 'NAPALM_PASSWORD', '')
-NAPALM_TIMEOUT = getattr(configuration, 'NAPALM_TIMEOUT', 30)
-NAPALM_USERNAME = getattr(configuration, 'NAPALM_USERNAME', '')
-PAGINATE_COUNT = getattr(configuration, 'PAGINATE_COUNT', 50)
-LOGIN_PERSISTENCE = getattr(configuration, 'LOGIN_PERSISTENCE', False)
PLUGINS = getattr(configuration, 'PLUGINS', [])
PLUGINS_CONFIG = getattr(configuration, 'PLUGINS_CONFIG', {})
-PREFER_IPV4 = getattr(configuration, 'PREFER_IPV4', False)
-RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = getattr(configuration, 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 22)
-RACK_ELEVATION_DEFAULT_UNIT_WIDTH = getattr(configuration, 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH', 220)
+RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None)
REMOTE_AUTH_AUTO_CREATE_USER = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_USER', False)
REMOTE_AUTH_BACKEND = getattr(configuration, 'REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
REMOTE_AUTH_DEFAULT_GROUPS = getattr(configuration, 'REMOTE_AUTH_DEFAULT_GROUPS', [])
@@ -127,7 +112,6 @@ REMOTE_AUTH_SUPERUSERS = getattr(configuration, 'REMOTE_AUTH_SUPERUSERS', [])
REMOTE_AUTH_STAFF_GROUPS = getattr(configuration, 'REMOTE_AUTH_STAFF_GROUPS', [])
REMOTE_AUTH_STAFF_USERS = getattr(configuration, 'REMOTE_AUTH_STAFF_USERS', [])
REMOTE_AUTH_GROUP_SEPARATOR = getattr(configuration, 'REMOTE_AUTH_GROUP_SEPARATOR', '|')
-RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None)
REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 'reports')).rstrip('/')
RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300)
SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/')
@@ -141,6 +125,11 @@ STORAGE_CONFIG = getattr(configuration, 'STORAGE_CONFIG', {})
TIME_FORMAT = getattr(configuration, 'TIME_FORMAT', 'g:i a')
TIME_ZONE = getattr(configuration, 'TIME_ZONE', 'UTC')
+# Check for hard-coded dynamic config parameters
+for param in PARAMS:
+ if hasattr(configuration, param.name):
+ globals()[param.name] = getattr(configuration, param.name)
+
# Validate update repo URL and timeout
if RELEASE_CHECK_URL:
validator = URLValidator(
@@ -346,6 +335,7 @@ MIDDLEWARE = [
'netbox.middleware.ExceptionHandlingMiddleware',
'netbox.middleware.RemoteUserMiddleware',
'netbox.middleware.LoginRequiredMiddleware',
+ 'netbox.middleware.DynamicConfigMiddleware',
'netbox.middleware.APIVersionMiddleware',
'netbox.middleware.ObjectChangeMiddleware',
'django_prometheus.middleware.PrometheusAfterMiddleware',
@@ -466,7 +456,7 @@ REST_FRAMEWORK = {
),
'DEFAULT_VERSION': REST_FRAMEWORK_VERSION,
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning',
- 'PAGE_SIZE': PAGINATE_COUNT,
+ # 'PAGE_SIZE': PAGINATE_COUNT,
'SCHEMA_COERCE_METHOD_NAMES': {
# Default mappings
'retrieve': 'read',
@@ -565,23 +555,6 @@ RQ_QUEUES = {
}
-#
-# NetBox internal settings
-#
-
-# Pagination
-if MAX_PAGE_SIZE and PAGINATE_COUNT > MAX_PAGE_SIZE:
- raise ImproperlyConfigured(
- f"PAGINATE_COUNT ({PAGINATE_COUNT}) must be less than or equal to MAX_PAGE_SIZE ({MAX_PAGE_SIZE}), if set."
- )
-PER_PAGE_DEFAULTS = [
- 25, 50, 100, 250, 500, 1000
-]
-if PAGINATE_COUNT not in PER_PAGE_DEFAULTS:
- PER_PAGE_DEFAULTS.append(PAGINATE_COUNT)
- PER_PAGE_DEFAULTS = sorted(PER_PAGE_DEFAULTS)
-
-
#
# Plugins
#
diff --git a/netbox/netbox/tests/test_config.py b/netbox/netbox/tests/test_config.py
new file mode 100644
index 000000000..d3a0328b3
--- /dev/null
+++ b/netbox/netbox/tests/test_config.py
@@ -0,0 +1,53 @@
+from django.conf import settings
+from django.core.cache import cache
+from django.test import override_settings, TestCase
+
+from extras.models import ConfigRevision
+from netbox.config import clear_config, get_config
+
+
+# Prefix cache keys to avoid interfering with the local environment
+CACHES = settings.CACHES
+CACHES['default'].update({'KEY_PREFIX': 'TEST-'})
+
+
+class ConfigTestCase(TestCase):
+
+ @override_settings(CACHES=CACHES)
+ def test_config_init_empty(self):
+ cache.clear()
+
+ config = get_config()
+ self.assertEqual(config.config, {})
+ self.assertEqual(config.version, None)
+
+ clear_config()
+
+ @override_settings(CACHES=CACHES)
+ def test_config_init_from_db(self):
+ CONFIG_DATA = {'BANNER_TOP': 'A'}
+ cache.clear()
+
+ # Create a config but don't load it into the cache
+ configrevision = ConfigRevision.objects.create(data=CONFIG_DATA)
+
+ config = get_config()
+ self.assertEqual(config.config, CONFIG_DATA)
+ self.assertEqual(config.version, configrevision.pk)
+
+ clear_config()
+
+ @override_settings(CACHES=CACHES)
+ def test_config_init_from_cache(self):
+ CONFIG_DATA = {'BANNER_TOP': 'B'}
+ cache.clear()
+
+ # Create a config and load it into the cache
+ configrevision = ConfigRevision.objects.create(data=CONFIG_DATA)
+ configrevision.activate()
+
+ config = get_config()
+ self.assertEqual(config.config, CONFIG_DATA)
+ self.assertEqual(config.version, configrevision.pk)
+
+ clear_config()
diff --git a/netbox/templates/admin/extras/configrevision/restore.html b/netbox/templates/admin/extras/configrevision/restore.html
new file mode 100644
index 000000000..4a0eb81a6
--- /dev/null
+++ b/netbox/templates/admin/extras/configrevision/restore.html
@@ -0,0 +1,37 @@
+{% extends "admin/base_site.html" %}
+{% load static %}
+
+{% block content %}
+ Restore configuration #{{ object.pk }} from {{ object.created }}?
+
+
+
+
+ Parameter |
+ Current Value |
+ New Value |
+ |
+
+
+
+ {% for param, current, new in params %}
+
+ {{ param }} |
+ {{ current }} |
+ {{ new }} |
+ {% if current != new %} {% endif %} |
+
+ {% endfor %}
+
+
+
+
+{% endblock content %}
+
+
diff --git a/netbox/templates/base/layout.html b/netbox/templates/base/layout.html
index 9575d4dcb..2770a6dc6 100644
--- a/netbox/templates/base/layout.html
+++ b/netbox/templates/base/layout.html
@@ -58,13 +58,13 @@
- {% if settings.BANNER_TOP %}
+ {% if config.BANNER_TOP %}
- {{ settings.BANNER_TOP|safe }}
+ {{ config.BANNER_TOP|safe }}
{% endif %}
- {% if settings.MAINTENANCE_MODE %}
+ {% if config.MAINTENANCE_MODE %}
Maintenance Mode
NetBox is currently in maintenance mode. Functionality may be limited.
@@ -98,9 +98,9 @@
{% endblock %}
- {% if settings.BANNER_BOTTOM %}
+ {% if config.BANNER_BOTTOM %}
- {{ settings.BANNER_BOTTOM|safe }}
+ {{ config.BANNER_BOTTOM|safe }}
{% endif %}
diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html
index a17c505a9..7429aa4f5 100644
--- a/netbox/templates/dcim/site.html
+++ b/netbox/templates/dcim/site.html
@@ -100,7 +100,7 @@
{% if object.physical_address %}
@@ -119,7 +119,7 @@
|
{% if object.latitude and object.longitude %}
diff --git a/netbox/templates/inc/paginator.html b/netbox/templates/inc/paginator.html
index c55203be3..8242ffcde 100644
--- a/netbox/templates/inc/paginator.html
+++ b/netbox/templates/inc/paginator.html
@@ -36,7 +36,7 @@
{% endfor %}
|