Просмотр исходного кода

Merge pull request #4114 from netbox-community/4113-component-bulk-editing

Closes #4113: Add bulk edit functionality for device type components
Jeremy Stretch 6 лет назад
Родитель
Сommit
091d860ae5

+ 116 - 0
netbox/dcim/forms.py

@@ -1058,6 +1058,21 @@ class ConsolePortTemplateCreateForm(BootstrapMixin, forms.Form):
     )
 
 
+class ConsolePortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=ConsolePortTemplate.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    type = forms.ChoiceField(
+        choices=add_blank_choice(ConsolePortTypeChoices),
+        required=False,
+        widget=StaticSelect2()
+    )
+
+    class Meta:
+        nullable_fields = ('type',)
+
+
 class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
@@ -1086,6 +1101,21 @@ class ConsoleServerPortTemplateCreateForm(BootstrapMixin, forms.Form):
     )
 
 
+class ConsoleServerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=ConsoleServerPortTemplate.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    type = forms.ChoiceField(
+        choices=add_blank_choice(ConsolePortTypeChoices),
+        required=False,
+        widget=StaticSelect2()
+    )
+
+    class Meta:
+        nullable_fields = ('type',)
+
+
 class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
@@ -1124,6 +1154,31 @@ class PowerPortTemplateCreateForm(BootstrapMixin, forms.Form):
     )
 
 
+class PowerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=PowerPortTemplate.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    type = forms.ChoiceField(
+        choices=add_blank_choice(PowerPortTypeChoices),
+        required=False,
+        widget=StaticSelect2()
+    )
+    maximum_draw = forms.IntegerField(
+        min_value=1,
+        required=False,
+        help_text="Maximum power draw (watts)"
+    )
+    allocated_draw = forms.IntegerField(
+        min_value=1,
+        required=False,
+        help_text="Allocated power draw (watts)"
+    )
+
+    class Meta:
+        nullable_fields = ('type', 'maximum_draw', 'allocated_draw')
+
+
 class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
@@ -1182,6 +1237,26 @@ class PowerOutletTemplateCreateForm(BootstrapMixin, forms.Form):
         )
 
 
+class PowerOutletTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=PowerOutletTemplate.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    type = forms.ChoiceField(
+        choices=add_blank_choice(PowerOutletTypeChoices),
+        required=False,
+        widget=StaticSelect2()
+    )
+    feed_leg = forms.ChoiceField(
+        choices=add_blank_choice(PowerOutletFeedLegChoices),
+        required=False,
+        widget=StaticSelect2()
+    )
+
+    class Meta:
+        nullable_fields = ('type', 'feed_leg')
+
+
 class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
@@ -1324,6 +1399,21 @@ class FrontPortTemplateCreateForm(BootstrapMixin, forms.Form):
         }
 
 
+class FrontPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=FrontPortTemplate.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    type = forms.ChoiceField(
+        choices=add_blank_choice(PortTypeChoices),
+        required=False,
+        widget=StaticSelect2()
+    )
+
+    class Meta:
+        nullable_fields = ()
+
+
 class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
@@ -1359,6 +1449,21 @@ class RearPortTemplateCreateForm(BootstrapMixin, forms.Form):
     )
 
 
+class RearPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(
+        queryset=RearPortTemplate.objects.all(),
+        widget=forms.MultipleHiddenInput()
+    )
+    type = forms.ChoiceField(
+        choices=add_blank_choice(PortTypeChoices),
+        required=False,
+        widget=StaticSelect2()
+    )
+
+    class Meta:
+        nullable_fields = ()
+
+
 class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
@@ -1383,6 +1488,17 @@ class DeviceBayTemplateCreateForm(BootstrapMixin, forms.Form):
     )
 
 
+# TODO: DeviceBayTemplate has no fields suitable for bulk-editing yet
+# class DeviceBayTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
+#     pk = forms.ModelMultipleChoiceField(
+#         queryset=FrontPortTemplate.objects.all(),
+#         widget=forms.MultipleHiddenInput()
+#     )
+#
+#     class Meta:
+#         nullable_fields = ()
+
+
 #
 # Component template import forms
 #

+ 1 - 1
netbox/dcim/tables.py

@@ -440,7 +440,7 @@ class ConsoleServerPortTemplateTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = ConsoleServerPortTemplate
-        fields = ('pk', 'name', 'actions')
+        fields = ('pk', 'name', 'type', 'actions')
         empty_text = "None"
 
 

+ 29 - 8
netbox/dcim/tests/test_views.py

@@ -537,7 +537,6 @@ class ConsolePortTemplateTestCase(StandardTestCases.Views):
     test_create_object = None
     test_delete_object = None
     test_import_objects = None
-    test_bulk_edit_objects = None
 
     def test_bulk_create_objects(self):
         return self._test_bulk_create_objects(expected_count=3)
@@ -569,6 +568,10 @@ class ConsolePortTemplateTestCase(StandardTestCases.Views):
             'type': ConsolePortTypeChoices.TYPE_RJ45,
         }
 
+        cls.bulk_edit_data = {
+            'type': ConsolePortTypeChoices.TYPE_RJ45,
+        }
+
 
 class ConsoleServerPortTemplateTestCase(StandardTestCases.Views):
     model = ConsoleServerPortTemplate
@@ -579,7 +582,6 @@ class ConsoleServerPortTemplateTestCase(StandardTestCases.Views):
     test_create_object = None
     test_delete_object = None
     test_import_objects = None
-    test_bulk_edit_objects = None
 
     def test_bulk_create_objects(self):
         return self._test_bulk_create_objects(expected_count=3)
@@ -611,6 +613,10 @@ class ConsoleServerPortTemplateTestCase(StandardTestCases.Views):
             'type': ConsolePortTypeChoices.TYPE_RJ45,
         }
 
+        cls.bulk_edit_data = {
+            'type': ConsolePortTypeChoices.TYPE_RJ45,
+        }
+
 
 class PowerPortTemplateTestCase(StandardTestCases.Views):
     model = PowerPortTemplate
@@ -621,7 +627,6 @@ class PowerPortTemplateTestCase(StandardTestCases.Views):
     test_create_object = None
     test_delete_object = None
     test_import_objects = None
-    test_bulk_edit_objects = None
 
     def test_bulk_create_objects(self):
         return self._test_bulk_create_objects(expected_count=3)
@@ -645,7 +650,7 @@ class PowerPortTemplateTestCase(StandardTestCases.Views):
             'device_type': devicetypes[1].pk,
             'name': 'Power Port Template X',
             'type': PowerPortTypeChoices.TYPE_IEC_C14,
-            'maxiumum_draw': 100,
+            'maximum_draw': 100,
             'allocated_draw': 50,
         }
 
@@ -653,7 +658,13 @@ class PowerPortTemplateTestCase(StandardTestCases.Views):
             'device_type': devicetypes[1].pk,
             'name_pattern': 'Power Port Template [4-6]',
             'type': PowerPortTypeChoices.TYPE_IEC_C14,
-            'maxiumum_draw': 100,
+            'maximum_draw': 100,
+            'allocated_draw': 50,
+        }
+
+        cls.bulk_edit_data = {
+            'type': PowerPortTypeChoices.TYPE_IEC_C14,
+            'maximum_draw': 100,
             'allocated_draw': 50,
         }
 
@@ -667,7 +678,6 @@ class PowerOutletTemplateTestCase(StandardTestCases.Views):
     test_create_object = None
     test_delete_object = None
     test_import_objects = None
-    test_bulk_edit_objects = None
 
     def test_bulk_create_objects(self):
         return self._test_bulk_create_objects(expected_count=3)
@@ -704,6 +714,11 @@ class PowerOutletTemplateTestCase(StandardTestCases.Views):
             'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
         }
 
+        cls.bulk_edit_data = {
+            'type': PowerOutletTypeChoices.TYPE_IEC_C13,
+            'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
+        }
+
 
 class InterfaceTemplateTestCase(StandardTestCases.Views):
     model = InterfaceTemplate
@@ -762,7 +777,6 @@ class FrontPortTemplateTestCase(StandardTestCases.Views):
     test_create_object = None
     test_delete_object = None
     test_import_objects = None
-    test_bulk_edit_objects = None
 
     def test_bulk_create_objects(self):
         return self._test_bulk_create_objects(expected_count=3)
@@ -805,6 +819,10 @@ class FrontPortTemplateTestCase(StandardTestCases.Views):
             ],
         }
 
+        cls.bulk_edit_data = {
+            'type': PortTypeChoices.TYPE_8P8C,
+        }
+
 
 class RearPortTemplateTestCase(StandardTestCases.Views):
     model = RearPortTemplate
@@ -815,7 +833,6 @@ class RearPortTemplateTestCase(StandardTestCases.Views):
     test_create_object = None
     test_delete_object = None
     test_import_objects = None
-    test_bulk_edit_objects = None
 
     def test_bulk_create_objects(self):
         return self._test_bulk_create_objects(expected_count=3)
@@ -849,6 +866,10 @@ class RearPortTemplateTestCase(StandardTestCases.Views):
             'positions': 2,
         }
 
+        cls.bulk_edit_data = {
+            'type': PortTypeChoices.TYPE_8P8C,
+        }
+
 
 class DeviceBayTemplateTestCase(StandardTestCases.Views):
     model = DeviceBayTemplate

+ 7 - 0
netbox/dcim/urls.py

@@ -92,21 +92,25 @@ urlpatterns = [
 
     # Console port templates
     path('console-port-templates/add/', views.ConsolePortTemplateCreateView.as_view(), name='consoleporttemplate_add'),
+    path('console-port-templates/edit/', views.ConsolePortTemplateBulkEditView.as_view(), name='consoleporttemplate_bulk_edit'),
     path('console-port-templates/delete/', views.ConsolePortTemplateBulkDeleteView.as_view(), name='consoleporttemplate_bulk_delete'),
     path('console-port-templates/<int:pk>/edit/', views.ConsolePortTemplateEditView.as_view(), name='consoleporttemplate_edit'),
 
     # Console server port templates
     path('console-server-port-templates/add/', views.ConsoleServerPortTemplateCreateView.as_view(), name='consoleserverporttemplate_add'),
+    path('console-server-port-templates/edit/', views.ConsoleServerPortTemplateBulkEditView.as_view(), name='consoleserverporttemplate_bulk_edit'),
     path('console-server-port-templates/delete/', views.ConsoleServerPortTemplateBulkDeleteView.as_view(), name='consoleserverporttemplate_bulk_delete'),
     path('console-server-port-templates/<int:pk>/edit/', views.ConsoleServerPortTemplateEditView.as_view(), name='consoleserverporttemplate_edit'),
 
     # Power port templates
     path('power-port-templates/add/', views.PowerPortTemplateCreateView.as_view(), name='powerporttemplate_add'),
+    path('power-port-templates/edit/', views.PowerPortTemplateBulkEditView.as_view(), name='powerporttemplate_bulk_edit'),
     path('power-port-templates/delete/', views.PowerPortTemplateBulkDeleteView.as_view(), name='powerporttemplate_bulk_delete'),
     path('power-port-templates/<int:pk>/edit/', views.PowerPortTemplateEditView.as_view(), name='powerporttemplate_edit'),
 
     # Power outlet templates
     path('power-outlet-templates/add/', views.PowerOutletTemplateCreateView.as_view(), name='poweroutlettemplate_add'),
+    path('power-outlet-templates/edit/', views.PowerOutletTemplateBulkEditView.as_view(), name='poweroutlettemplate_bulk_edit'),
     path('power-outlet-templates/delete/', views.PowerOutletTemplateBulkDeleteView.as_view(), name='poweroutlettemplate_bulk_delete'),
     path('power-outlet-templates/<int:pk>/edit/', views.PowerOutletTemplateEditView.as_view(), name='poweroutlettemplate_edit'),
 
@@ -118,16 +122,19 @@ urlpatterns = [
 
     # Front port templates
     path('front-port-templates/add/', views.FrontPortTemplateCreateView.as_view(), name='frontporttemplate_add'),
+    path('front-port-templates/edit/', views.FrontPortTemplateBulkEditView.as_view(), name='frontporttemplate_bulk_edit'),
     path('front-port-templates/delete/', views.FrontPortTemplateBulkDeleteView.as_view(), name='frontporttemplate_bulk_delete'),
     path('front-port-templates/<int:pk>/edit/', views.FrontPortTemplateEditView.as_view(), name='frontporttemplate_edit'),
 
     # Rear port templates
     path('rear-port-templates/add/', views.RearPortTemplateCreateView.as_view(), name='rearporttemplate_add'),
+    path('rear-port-templates/edit/', views.RearPortTemplateBulkEditView.as_view(), name='rearporttemplate_bulk_edit'),
     path('rear-port-templates/delete/', views.RearPortTemplateBulkDeleteView.as_view(), name='rearporttemplate_bulk_delete'),
     path('rear-port-templates/<int:pk>/edit/', views.RearPortTemplateEditView.as_view(), name='rearporttemplate_edit'),
 
     # Device bay templates
     path('device-bay-templates/add/', views.DeviceBayTemplateCreateView.as_view(), name='devicebaytemplate_add'),
+    # path('device-bay-templates/edit/', views.DeviceBayTemplateBulkEditView.as_view(), name='devicebaytemplate_bulk_edit'),
     path('device-bay-templates/delete/', views.DeviceBayTemplateBulkDeleteView.as_view(), name='devicebaytemplate_bulk_delete'),
     path('device-bay-templates/<int:pk>/edit/', views.DeviceBayTemplateEditView.as_view(), name='devicebaytemplate_edit'),
 

+ 49 - 0
netbox/dcim/views.py

@@ -717,6 +717,13 @@ class ConsolePortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
     model_form = forms.ConsolePortTemplateForm
 
 
+class ConsolePortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_consoleporttemplate'
+    queryset = ConsolePortTemplate.objects.all()
+    table = tables.ConsolePortTemplateTable
+    form = forms.ConsolePortTemplateBulkEditForm
+
+
 class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_consoleporttemplate'
     queryset = ConsolePortTemplate.objects.all()
@@ -737,6 +744,13 @@ class ConsoleServerPortTemplateEditView(PermissionRequiredMixin, ObjectEditView)
     model_form = forms.ConsoleServerPortTemplateForm
 
 
+class ConsoleServerPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_consoleserverporttemplate'
+    queryset = ConsoleServerPortTemplate.objects.all()
+    table = tables.ConsoleServerPortTemplateTable
+    form = forms.ConsoleServerPortTemplateBulkEditForm
+
+
 class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_consoleserverporttemplate'
     queryset = ConsoleServerPortTemplate.objects.all()
@@ -757,6 +771,13 @@ class PowerPortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
     model_form = forms.PowerPortTemplateForm
 
 
+class PowerPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_powerporttemplate'
+    queryset = PowerPortTemplate.objects.all()
+    table = tables.PowerPortTemplateTable
+    form = forms.PowerPortTemplateBulkEditForm
+
+
 class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_powerporttemplate'
     queryset = PowerPortTemplate.objects.all()
@@ -777,6 +798,13 @@ class PowerOutletTemplateEditView(PermissionRequiredMixin, ObjectEditView):
     model_form = forms.PowerOutletTemplateForm
 
 
+class PowerOutletTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_poweroutlettemplate'
+    queryset = PowerOutletTemplate.objects.all()
+    table = tables.PowerOutletTemplateTable
+    form = forms.PowerOutletTemplateBulkEditForm
+
+
 class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_poweroutlettemplate'
     queryset = PowerOutletTemplate.objects.all()
@@ -824,6 +852,13 @@ class FrontPortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
     model_form = forms.FrontPortTemplateForm
 
 
+class FrontPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_frontporttemplate'
+    queryset = FrontPortTemplate.objects.all()
+    table = tables.FrontPortTemplateTable
+    form = forms.FrontPortTemplateBulkEditForm
+
+
 class FrontPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_frontporttemplate'
     queryset = FrontPortTemplate.objects.all()
@@ -844,6 +879,13 @@ class RearPortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
     model_form = forms.RearPortTemplateForm
 
 
+class RearPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_rearporttemplate'
+    queryset = RearPortTemplate.objects.all()
+    table = tables.RearPortTemplateTable
+    form = forms.RearPortTemplateBulkEditForm
+
+
 class RearPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_rearporttemplate'
     queryset = RearPortTemplate.objects.all()
@@ -864,6 +906,13 @@ class DeviceBayTemplateEditView(PermissionRequiredMixin, ObjectEditView):
     model_form = forms.DeviceBayTemplateForm
 
 
+# class DeviceBayTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
+#     permission_required = 'dcim.change_devicebaytemplate'
+#     queryset = DeviceBayTemplate.objects.all()
+#     table = tables.DeviceBayTemplateTable
+#     form = forms.DeviceBayTemplateBulkEditForm
+
+
 class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_devicebaytemplate'
     queryset = DeviceBayTemplate.objects.all()

+ 6 - 6
netbox/templates/dcim/devicetype.html

@@ -136,10 +136,10 @@
 {% if devicetype.consoleport_templates.exists or devicetype.powerport_templates.exists %}
     <div class="row">
         <div class="col-md-6">
-            {% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:consoleporttemplate_add' edit_url=None delete_url='dcim:consoleporttemplate_bulk_delete' %}
+            {% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:consoleporttemplate_add' edit_url='dcim:consoleporttemplate_bulk_edit' delete_url='dcim:consoleporttemplate_bulk_delete' %}
         </div>
         <div class="col-md-6">
-             {% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:powerporttemplate_add' edit_url=None delete_url='dcim:powerporttemplate_bulk_delete' %}
+             {% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:powerporttemplate_add' edit_url='dcim:powerporttemplate_bulk_edit' delete_url='dcim:powerporttemplate_bulk_delete' %}
         </div>
     </div>
 {% endif %}
@@ -153,14 +153,14 @@
 {% if devicetype.consoleserverport_templates.exists %}
     <div class="row">
         <div class="col-md-12">
-            {% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:consoleserverporttemplate_add' edit_url=None delete_url='dcim:consoleserverporttemplate_bulk_delete' %}
+            {% include 'dcim/inc/devicetype_component_table.html' with table=consoleserverport_table title='Console Server Ports' add_url='dcim:consoleserverporttemplate_add' edit_url='dcim:consoleserverporttemplate_bulk_edit' delete_url='dcim:consoleserverporttemplate_bulk_delete' %}
         </div>
     </div>
 {% endif %}
 {% if devicetype.poweroutlet_templates.exists %}
     <div class="row">
         <div class="col-md-12">
-            {% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:poweroutlettemplate_add' edit_url=None delete_url='dcim:poweroutlettemplate_bulk_delete' %}
+            {% include 'dcim/inc/devicetype_component_table.html' with table=poweroutlet_table title='Power Outlets' add_url='dcim:poweroutlettemplate_add' edit_url='dcim:poweroutlettemplate_bulk_edit' delete_url='dcim:poweroutlettemplate_bulk_delete' %}
         </div>
     </div>
 {% endif %}
@@ -174,10 +174,10 @@
 {% if devicetype.frontport_templates.exists or devicetype.rearport_templates.exists %}
     <div class="row">
         <div class="col-md-6">
-            {% include 'dcim/inc/devicetype_component_table.html' with table=front_port_table title='Front Ports' add_url='dcim:frontporttemplate_add' edit_url=None delete_url='dcim:frontporttemplate_bulk_delete' %}
+            {% include 'dcim/inc/devicetype_component_table.html' with table=front_port_table title='Front Ports' add_url='dcim:frontporttemplate_add' edit_url='dcim:frontporttemplate_bulk_edit' delete_url='dcim:frontporttemplate_bulk_delete' %}
         </div>
         <div class="col-md-6">
-            {% include 'dcim/inc/devicetype_component_table.html' with table=rear_port_table title='Rear Ports' add_url='dcim:rearporttemplate_add' edit_url=None delete_url='dcim:rearporttemplate_bulk_delete' %}
+            {% include 'dcim/inc/devicetype_component_table.html' with table=rear_port_table title='Rear Ports' add_url='dcim:rearporttemplate_add' edit_url='dcim:rearporttemplate_bulk_edit' delete_url='dcim:rearporttemplate_bulk_delete' %}
         </div>
     </div>
 {% endif %}

+ 1 - 1
netbox/templates/dcim/inc/devicetype_component_table.html

@@ -20,7 +20,7 @@
                     {% endif %}
                 {% endif %}
                 <div class="pull-right">
-                    <a href="{% url add_url %}" class="btn btn-primary btn-xs">
+                    <a href="{% url add_url %}?device_type={{ devicetype.pk }}&return_url={{ devicetype.get_absolute_url }}" class="btn btn-primary btn-xs">
                         <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
                         Add {{ title }}
                     </a>

+ 1 - 1
netbox/utilities/utils.py

@@ -219,6 +219,6 @@ def querydict_to_dict(querydict):
     """
     assert isinstance(querydict, QueryDict)
     return {
-        key: querydict.get(key) if len(value) == 1 else querydict.getlist(key)
+        key: querydict.get(key) if len(value) == 1 and key != 'pk' else querydict.getlist(key)
         for key, value in querydict.lists()
     }