diff --git a/netbox/extras/filtersets.py b/netbox/extras/filtersets.py index 5253ae7b0..acdca30cc 100644 --- a/netbox/extras/filtersets.py +++ b/netbox/extras/filtersets.py @@ -16,6 +16,7 @@ from .models import * __all__ = ( 'ConfigContextFilterSet', + 'ConfigRevisionFilterSet', 'ConfigTemplateFilterSet', 'ContentTypeFilterSet', 'CustomFieldFilterSet', @@ -557,3 +558,27 @@ class ContentTypeFilterSet(django_filters.FilterSet): 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) + ) diff --git a/netbox/extras/forms/__init__.py b/netbox/extras/forms/__init__.py index 0825c9ca7..e203bee46 100644 --- a/netbox/extras/forms/__init__.py +++ b/netbox/extras/forms/__init__.py @@ -4,5 +4,4 @@ from .bulk_edit import * from .bulk_import import * from .misc import * from .mixins import * -from .config import * from .scripts import * diff --git a/netbox/extras/forms/bulk_edit.py b/netbox/extras/forms/bulk_edit.py index 7c838be20..080a1b108 100644 --- a/netbox/extras/forms/bulk_edit.py +++ b/netbox/extras/forms/bulk_edit.py @@ -9,6 +9,7 @@ from utilities.forms.widgets import BulkEditNullBooleanSelect __all__ = ( 'ConfigContextBulkEditForm', + 'ConfigRevisionBulkEditForm', 'ConfigTemplateBulkEditForm', 'CustomFieldBulkEditForm', 'CustomLinkBulkEditForm', @@ -20,6 +21,38 @@ __all__ = ( ) +class ConfigRevisionBulkEditForm(BulkEditForm): + pk = forms.ModelMultipleChoiceField( + queryset=CustomField.objects.all(), + widget=forms.MultipleHiddenInput + ) + group_name = forms.CharField( + required=False + ) + description = forms.CharField( + required=False + ) + required = forms.NullBooleanField( + required=False, + widget=BulkEditNullBooleanSelect() + ) + weight = forms.IntegerField( + required=False + ) + ui_visibility = forms.ChoiceField( + label=_("UI visibility"), + choices=add_blank_choice(CustomFieldVisibilityChoices), + required=False, + initial='' + ) + is_cloneable = forms.NullBooleanField( + required=False, + widget=BulkEditNullBooleanSelect() + ) + + nullable_fields = ('group_name', 'description',) + + class CustomFieldBulkEditForm(BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=CustomField.objects.all(), diff --git a/netbox/extras/forms/bulk_import.py b/netbox/extras/forms/bulk_import.py index 818b8a52f..de30bc469 100644 --- a/netbox/extras/forms/bulk_import.py +++ b/netbox/extras/forms/bulk_import.py @@ -12,6 +12,7 @@ from utilities.forms import CSVModelForm from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVMultipleContentTypeField, SlugField __all__ = ( + 'ConfigRevisionImportForm', 'ConfigTemplateImportForm', 'CustomFieldImportForm', 'CustomLinkImportForm', @@ -23,6 +24,41 @@ __all__ = ( ) +class ConfigRevisionImportForm(CSVModelForm): + content_types = CSVMultipleContentTypeField( + queryset=ContentType.objects.all(), + limit_choices_to=FeatureQuery('custom_fields'), + help_text=_("One or more assigned object types") + ) + type = CSVChoiceField( + choices=CustomFieldTypeChoices, + help_text=_('Field data type (e.g. text, integer, etc.)') + ) + object_type = CSVContentTypeField( + queryset=ContentType.objects.all(), + limit_choices_to=FeatureQuery('custom_fields'), + required=False, + help_text=_("Object type (for object or multi-object fields)") + ) + choices = SimpleArrayField( + base_field=forms.CharField(), + required=False, + help_text=_('Comma-separated list of field choices') + ) + ui_visibility = CSVChoiceField( + choices=CustomFieldVisibilityChoices, + help_text=_('How the custom field is displayed in the user interface') + ) + + class Meta: + model = CustomField + fields = ( + 'name', 'label', 'group_name', 'type', 'content_types', 'object_type', 'required', 'description', + 'search_weight', 'filter_logic', 'default', 'choices', 'weight', 'validation_minimum', 'validation_maximum', + 'validation_regex', 'ui_visibility', 'is_cloneable', + ) + + class CustomFieldImportForm(CSVModelForm): content_types = CSVMultipleContentTypeField( queryset=ContentType.objects.all(), diff --git a/netbox/extras/forms/filtersets.py b/netbox/extras/forms/filtersets.py index fae15d041..1ff57eae9 100644 --- a/netbox/extras/forms/filtersets.py +++ b/netbox/extras/forms/filtersets.py @@ -18,6 +18,7 @@ from .mixins import SavedFiltersMixin __all__ = ( 'ConfigContextFilterForm', + 'ConfigRevisionFilterForm', 'ConfigTemplateFilterForm', 'CustomFieldFilterForm', 'CustomLinkFilterForm', @@ -444,3 +445,9 @@ class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm): api_url='/api/extras/content-types/', ) ) + + +class ConfigRevisionFilterForm(SavedFiltersMixin, FilterForm): + fieldsets = ( + (None, ('q', 'filter_id')), + ) diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index aaa426ac3..763a6d42e 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -1,6 +1,7 @@ import json from django import forms +from django.conf import settings from django.db.models import Q from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ @@ -20,6 +21,7 @@ from utilities.forms.fields import ( ) from virtualization.models import Cluster, ClusterGroup, ClusterType + __all__ = ( 'ConfigContextForm', 'ConfigRevisionForm', @@ -400,11 +402,13 @@ class FormMetaclass(forms.models.ModelFormMetaclass): return super().__new__(mcs, name, bases, attrs) -class ConfigRevisionForm(forms.BaseModelForm, metaclass=FormMetaclass): +class ConfigRevisionForm(BootstrapMixin, forms.ModelForm, metaclass=FormMetaclass): """ Form for creating a new ConfigRevision. """ class Meta: + model = ConfigRevision + fields = ['comment', ] widgets = { 'comment': forms.Textarea(), } diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 9433ab6b0..9df59bd9f 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -619,6 +619,9 @@ class ConfigRevision(models.Model): return self.data[item] return super().__getattribute__(item) + def get_absolute_url(self): + return reverse('extras:configrevision', args=[self.pk]) + def activate(self): """ Cache the configuration data. diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index 9e4924532..f5de07f33 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -9,6 +9,7 @@ from .template_code import * __all__ = ( 'ConfigContextTable', + 'ConfigRevisionTable', 'ConfigTemplateTable', 'CustomFieldTable', 'CustomLinkTable', @@ -31,6 +32,20 @@ IMAGEATTACHMENT_IMAGE = ''' ''' +class ConfigRevisionTable(NetBoxTable): + is_active = columns.BooleanColumn() + actions = columns.ActionsColumn( + actions=('edit', 'delete'), + ) + + class Meta(NetBoxTable.Meta): + model = ConfigRevision + fields = ( + 'pk', 'id', 'is_active', 'created', 'comment', + ) + default_columns = ('pk', 'id', 'is_active', 'created', 'comment') + + class CustomFieldTable(NetBoxTable): name = tables.Column( linkify=True diff --git a/netbox/extras/urls.py b/netbox/extras/urls.py index 0b4ac1e3c..440aec0af 100644 --- a/netbox/extras/urls.py +++ b/netbox/extras/urls.py @@ -117,7 +117,10 @@ urlpatterns = [ path('render/markdown/', views.RenderMarkdownView.as_view(), name="render_markdown"), # Config Revision - path('config-revision/', views.ConfigRevisionView.as_view(), name='configrevision'), + path('config-revision/', views.ConfigRevisionListView.as_view(), name='configrevision'), path('config-revision/add/', views.ConfigRevisionEditView.as_view(), name='configrevision_add'), - path('config-revision//edit/', views.ConfigRevisionEditView.as_view(), name='configrevision_edit'), + # path('custom-revision/import/', views.ConfigRevisionBulkImportView.as_view(), name='configrevision_import'), + # path('config-revision/edit/', views.ConfigRevisionBulkEditView.as_view(), name='configrevision_bulk_edit'), + path('config-revision/delete/', views.ConfigRevisionBulkDeleteView.as_view(), name='configrevision_bulk_delete'), + path('config-revision//', include(get_model_urls('extras', 'configrevision'))), ] diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 842936766..001193cfb 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -1195,21 +1195,43 @@ class RenderMarkdownView(View): # Config Revision # + +class ConfigRevisionListView(generic.ObjectListView): + queryset = ConfigRevision.objects.all() + filterset = filtersets.ConfigRevisionFilterSet + filterset_form = forms.ConfigRevisionFilterForm + table = tables.ConfigRevisionTable + + @register_model_view(ConfigRevision) class ConfigRevisionView(generic.ObjectView): queryset = ConfigRevision.objects.all() - def get(self, request, **kwargs): - instance = ConfigRevision.objects.last() - - return render(request, self.get_template_name(), { - 'object': instance, - 'tab': self.tab, - **self.get_extra_context(request, instance), - }) - @register_model_view(ConfigRevision, 'edit') class ConfigRevisionEditView(generic.ObjectEditView): queryset = ConfigRevision.objects.all() form = forms.ConfigRevisionForm + + +@register_model_view(ConfigRevision, 'delete') +class ConfigRevisionDeleteView(generic.ObjectDeleteView): + queryset = ConfigRevision.objects.all() + + +class ConfigRevisionBulkImportView(generic.BulkImportView): + queryset = ConfigRevision.objects.all() + model_form = forms.ConfigRevisionImportForm + + +class ConfigRevisionBulkEditView(generic.BulkEditView): + queryset = ConfigRevision.objects.all() + filterset = filtersets.ConfigRevisionFilterSet + table = tables.ConfigRevisionTable + form = forms.ConfigRevisionBulkEditForm + + +class ConfigRevisionBulkDeleteView(generic.BulkDeleteView): + queryset = ConfigRevision.objects.all() + filterset = filtersets.ConfigRevisionFilterSet + table = tables.ConfigRevisionTable