Skip to content

Commit

Permalink
12591 config params admin (#12904)
Browse files Browse the repository at this point in the history
* 12591 initial commit

* 12591 detail view

* 12591 add/edit view

* 12591 edit button

* 12591 base views and forms

* 12591 form cleanup

* 12591 form cleanup

* 12591 form cleanup

* 12591 review changes

* 12591 move check for restrictedqueryset

* 12591 restore view

* 12591 restore page styling

* 12591 remove admin

* Remove edit view for ConfigRevision instances

* Order ConfigRevisions by creation time

* Correct permission name

* Use RestrictedQuerySet for ConfigRevision

* Fix redirect URL

---------

Co-authored-by: Jeremy Stretch <jstretch@netboxlabs.com>
  • Loading branch information
arthanson and jeremystretch committed Jun 22, 2023
1 parent 48b2ab3 commit 148278a
Show file tree
Hide file tree
Showing 16 changed files with 567 additions and 252 deletions.
129 changes: 1 addition & 128 deletions netbox/extras/admin.py
Original file line number Diff line number Diff line change
@@ -1,129 +1,2 @@
from django.contrib import admin
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.html import format_html

from netbox.config import get_config, PARAMS
# TODO: Removing this import triggers an import loop due to how form mixins are currently organized
from .forms import ConfigRevisionForm
from .models import ConfigRevision


@admin.register(ConfigRevision)
class ConfigRevisionAdmin(admin.ModelAdmin):
fieldsets = [
('Rack Elevations', {
'fields': ('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH'),
}),
('Power', {
'fields': ('POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION')
}),
('IPAM', {
'fields': ('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4'),
}),
('Security', {
'fields': ('ALLOWED_URL_SCHEMES',),
}),
('Banners', {
'fields': ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM'),
'classes': ('monospace',),
}),
('Pagination', {
'fields': ('PAGINATE_COUNT', 'MAX_PAGE_SIZE'),
}),
('Validation', {
'fields': ('CUSTOM_VALIDATORS',),
'classes': ('monospace',),
}),
('User Preferences', {
'fields': ('DEFAULT_USER_PREFERENCES',),
}),
('Miscellaneous', {
'fields': ('MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'JOB_RETENTION', 'MAPS_URL'),
}),
('Config Revision', {
'fields': ('comment',),
})
]
form = ConfigRevisionForm
list_display = ('id', 'is_active', 'created', 'comment', 'restore_link')
ordering = ('-id',)
readonly_fields = ('data',)

def get_changeform_initial_data(self, request):
"""
Populate initial form data from the most recent ConfigRevision.
"""
latest_revision = ConfigRevision.objects.last()
initial = latest_revision.data if latest_revision else {}
initial.update(super().get_changeform_initial_data(request))

return initial

# Permissions

def has_add_permission(self, request):
# Only superusers may modify the configuration.
return request.user.is_superuser

def has_change_permission(self, request, obj=None):
# ConfigRevisions cannot be modified once created.
return False

def has_delete_permission(self, request, obj=None):
# Only inactive ConfigRevisions may be deleted (must be superuser).
return request.user.is_superuser and (
obj is None or not obj.is_active()
)

# List display methods

def restore_link(self, obj):
if obj.is_active():
return ''
return format_html(
'<a href="{url}" class="button">Restore</a>',
url=reverse('admin:extras_configrevision_restore', args=(obj.pk,))
)
restore_link.short_description = "Actions"

# URLs

def get_urls(self):
urls = [
path('<int:pk>/restore/', self.admin_site.admin_view(self.restore), name='extras_configrevision_restore'),
]

return urls + super().get_urls()

# Views

def restore(self, request, pk):
# Get the ConfigRevision being restored
candidate_config = get_object_or_404(ConfigRevision, pk=pk)

if request.method == 'POST':
candidate_config.activate()
self.message_user(request, f"Restored configuration revision #{pk}")

return redirect(reverse('admin:extras_configrevision_changelist'))

# Get the current ConfigRevision
config_version = get_config().version
current_config = ConfigRevision.objects.filter(pk=config_version).first()

params = []
for param in PARAMS:
params.append((
param.name,
current_config.data.get(param.name, None),
candidate_config.data.get(param.name, None)
))

context = self.admin_site.each_context(request)
context.update({
'object': candidate_config,
'params': params,
})

return TemplateResponse(request, 'admin/extras/configrevision/restore.html', context)
25 changes: 25 additions & 0 deletions netbox/extras/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

__all__ = (
'ConfigContextFilterSet',
'ConfigRevisionFilterSet',
'ConfigTemplateFilterSet',
'ContentTypeFilterSet',
'CustomFieldFilterSet',
Expand Down Expand Up @@ -557,3 +558,27 @@ def search(self, queryset, name, value):
Q(app_label__icontains=value) |
Q(model__icontains=value)
)


#
# ConfigRevisions
#

class ConfigRevisionFilterSet(BaseFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
)

class Meta:
model = ConfigRevision
fields = [
'id',
]

def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(comment__icontains=value)
)
1 change: 0 additions & 1 deletion netbox/extras/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
from .bulk_import import *
from .misc import *
from .mixins import *
from .config import *
from .scripts import *
82 changes: 0 additions & 82 deletions netbox/extras/forms/config.py

This file was deleted.

7 changes: 7 additions & 0 deletions netbox/extras/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

__all__ = (
'ConfigContextFilterForm',
'ConfigRevisionFilterForm',
'ConfigTemplateFilterForm',
'CustomFieldFilterForm',
'CustomLinkFilterForm',
Expand Down Expand Up @@ -444,3 +445,9 @@ class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
api_url='/api/extras/content-types/',
)
)


class ConfigRevisionFilterForm(SavedFiltersMixin, FilterForm):
fieldsets = (
(None, ('q', 'filter_id')),
)
Loading

0 comments on commit 148278a

Please sign in to comment.