Parcourir la source

#353: Allow bulk editing of interfaces

Jeremy Stretch il y a 8 ans
Parent
commit
0da3661ff0

+ 12 - 3
netbox/dcim/forms.py

@@ -16,9 +16,9 @@ from utilities.forms import (
 from .models import (
     DeviceBay, DeviceBayTemplate, CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_PLANNED, CONNECTION_STATUS_CONNECTED,
     ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
-    Interface, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
-    PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackRole,
-    Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
+    Interface, IFACE_FF_CHOICES, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module,
+    Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES,
+    Rack, RackGroup, RackRole, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
 )
 
 
@@ -1008,6 +1008,15 @@ class InterfaceBulkCreateForm(InterfaceCreateForm, BootstrapMixin):
     pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
 
 
+class InterfaceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
+    pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
+    form_factor = forms.ChoiceField(choices=add_blank_choice(IFACE_FF_CHOICES), required=False)
+    description = forms.CharField(max_length=100, required=False)
+
+    class Meta:
+        nullable_fields = ['description']
+
+
 #
 # Interface connections
 #

+ 1 - 4
netbox/dcim/urls.py

@@ -3,10 +3,6 @@ from django.conf.urls import url
 from secrets.views import secret_add
 
 from . import views
-from .models import (
-    ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, PowerPortTemplate, PowerOutletTemplate,
-    InterfaceTemplate,
-)
 
 
 urlpatterns = [
@@ -159,6 +155,7 @@ urlpatterns = [
     # Interfaces
     url(r'^devices/interfaces/add/$', views.InterfaceBulkAddView.as_view(), name='interface_add_multi'),
     url(r'^devices/(?P<pk>\d+)/interfaces/add/$', views.interface_add, name='interface_add'),
+    url(r'^devices/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
     url(r'^devices/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
     url(r'^devices/(?P<pk>\d+)/interface-connections/add/$', views.interfaceconnection_add, name='interfaceconnection_add'),
     url(r'^interface-connections/(?P<pk>\d+)/delete/$', views.interfaceconnection_delete, name='interfaceconnection_delete'),

+ 8 - 0
netbox/dcim/views.py

@@ -1425,6 +1425,14 @@ class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
                                                                                       len(selected_devices)))
 
 
+class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_interface'
+    cls = Interface
+    parent_cls = Device
+    form = forms.InterfaceBulkEditForm
+    template_name = 'dcim/interface_bulk_edit.html'
+
+
 class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_interface'
     cls = Interface

+ 7 - 2
netbox/templates/dcim/device.html

@@ -355,7 +355,7 @@
         {% endif %}
         {% if interfaces or device.device_type.is_network_device %}
             {% if perms.dcim.delete_interface %}
-                <form method="post" action="{% url 'dcim:interface_bulk_delete' pk=device.pk %}">
+                <form method="post">
                 {% csrf_token %}
             {% endif %}
             <div class="panel panel-default">
@@ -380,8 +380,13 @@
                 </table>
                 {% if perms.dcim.add_interface or perms.dcim.delete_interface %}
                     <div class="panel-footer">
+                        {% if interfaces and perms.dcim.change_interface %}
+                            <button type="submit" name="_edit" formaction="{% url 'dcim:interface_bulk_edit' pk=device.pk %}" class="btn btn-warning btn-xs">
+                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Edit selected
+                            </button>
+                        {% endif %}
                         {% if interfaces and perms.dcim.delete_interface %}
-                            <button type="submit" class="btn btn-danger btn-xs">
+                            <button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' pk=device.pk %}" class="btn btn-danger btn-xs">
                                 <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete selected
                             </button>
                         {% endif %}

+ 17 - 0
netbox/templates/dcim/interface_bulk_edit.html

@@ -0,0 +1,17 @@
+{% extends 'utilities/bulk_edit_form.html' %}
+{% load form_helpers %}
+
+{% block title %}Interface Bulk Edit{% endblock %}
+
+{% block selected_objects_table %}
+    <tr>
+        <th>Name</th>
+        <th>Form Factor</th>
+    </tr>
+    {% for iface in selected_objects %}
+        <tr>
+            <td>{{ iface.name }}</td>
+            <td>{{ iface.get_form_factor_display }}</td>
+        </tr>
+    {% endfor %}
+{% endblock %}

+ 1 - 1
netbox/utilities/forms.py

@@ -34,7 +34,7 @@ def add_blank_choice(choices):
     """
     Add a blank choice to the beginning of a choices list.
     """
-    return ((None, '---------'),) + choices
+    return ((None, '---------'),) + tuple(choices)
 
 
 #

+ 19 - 6
netbox/utilities/views.py

@@ -280,21 +280,34 @@ class BulkImportView(View):
 
 class BulkEditView(View):
     cls = None
+    parent_cls = None
     form = None
     template_name = None
     default_redirect_url = None
 
-    def get(self, request, *args, **kwargs):
+    def get(self):
         return redirect(self.default_redirect_url)
 
-    def post(self, request, *args, **kwargs):
+    def post(self, request, **kwargs):
+
+        # Attempt to derive parent object if a parent class has been given
+        if self.parent_cls:
+            parent_obj = get_object_or_404(self.parent_cls, **kwargs)
+        else:
+            parent_obj = None
 
+        # Determine URL to redirect users upon modification of objects
         posted_redirect_url = request.POST.get('redirect_url')
         if posted_redirect_url and is_safe_url(url=posted_redirect_url, host=request.get_host()):
             redirect_url = posted_redirect_url
-        else:
+        elif parent_obj:
+            redirect_url = parent_obj.get_absolute_url()
+        elif self.default_redirect_url:
             redirect_url = reverse(self.default_redirect_url)
+        else:
+            raise ImproperlyConfigured('No redirect URL has been provided.')
 
+        # Are we editing *all* objects in the queryset or just a selected subset?
         if request.POST.get('_all'):
             pk_list = [int(pk) for pk in request.POST.get('pk_all').split(',') if pk]
         else:
@@ -398,7 +411,7 @@ class BulkDeleteView(View):
     template_name = 'utilities/confirm_bulk_delete.html'
     default_redirect_url = None
 
-    def post(self, request, *args, **kwargs):
+    def post(self, request, **kwargs):
 
         # Attempt to derive parent object if a parent class has been given
         if self.parent_cls:
@@ -419,9 +432,9 @@ class BulkDeleteView(View):
 
         # Are we deleting *all* objects in the queryset or just a selected subset?
         if request.POST.get('_all'):
-            pk_list = [x for x in request.POST.get('pk_all').split(',') if x]
+            pk_list = [int(pk) for pk in request.POST.get('pk_all').split(',') if pk]
         else:
-            pk_list = request.POST.getlist('pk')
+            pk_list = [int(pk) for pk in request.POST.getlist('pk')]
 
         form_cls = self.get_form()