Repository: netbox-community/netbox

Issue:
Unable to add Power Port or Interface Templates to Module Type via WebUI
### NetBox version

v3.3.3

### Python version

3.8

### Steps to Reproduce

Using web interface:
1. Create new Module Type
2. Add Components -> Power Port
3. "Module Type" defaults to the newly created Module Type, and Device Type is "------" (null?), but still has a "required" asterisk next to it and bold field name
4. Click "Create"

### Expected Behavior

Should be able to create Power Ports (and Interfaces) on Module Types. I believe this worked in the past, because I have existing Module Types (removable power supplies) that have Power Port Templates attached to them already, and I'm pretty sure I defined those Module Types by hand with the GUI (not with YAML import).

I'm not sure when this broke, as I haven't attempted to create a new Module Type in a while.

I have also **not** attempted to do this task with the API to see if that avoids the problem (we aren't really using it, yet).

### Observed Behavior

Not able to create Power Port (or Interface) associated with a Module Type rather than a Device Type - form is requiring that a Device Type be selected.

On a whim, tried selecting both a Device Type and the Module Type, and got the error:
> A component template cannot be associated with both a device type and a module type.




Discussion:

Resolving patch:
diff --git a/netbox/dcim/forms/models.py b/netbox/dcim/forms/models.py
--- a/netbox/dcim/forms/models.py
+++ b/netbox/dcim/forms/models.py
@@ -1000,11 +1000,22 @@ def __init__(self, *args, **kwargs):
 
 
 class ModularComponentTemplateForm(ComponentTemplateForm):
+    device_type = DynamicModelChoiceField(
+        queryset=DeviceType.objects.all().all(),
+        required=False
+    )
     module_type = DynamicModelChoiceField(
         queryset=ModuleType.objects.all(),
         required=False
     )
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        # Disable reassignment of ModuleType when editing an existing instance
+        if self.instance.pk:
+            self.fields['module_type'].disabled = True
+
 
 class ConsolePortTemplateForm(ModularComponentTemplateForm):
     fieldsets = (
@@ -1429,16 +1440,6 @@ class Meta:
             'rf_channel_width': "Populated by selected channel (if set)",
         }
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        # Restrict LAG/bridge interface assignment by device/VC
-        device_id = self.data['device'] if self.is_bound else self.initial.get('device')
-        device = Device.objects.filter(pk=device_id).first()
-        if device and device.virtual_chassis and device.virtual_chassis.master:
-            self.fields['lag'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
-            self.fields['bridge'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
-
 
 class FrontPortForm(ModularDeviceComponentForm):
     rear_port = DynamicModelChoiceField(
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -29,7 +29,7 @@
 # Environment setup
 #
 
-VERSION = '3.3.3'
+VERSION = '3.3.4'
 
 # Hostname
 HOSTNAME = platform.node()


Test patch: