Repository: nautobot/nautobot

Issue:
AttributeError when adding VM Component interfaces

### Environment
* Python version: 3.6.3
* Nautobot version: v1.1.4

### Steps to Reproduce
1. Navigate to Virtualization -> Virtual Machines/
2. Click checkbox of device
3. Expand the "Add Components" button
4. Click "Interfaces"

![image](https://user-images.githubusercontent.com/9260483/139539065-67317d04-186b-494b-8c85-d6e100b91289.png)

![image](https://user-images.githubusercontent.com/9260483/139539095-4f250041-d3b2-46f0-87c7-5626922231eb.png)

<!-- What did you expect to happen? -->
### Expected Behavior

Ability to be able to bulk update interfaces to vm's

<!-- What happened instead? -->
### Observed Behavior

Stacktrace


Discussion:
Well this one is quite the metaprogramming pickle. Thanks for the report @itdependsnetworks!

Detailed meta traceback shows something funky getting passed into the the form:

![Cursor_and_AttributeError__type_object__VMInterface__has_no_attribute__get__-_networktocode-llc_-_nautobot](https://user-images.githubusercontent.com/138052/140197523-706a2bda-bd92-46df-b9fa-f57efceefc51.png)



I'd like to work on this one

Resolving patch:
diff --git a/nautobot/virtualization/forms.py b/nautobot/virtualization/forms.py
--- a/nautobot/virtualization/forms.py
+++ b/nautobot/virtualization/forms.py
@@ -8,6 +8,7 @@
 from nautobot.dcim.models import Device, DeviceRole, Platform, Rack, Region, Site
 from nautobot.extras.forms import (
     AddRemoveTagsForm,
+    CustomFieldBulkCreateForm,
     CustomFieldBulkEditForm,
     CustomFieldFilterForm,
     CustomFieldModelCSVForm,
@@ -679,18 +680,24 @@ class VMInterfaceFilterForm(BootstrapMixin, CustomFieldFilterForm):
 #
 
 
-class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
+class VirtualMachineBulkAddComponentForm(CustomFieldBulkCreateForm, BootstrapMixin, forms.Form):
     pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput())
     name_pattern = ExpandableNameField(label="Name")
+    tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False)
 
-    def clean_tags(self):
-        # Because we're feeding TagField data (on the bulk edit form) to another TagField (on the model form), we
-        # must first convert the list of tags to a string.
-        return ",".join(self.cleaned_data.get("tags"))
+    class Meta:
+        nullable_fields = []
 
 
 class VMInterfaceBulkCreateForm(
-    form_from_model(VMInterface, ["enabled", "mtu", "description", "tags"]),
+    form_from_model(VMInterface, ["enabled", "mtu", "description", "mode"]),
     VirtualMachineBulkAddComponentForm,
 ):
-    pass
+    field_order = (
+        "name_pattern",
+        "enabled",
+        "mtu",
+        "description",
+        "mode",
+        "tags",
+    )


Test patch:
diff --git a/nautobot/virtualization/tests/test_views.py b/nautobot/virtualization/tests/test_views.py
--- a/nautobot/virtualization/tests/test_views.py
+++ b/nautobot/virtualization/tests/test_views.py
@@ -1,9 +1,10 @@
 from django.test import override_settings
+from django.contrib.contenttypes.models import ContentType
 from netaddr import EUI
 
 from nautobot.dcim.choices import InterfaceModeChoices
 from nautobot.dcim.models import DeviceRole, Platform, Site
-from nautobot.extras.models import ConfigContextSchema, Status
+from nautobot.extras.models import ConfigContextSchema, CustomField, Status
 from nautobot.ipam.models import VLAN
 from nautobot.utilities.testing import ViewTestCases, post_data
 from nautobot.virtualization.models import (
@@ -300,6 +301,11 @@ def setUpTestData(cls):
             VLAN.objects.create(vid=103, name="VLAN103", site=site),
         )
 
+        obj_type = ContentType.objects.get_for_model(VMInterface)
+        cf = CustomField.objects.create(name="custom_field_1", type="text")
+        cf.save()
+        cf.content_types.set([obj_type])
+
         tags = cls.create_tags("Alpha", "Bravo", "Charlie")
 
         cls.form_data = {
@@ -312,6 +318,7 @@ def setUpTestData(cls):
             "mode": InterfaceModeChoices.MODE_TAGGED,
             "untagged_vlan": vlans[0].pk,
             "tagged_vlans": [v.pk for v in vlans[1:4]],
+            "custom_field_1": "Custom Field Data",
             "tags": [t.pk for t in tags],
         }
 
@@ -325,6 +332,7 @@ def setUpTestData(cls):
             "mode": InterfaceModeChoices.MODE_TAGGED,
             "untagged_vlan": vlans[0].pk,
             "tagged_vlans": [v.pk for v in vlans[1:4]],
+            "custom_field_1": "Custom Field Data",
             "tags": [t.pk for t in tags],
         }
 
@@ -342,4 +350,5 @@ def setUpTestData(cls):
             "mode": InterfaceModeChoices.MODE_TAGGED,
             "untagged_vlan": vlans[0].pk,
             "tagged_vlans": [v.pk for v in vlans[1:4]],
+            "custom_field_1": "New Custom Field Data",
         }