diff --git a/docs/configuration/dynamic-settings.md b/docs/configuration/dynamic-settings.md index 765e1d0f1..a222272c2 100644 --- a/docs/configuration/dynamic-settings.md +++ b/docs/configuration/dynamic-settings.md @@ -31,6 +31,41 @@ This defines custom content to be displayed on the login page above the login fo --- +## CHANGELOG_RETENTION + +Default: 90 + +The number of days to retain logged changes (object creations, updates, and deletions). Set this to `0` to retain +changes in the database indefinitely. + +!!! warning + If enabling indefinite changelog retention, it is recommended to periodically delete old entries. Otherwise, the database may eventually exceed capacity. + +--- + +## CUSTOM_VALIDATORS + +This is a mapping of models to [custom validators](../customization/custom-validation.md) that have been defined locally to enforce custom validation logic. An example is provided below: + +```python +CUSTOM_VALIDATORS = { + "dcim.site": [ + { + "name": { + "min_length": 5, + "max_length": 30 + } + }, + "my_plugin.validators.Validator1" + ], + "dim.device": [ + "my_plugin.validators.Validator1" + ] +} +``` + +--- + ## ENFORCE_GLOBAL_UNIQUE Default: False @@ -39,6 +74,14 @@ By default, NetBox will permit users to create duplicate prefixes and IP address --- +## GRAPHQL_ENABLED + +Default: True + +Setting this to False will disable the GraphQL API. + +--- + ## MAINTENANCE_MODE Default: False diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index d3b82e995..d8d79b6ec 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -25,18 +25,6 @@ BASE_PATH = 'netbox/' --- -## CHANGELOG_RETENTION - -Default: 90 - -The number of days to retain logged changes (object creations, updates, and deletions). Set this to `0` to retain -changes in the database indefinitely. - -!!! warning - If enabling indefinite changelog retention, it is recommended to periodically delete old entries. Otherwise, the database may eventually exceed capacity. - ---- - ## CORS_ORIGIN_ALLOW_ALL Default: False @@ -61,22 +49,6 @@ CORS_ORIGIN_WHITELIST = [ --- -## CUSTOM_VALIDATORS - -This is a mapping of models to [custom validators](../customization/custom-validation.md) that have been defined locally to enforce custom validation logic. An example is provided below: - -```python -CUSTOM_VALIDATORS = { - 'dcim.site': ( - Validator1, - Validator2, - Validator3 - ) -} -``` - ---- - ## DEBUG Default: False @@ -168,14 +140,6 @@ EXEMPT_VIEW_PERMISSIONS = ['*'] --- -## GRAPHQL_ENABLED - -Default: True - -Setting this to False will disable the GraphQL API. - ---- - ## HTTP_PROXIES Default: None diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index 746a64e62..b19e7979c 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -3,6 +3,7 @@ ### Enhancements * [#7619](https://github.com/netbox-community/netbox/issues/7619) - Permit custom validation rules to be defined as plain data or dotted path to class +* [#7775](https://github.com/netbox-community/netbox/issues/7775) - Enable dynamic config for `CHANGELOG_RETENTION`, `CUSTOM_VALIDATORS`, and `GRAPHQL_ENABLED` ### Bug Fixes diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 752c8c83d..b6ee01db9 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -27,11 +27,14 @@ class ConfigRevisionAdmin(admin.ModelAdmin): ('Pagination', { 'fields': ('PAGINATE_COUNT', 'MAX_PAGE_SIZE'), }), + ('Validation', { + 'fields': ('CUSTOM_VALIDATORS',), + }), ('NAPALM', { 'fields': ('NAPALM_USERNAME', 'NAPALM_PASSWORD', 'NAPALM_TIMEOUT', 'NAPALM_ARGS'), }), ('Miscellaneous', { - 'fields': ('MAINTENANCE_MODE', 'MAPS_URL'), + 'fields': ('MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'MAPS_URL'), }), ('Config Revision', { 'fields': ('comment',), diff --git a/netbox/extras/management/commands/housekeeping.py b/netbox/extras/management/commands/housekeeping.py index a4d617c9a..0607a16c2 100644 --- a/netbox/extras/management/commands/housekeeping.py +++ b/netbox/extras/management/commands/housekeeping.py @@ -10,12 +10,14 @@ from django.utils import timezone from packaging import version from extras.models import ObjectChange +from netbox.config import Config class Command(BaseCommand): help = "Perform nightly housekeeping tasks. (This command can be run at any time.)" def handle(self, *args, **options): + config = Config() # Clear expired authentication sessions (essentially replicating the `clearsessions` command) if options['verbosity']: @@ -37,10 +39,10 @@ class Command(BaseCommand): # Delete expired ObjectRecords if options['verbosity']: self.stdout.write("[*] Checking for expired changelog records") - if settings.CHANGELOG_RETENTION: - cutoff = timezone.now() - timedelta(days=settings.CHANGELOG_RETENTION) + if config.CHANGELOG_RETENTION: + cutoff = timezone.now() - timedelta(days=config.CHANGELOG_RETENTION) if options['verbosity'] >= 2: - self.stdout.write(f"\tRetention period: {settings.CHANGELOG_RETENTION} days") + self.stdout.write(f"\tRetention period: {config.CHANGELOG_RETENTION} days") self.stdout.write(f"\tCut-off time: {cutoff}") expired_records = ObjectChange.objects.filter(time__lt=cutoff).count() if expired_records: @@ -58,7 +60,7 @@ class Command(BaseCommand): self.stdout.write("\tNo expired records found.", self.style.SUCCESS) elif options['verbosity']: self.stdout.write( - f"\tSkipping: No retention period specified (CHANGELOG_RETENTION = {settings.CHANGELOG_RETENTION})" + f"\tSkipping: No retention period specified (CHANGELOG_RETENTION = {config.CHANGELOG_RETENTION})" ) # Check for new releases (if enabled) diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py index 99bc91236..77931f268 100644 --- a/netbox/extras/signals.py +++ b/netbox/extras/signals.py @@ -1,13 +1,13 @@ import importlib import logging -from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db.models.signals import m2m_changed, post_save, pre_delete from django.dispatch import receiver, Signal from django_prometheus.models import model_deletes, model_inserts, model_updates from extras.validators import CustomValidator +from netbox.config import get_config from netbox.signals import post_clean from .choices import ObjectChangeActionChoices from .models import ConfigRevision, CustomField, ObjectChange @@ -159,8 +159,9 @@ m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_type @receiver(post_clean) def run_custom_validators(sender, instance, **kwargs): + config = get_config() model_name = f'{sender._meta.app_label}.{sender._meta.model_name}' - validators = settings.CUSTOM_VALIDATORS.get(model_name, []) + validators = config.CUSTOM_VALIDATORS.get(model_name, []) for validator in validators: diff --git a/netbox/netbox/config/parameters.py b/netbox/netbox/config/parameters.py index 8bf1d6dc5..b4f16bf28 100644 --- a/netbox/netbox/config/parameters.py +++ b/netbox/netbox/config/parameters.py @@ -94,6 +94,15 @@ PARAMS = ( field=forms.IntegerField ), + # Validation + ConfigParam( + name='CUSTOM_VALIDATORS', + label='Custom validators', + default={}, + description="Custom validation rules (JSON)", + field=forms.JSONField + ), + # NAPALM ConfigParam( name='NAPALM_USERNAME', @@ -130,6 +139,20 @@ PARAMS = ( description="Enable maintenance mode", field=forms.BooleanField ), + ConfigParam( + name='GRAPHQL_ENABLED', + label='GraphQL enabled', + default=True, + description="Enable the GraphQL API", + field=forms.BooleanField + ), + ConfigParam( + name='CHANGELOG_RETENTION', + label='Changelog retention', + default=90, + description="Days to retain changelog history (set to zero for unlimited)", + field=forms.IntegerField + ), ConfigParam( name='MAPS_URL', label='Maps URL', diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index 189e98d11..8130acb2e 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -76,9 +76,6 @@ ADMINS = [ # BASE_PATH = 'netbox/' BASE_PATH = '' -# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) -CHANGELOG_RETENTION = 90 - # API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be # allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or # CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers @@ -90,20 +87,6 @@ CORS_ORIGIN_REGEX_WHITELIST = [ # r'^(https?://)?(\w+\.)?example\.com$', ] -# Specify any custom validators here, as a mapping of model to a list of validators classes. Validators should be -# instances of or inherit from CustomValidator. -# from extras.validators import CustomValidator -CUSTOM_VALIDATORS = { - # 'dcim.site': [ - # CustomValidator({ - # 'name': { - # 'min_length': 10, - # 'regex': r'\d{3}$', - # } - # }) - # ], -} - # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal # sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging # on a production system. @@ -129,9 +112,6 @@ EXEMPT_VIEW_PERMISSIONS = [ # 'ipam.prefix', ] -# Enable the GraphQL API -GRAPHQL_ENABLED = True - # HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks). # HTTP_PROXIES = { # 'http': 'http://10.10.1.10:3128', diff --git a/netbox/netbox/graphql/views.py b/netbox/netbox/graphql/views.py index c2c0269fa..e1573dba6 100644 --- a/netbox/netbox/graphql/views.py +++ b/netbox/netbox/graphql/views.py @@ -6,6 +6,7 @@ from graphene_django.views import GraphQLView as GraphQLView_ from rest_framework.exceptions import AuthenticationFailed from netbox.api.authentication import TokenAuthentication +from netbox.config import get_config class GraphQLView(GraphQLView_): @@ -15,9 +16,10 @@ class GraphQLView(GraphQLView_): graphiql_template = 'graphiql.html' def dispatch(self, request, *args, **kwargs): + config = get_config() # Enforce GRAPHQL_ENABLED - if not settings.GRAPHQL_ENABLED: + if not config.GRAPHQL_ENABLED: return HttpResponseNotFound("The GraphQL API is not enabled.") # Attempt to authenticate the user using a DRF token, if provided diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index f4f6c2f32..5e5718559 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -80,11 +80,9 @@ ADMINS = getattr(configuration, 'ADMINS', []) BASE_PATH = getattr(configuration, 'BASE_PATH', '') if BASE_PATH: BASE_PATH = BASE_PATH.strip('/') + '/' # Enforce trailing slash only -CHANGELOG_RETENTION = getattr(configuration, 'CHANGELOG_RETENTION', 90) CORS_ORIGIN_ALLOW_ALL = getattr(configuration, 'CORS_ORIGIN_ALLOW_ALL', False) CORS_ORIGIN_REGEX_WHITELIST = getattr(configuration, 'CORS_ORIGIN_REGEX_WHITELIST', []) CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', []) -CUSTOM_VALIDATORS = getattr(configuration, 'CUSTOM_VALIDATORS', {}) DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y') DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a') DEBUG = getattr(configuration, 'DEBUG', False) @@ -92,7 +90,6 @@ DEVELOPER = getattr(configuration, 'DEVELOPER', False) DOCS_ROOT = getattr(configuration, 'DOCS_ROOT', os.path.join(os.path.dirname(BASE_DIR), 'docs')) EMAIL = getattr(configuration, 'EMAIL', {}) 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', {}) diff --git a/netbox/templates/base/layout.html b/netbox/templates/base/layout.html index 2770a6dc6..d45dc62f6 100644 --- a/netbox/templates/base/layout.html +++ b/netbox/templates/base/layout.html @@ -127,7 +127,7 @@ {# GraphQL API #} - {% if settings.GRAPHQL_ENABLED %} + {% if config.GRAPHQL_ENABLED %}