From 2a00519b93afeffe638f0a27b1e204adbf66d59f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 8 Nov 2021 15:07:58 -0500 Subject: [PATCH 1/4] Move CHANGELOG_RETENTION to dyanmic configuration --- docs/configuration/dynamic-settings.md | 12 ++++++++++++ docs/configuration/optional-settings.md | 12 ------------ netbox/extras/admin.py | 2 +- netbox/extras/management/commands/housekeeping.py | 10 ++++++---- netbox/netbox/config/parameters.py | 7 +++++++ netbox/netbox/configuration.example.py | 3 --- netbox/netbox/settings.py | 1 - 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/docs/configuration/dynamic-settings.md b/docs/configuration/dynamic-settings.md index 765e1d0f1..bb8fb33d7 100644 --- a/docs/configuration/dynamic-settings.md +++ b/docs/configuration/dynamic-settings.md @@ -31,6 +31,18 @@ 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. + +--- + ## ENFORCE_GLOBAL_UNIQUE Default: False diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index d3b82e995..49a4a776b 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 diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 752c8c83d..a905367c5 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -31,7 +31,7 @@ class ConfigRevisionAdmin(admin.ModelAdmin): 'fields': ('NAPALM_USERNAME', 'NAPALM_PASSWORD', 'NAPALM_TIMEOUT', 'NAPALM_ARGS'), }), ('Miscellaneous', { - 'fields': ('MAINTENANCE_MODE', 'MAPS_URL'), + 'fields': ('MAINTENANCE_MODE', '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/netbox/config/parameters.py b/netbox/netbox/config/parameters.py index 8bf1d6dc5..7b9f6a1f7 100644 --- a/netbox/netbox/config/parameters.py +++ b/netbox/netbox/config/parameters.py @@ -130,6 +130,13 @@ PARAMS = ( description="Enable maintenance mode", 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..947ed6d53 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 diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index f4f6c2f32..c91a5e75b 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -80,7 +80,6 @@ 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', []) From f8e44c09ebd54704594eb1c544a98a7e3e991fa9 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 8 Nov 2021 15:22:29 -0500 Subject: [PATCH 2/4] Move CUSTOM_VALIDATORS to dynamic configuration --- docs/configuration/dynamic-settings.md | 23 +++++++++++++++++++++++ docs/configuration/optional-settings.md | 16 ---------------- netbox/extras/admin.py | 3 +++ netbox/extras/signals.py | 5 +++-- netbox/netbox/config/parameters.py | 9 +++++++++ netbox/netbox/configuration.example.py | 14 -------------- netbox/netbox/settings.py | 1 - 7 files changed, 38 insertions(+), 33 deletions(-) diff --git a/docs/configuration/dynamic-settings.md b/docs/configuration/dynamic-settings.md index bb8fb33d7..a98143045 100644 --- a/docs/configuration/dynamic-settings.md +++ b/docs/configuration/dynamic-settings.md @@ -43,6 +43,29 @@ changes in the database indefinitely. --- +## 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 diff --git a/docs/configuration/optional-settings.md b/docs/configuration/optional-settings.md index 49a4a776b..e49968130 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -49,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 diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index a905367c5..73ffb40fc 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -27,6 +27,9 @@ 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'), }), 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 7b9f6a1f7..1be664b28 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', diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index 947ed6d53..48885f844 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -87,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. diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index c91a5e75b..529da77dc 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -83,7 +83,6 @@ if BASE_PATH: 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) From d2391b9c63ecdac04121e18f2f072f7c62193d2a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 8 Nov 2021 15:31:09 -0500 Subject: [PATCH 3/4] Move GRAPHQL_ENABLED to dynamic configuration --- docs/configuration/dynamic-settings.md | 8 ++++++++ docs/configuration/optional-settings.md | 8 -------- netbox/extras/admin.py | 2 +- netbox/netbox/config/parameters.py | 7 +++++++ netbox/netbox/configuration.example.py | 3 --- netbox/netbox/graphql/views.py | 4 +++- netbox/netbox/settings.py | 1 - netbox/templates/base/layout.html | 2 +- 8 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/configuration/dynamic-settings.md b/docs/configuration/dynamic-settings.md index a98143045..a222272c2 100644 --- a/docs/configuration/dynamic-settings.md +++ b/docs/configuration/dynamic-settings.md @@ -74,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 e49968130..d8d79b6ec 100644 --- a/docs/configuration/optional-settings.md +++ b/docs/configuration/optional-settings.md @@ -140,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/netbox/extras/admin.py b/netbox/extras/admin.py index 73ffb40fc..b6ee01db9 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -34,7 +34,7 @@ class ConfigRevisionAdmin(admin.ModelAdmin): 'fields': ('NAPALM_USERNAME', 'NAPALM_PASSWORD', 'NAPALM_TIMEOUT', 'NAPALM_ARGS'), }), ('Miscellaneous', { - 'fields': ('MAINTENANCE_MODE', 'CHANGELOG_RETENTION', 'MAPS_URL'), + 'fields': ('MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'MAPS_URL'), }), ('Config Revision', { 'fields': ('comment',), diff --git a/netbox/netbox/config/parameters.py b/netbox/netbox/config/parameters.py index 1be664b28..b4f16bf28 100644 --- a/netbox/netbox/config/parameters.py +++ b/netbox/netbox/config/parameters.py @@ -139,6 +139,13 @@ 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', diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index 48885f844..8130acb2e 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -112,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 529da77dc..5e5718559 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -90,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 %} From f3d8f1b1fb06be80ef7afc2fc6c145f2fe83922a Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 8 Nov 2021 15:38:55 -0500 Subject: [PATCH 4/4] Changelog for #7775 --- docs/release-notes/version-3.1.md | 1 + 1 file changed, 1 insertion(+) 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