Răsfoiți Sursa

Add bulk renaming function for VM interfaces

Jeremy Stretch 5 ani în urmă
părinte
comite
052555c3f7

+ 2 - 26
netbox/dcim/forms.py

@@ -23,12 +23,12 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
     APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
-    ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm,
+    BulkRenameForm, ColorSelect, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm,
     DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField,
     DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField,
     NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
     NumericArrayField, SelectWithPK, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
     BOOLEAN_WITH_BLANK_CHOICES,
     BOOLEAN_WITH_BLANK_CHOICES,
 )
 )
-from virtualization.models import Cluster, ClusterGroup, VirtualMachine
+from virtualization.models import Cluster, ClusterGroup
 from .choices import *
 from .choices import *
 from .constants import *
 from .constants import *
 from .models import (
 from .models import (
@@ -150,30 +150,6 @@ class LabeledComponentForm(BootstrapMixin, forms.Form):
             }, code='label_pattern_mismatch')
             }, code='label_pattern_mismatch')
 
 
 
 
-class BulkRenameForm(forms.Form):
-    """
-    An extendable form to be used for renaming device components in bulk.
-    """
-    find = forms.CharField()
-    replace = forms.CharField()
-    use_regex = forms.BooleanField(
-        required=False,
-        initial=True,
-        label='Use regular expressions'
-    )
-
-    def clean(self):
-
-        # Validate regular expression in "find" field
-        if self.cleaned_data['use_regex']:
-            try:
-                re.compile(self.cleaned_data['find'])
-            except re.error:
-                raise forms.ValidationError({
-                    'find': "Invalid regular expression"
-                })
-
-
 #
 #
 # Fields
 # Fields
 #
 #

+ 3 - 55
netbox/dcim/views.py

@@ -1,5 +1,4 @@
 from collections import OrderedDict
 from collections import OrderedDict
-import re
 
 
 from django.conf import settings
 from django.conf import settings
 from django.contrib import messages
 from django.contrib import messages
@@ -25,8 +24,9 @@ from utilities.paginator import EnhancedPaginator
 from utilities.permissions import get_permission_for_model
 from utilities.permissions import get_permission_for_model
 from utilities.utils import csv_format
 from utilities.utils import csv_format
 from utilities.views import (
 from utilities.views import (
-    BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, GetReturnURLMixin,
-    ObjectView, ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView, ObjectPermissionRequiredMixin,
+    BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, BulkRenameView, ComponentCreateView,
+    GetReturnURLMixin, ObjectView, ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
+    ObjectPermissionRequiredMixin,
 )
 )
 from virtualization.models import VirtualMachine
 from virtualization.models import VirtualMachine
 from . import filters, forms, tables
 from . import filters, forms, tables
@@ -41,58 +41,6 @@ from .models import (
 )
 )
 
 
 
 
-class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
-    """
-    An extendable view for renaming device components in bulk.
-    """
-    queryset = None
-    form = None
-    template_name = 'dcim/bulk_rename.html'
-
-    def get_required_permission(self):
-        return get_permission_for_model(self.queryset.model, 'change')
-
-    def post(self, request):
-
-        if '_preview' in request.POST or '_apply' in request.POST:
-            form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
-            selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
-
-            if form.is_valid():
-                for obj in selected_objects:
-                    find = form.cleaned_data['find']
-                    replace = form.cleaned_data['replace']
-                    if form.cleaned_data['use_regex']:
-                        try:
-                            obj.new_name = re.sub(find, replace, obj.name)
-                        # Catch regex group reference errors
-                        except re.error:
-                            obj.new_name = obj.name
-                    else:
-                        obj.new_name = obj.name.replace(find, replace)
-
-                if '_apply' in request.POST:
-                    for obj in selected_objects:
-                        obj.name = obj.new_name
-                        obj.save()
-                    messages.success(request, "Renamed {} {}".format(
-                        len(selected_objects),
-                        self.queryset.model._meta.verbose_name_plural
-                    ))
-                    return redirect(self.get_return_url(request))
-
-        else:
-            form = self.form(initial={'pk': request.POST.getlist('pk')})
-            selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
-
-        return render(request, self.template_name, {
-            'form': form,
-            'obj_type_plural': self.queryset.model._meta.verbose_name_plural,
-            'selected_objects': selected_objects,
-            'return_url': self.get_return_url(request),
-        })
-
-
 class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
 class BulkDisconnectView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
     """
     """
     An extendable view for disconnection console/power/interface components in bulk.
     An extendable view for disconnection console/power/interface components in bulk.

+ 0 - 0
netbox/templates/dcim/bulk_rename.html → netbox/templates/utilities/obj_bulk_rename.html


+ 3 - 0
netbox/templates/virtualization/virtualmachine.html

@@ -292,6 +292,9 @@
             {% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %}
             {% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %}
                 <div class="panel-footer noprint">
                 <div class="panel-footer noprint">
                     {% if interfaces and perms.virtualization.change_vminterface %}
                     {% if interfaces and perms.virtualization.change_vminterface %}
+                        <button type="submit" name="_rename" formaction="{% url 'virtualization:vminterface_bulk_rename' %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-warning btn-xs">
+                            <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
+                        </button>
                         <button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-warning btn-xs">
                         <button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-warning btn-xs">
                             <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
                             <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
                         </button>
                         </button>

+ 24 - 0
netbox/utilities/forms.py

@@ -733,6 +733,30 @@ class BulkEditForm(forms.Form):
             self.nullable_fields = self.Meta.nullable_fields
             self.nullable_fields = self.Meta.nullable_fields
 
 
 
 
+class BulkRenameForm(forms.Form):
+    """
+    An extendable form to be used for renaming objects in bulk.
+    """
+    find = forms.CharField()
+    replace = forms.CharField()
+    use_regex = forms.BooleanField(
+        required=False,
+        initial=True,
+        label='Use regular expressions'
+    )
+
+    def clean(self):
+
+        # Validate regular expression in "find" field
+        if self.cleaned_data['use_regex']:
+            try:
+                re.compile(self.cleaned_data['find'])
+            except re.error:
+                raise forms.ValidationError({
+                    'find': "Invalid regular expression"
+                })
+
+
 class CSVModelForm(forms.ModelForm):
 class CSVModelForm(forms.ModelForm):
     """
     """
     ModelForm used for the import of objects in CSV format.
     ModelForm used for the import of objects in CSV format.

+ 53 - 0
netbox/utilities/views.py

@@ -1,4 +1,5 @@
 import logging
 import logging
+import re
 import sys
 import sys
 from copy import deepcopy
 from copy import deepcopy
 
 
@@ -963,6 +964,58 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
         })
         })
 
 
 
 
+class BulkRenameView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
+    """
+    An extendable view for renaming objects in bulk.
+    """
+    queryset = None
+    form = None
+    template_name = 'utilities/obj_bulk_rename.html'
+
+    def get_required_permission(self):
+        return get_permission_for_model(self.queryset.model, 'change')
+
+    def post(self, request):
+
+        if '_preview' in request.POST or '_apply' in request.POST:
+            form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
+            selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
+
+            if form.is_valid():
+                for obj in selected_objects:
+                    find = form.cleaned_data['find']
+                    replace = form.cleaned_data['replace']
+                    if form.cleaned_data['use_regex']:
+                        try:
+                            obj.new_name = re.sub(find, replace, obj.name)
+                        # Catch regex group reference errors
+                        except re.error:
+                            obj.new_name = obj.name
+                    else:
+                        obj.new_name = obj.name.replace(find, replace)
+
+                if '_apply' in request.POST:
+                    for obj in selected_objects:
+                        obj.name = obj.new_name
+                        obj.save()
+                    messages.success(request, "Renamed {} {}".format(
+                        len(selected_objects),
+                        self.queryset.model._meta.verbose_name_plural
+                    ))
+                    return redirect(self.get_return_url(request))
+
+        else:
+            form = self.form(initial={'pk': request.POST.getlist('pk')})
+            selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
+
+        return render(request, self.template_name, {
+            'form': form,
+            'obj_type_plural': self.queryset.model._meta.verbose_name_plural,
+            'selected_objects': selected_objects,
+            'return_url': self.get_return_url(request),
+        })
+
+
 class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
 class BulkDeleteView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
     """
     """
     Delete objects in bulk.
     Delete objects in bulk.

+ 10 - 3
netbox/virtualization/forms.py

@@ -14,9 +14,9 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from utilities.forms import (
 from utilities.forms import (
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
     add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
-    CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm, DynamicModelChoiceField,
-    DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField, SlugField, SmallTextarea,
-    StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
+    BulkRenameForm, CommentField, ConfirmationForm, CSVChoiceField, CSVModelChoiceField, CSVModelForm,
+    DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model, JSONField,
+    SlugField, SmallTextarea, StaticSelect2, StaticSelect2Multiple, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
 )
 )
 from .choices import *
 from .choices import *
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@@ -810,6 +810,13 @@ class VMInterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
                 self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
                 self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
 
 
 
 
+class VMInterfaceBulkRenameForm(BulkRenameForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=VMInterface.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+
+
 class VMInterfaceFilterForm(forms.Form):
 class VMInterfaceFilterForm(forms.Form):
     model = VMInterface
     model = VMInterface
     enabled = forms.NullBooleanField(
     enabled = forms.NullBooleanField(

+ 1 - 0
netbox/virtualization/urls.py

@@ -55,6 +55,7 @@ urlpatterns = [
     path('interfaces/add/', views.InterfaceCreateView.as_view(), name='vminterface_add'),
     path('interfaces/add/', views.InterfaceCreateView.as_view(), name='vminterface_add'),
     path('interfaces/import/', views.InterfaceBulkImportView.as_view(), name='vminterface_import'),
     path('interfaces/import/', views.InterfaceBulkImportView.as_view(), name='vminterface_import'),
     path('interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='vminterface_bulk_edit'),
     path('interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='vminterface_bulk_edit'),
+    path('interfaces/rename/', views.InterfaceBulkRenameView.as_view(), name='vminterface_bulk_rename'),
     path('interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='vminterface_bulk_delete'),
     path('interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='vminterface_bulk_delete'),
     path('interfaces/<int:pk>/', views.InterfaceView.as_view(), name='vminterface'),
     path('interfaces/<int:pk>/', views.InterfaceView.as_view(), name='vminterface'),
     path('interfaces/<int:pk>/edit/', views.InterfaceEditView.as_view(), name='vminterface_edit'),
     path('interfaces/<int:pk>/edit/', views.InterfaceEditView.as_view(), name='vminterface_edit'),

+ 7 - 2
netbox/virtualization/views.py

@@ -10,8 +10,8 @@ from extras.views import ObjectConfigContextView
 from ipam.models import Service
 from ipam.models import Service
 from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
 from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
 from utilities.views import (
 from utilities.views import (
-    BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ObjectView,
-    ObjectDeleteView, ObjectEditView, ObjectListView,
+    BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, BulkRenameView, ComponentCreateView,
+    ObjectView, ObjectDeleteView, ObjectEditView, ObjectListView,
 )
 )
 from . import filters, forms, tables
 from . import filters, forms, tables
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@@ -362,6 +362,11 @@ class InterfaceBulkEditView(BulkEditView):
     form = forms.VMInterfaceBulkEditForm
     form = forms.VMInterfaceBulkEditForm
 
 
 
 
+class InterfaceBulkRenameView(BulkRenameView):
+    queryset = VMInterface.objects.all()
+    form = forms.VMInterfaceBulkRenameForm
+
+
 class InterfaceBulkDeleteView(BulkDeleteView):
 class InterfaceBulkDeleteView(BulkDeleteView):
     queryset = VMInterface.objects.all()
     queryset = VMInterface.objects.all()
     table = tables.VMInterfaceTable
     table = tables.VMInterfaceTable