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

Merge pull request #3412 from netbox-community/3405-bugfix

Move device component creation logic out of Device model
Jeremy Stretch 6 лет назад
Родитель
Сommit
f18c3be745
3 измененных файлов с 224 добавлено и 30 удалено
  1. 8 0
      CHANGELOG.md
  2. 85 29
      netbox/dcim/models.py
  3. 131 1
      netbox/dcim/tests/test_models.py

+ 8 - 0
CHANGELOG.md

@@ -1,3 +1,11 @@
+v2.6.3 (FUTURE)
+
+## Bug Fixes
+
+* [#3405](https://github.com/netbox-community/netbox/issues/3405) - Fix population of power port/outlet details on device creation
+
+---
+
 v2.6.2 (2019-08-02)
 
 ## Enhancements

+ 85 - 29
netbox/dcim/models.py

@@ -31,6 +31,12 @@ class ComponentTemplateModel(models.Model):
     class Meta:
         abstract = True
 
+    def instantiate(self, device):
+        """
+        Instantiate a new component on the specified Device.
+        """
+        raise NotImplementedError()
+
     def log_change(self, user, request_id, action):
         """
         Log an ObjectChange including the parent DeviceType.
@@ -1010,6 +1016,12 @@ class ConsolePortTemplate(ComponentTemplateModel):
     def __str__(self):
         return self.name
 
+    def instantiate(self, device):
+        return ConsolePort(
+            device=device,
+            name=self.name
+        )
+
 
 class ConsoleServerPortTemplate(ComponentTemplateModel):
     """
@@ -1033,6 +1045,12 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
     def __str__(self):
         return self.name
 
+    def instantiate(self, device):
+        return ConsoleServerPort(
+            device=device,
+            name=self.name
+        )
+
 
 class PowerPortTemplate(ComponentTemplateModel):
     """
@@ -1068,6 +1086,14 @@ class PowerPortTemplate(ComponentTemplateModel):
     def __str__(self):
         return self.name
 
+    def instantiate(self, device):
+        return PowerPort(
+            device=device,
+            name=self.name,
+            maximum_draw=self.maximum_draw,
+            allocated_draw=self.allocated_draw
+        )
+
 
 class PowerOutletTemplate(ComponentTemplateModel):
     """
@@ -1112,6 +1138,18 @@ class PowerOutletTemplate(ComponentTemplateModel):
                 "Parent power port ({}) must belong to the same device type".format(self.power_port)
             )
 
+    def instantiate(self, device):
+        if self.power_port:
+            power_port = PowerPort.objects.get(device=device, name=self.power_port.name)
+        else:
+            power_port = None
+        return PowerOutlet(
+            device=device,
+            name=self.name,
+            power_port=power_port,
+            feed_leg=self.feed_leg
+        )
+
 
 class InterfaceTemplate(ComponentTemplateModel):
     """
@@ -1159,6 +1197,14 @@ class InterfaceTemplate(ComponentTemplateModel):
         """
         self.type = value
 
+    def instantiate(self, device):
+        return Interface(
+            device=device,
+            name=self.name,
+            type=self.type,
+            mgmt_only=self.mgmt_only
+        )
+
 
 class FrontPortTemplate(ComponentTemplateModel):
     """
@@ -1213,6 +1259,19 @@ class FrontPortTemplate(ComponentTemplateModel):
                 )
             )
 
+    def instantiate(self, device):
+        if self.rear_port:
+            rear_port = RearPort.objects.get(device=device, name=self.rear_port.name)
+        else:
+            rear_port = None
+        return FrontPort(
+            device=device,
+            name=self.name,
+            type=self.type,
+            rear_port=rear_port,
+            rear_port_position=self.rear_port_position
+        )
+
 
 class RearPortTemplate(ComponentTemplateModel):
     """
@@ -1243,6 +1302,14 @@ class RearPortTemplate(ComponentTemplateModel):
     def __str__(self):
         return self.name
 
+    def instantiate(self, device):
+        return RearPort(
+            device=device,
+            name=self.name,
+            type=self.type,
+            positions=self.positions
+        )
+
 
 class DeviceBayTemplate(ComponentTemplateModel):
     """
@@ -1266,6 +1333,12 @@ class DeviceBayTemplate(ComponentTemplateModel):
     def __str__(self):
         return self.name
 
+    def instantiate(self, device):
+        return DeviceBay(
+            device=device,
+            name=self.name
+        )
+
 
 #
 # Devices
@@ -1640,45 +1713,28 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
         # If this is a new Device, instantiate all of the related components per the DeviceType definition
         if is_new:
             ConsolePort.objects.bulk_create(
-                [ConsolePort(device=self, name=template.name) for template in
-                 self.device_type.consoleport_templates.all()]
+                [x.instantiate(self) for x in self.device_type.consoleport_templates.all()]
             )
             ConsoleServerPort.objects.bulk_create(
-                [ConsoleServerPort(device=self, name=template.name) for template in
-                 self.device_type.consoleserverport_templates.all()]
+                [x.instantiate(self) for x in self.device_type.consoleserverport_templates.all()]
             )
             PowerPort.objects.bulk_create(
-                [PowerPort(device=self, name=template.name) for template in
-                 self.device_type.powerport_templates.all()]
+                [x.instantiate(self) for x in self.device_type.powerport_templates.all()]
             )
             PowerOutlet.objects.bulk_create(
-                [PowerOutlet(device=self, name=template.name) for template in
-                 self.device_type.poweroutlet_templates.all()]
+                [x.instantiate(self) for x in self.device_type.poweroutlet_templates.all()]
             )
             Interface.objects.bulk_create(
-                [Interface(device=self, name=template.name, type=template.type,
-                           mgmt_only=template.mgmt_only) for template in self.device_type.interface_templates.all()]
+                [x.instantiate(self) for x in self.device_type.interface_templates.all()]
+            )
+            RearPort.objects.bulk_create(
+                [x.instantiate(self) for x in self.device_type.rearport_templates.all()]
+            )
+            FrontPort.objects.bulk_create(
+                [x.instantiate(self) for x in self.device_type.frontport_templates.all()]
             )
-            RearPort.objects.bulk_create([
-                RearPort(
-                    device=self,
-                    name=template.name,
-                    type=template.type,
-                    positions=template.positions
-                ) for template in self.device_type.rearport_templates.all()
-            ])
-            FrontPort.objects.bulk_create([
-                FrontPort(
-                    device=self,
-                    name=template.name,
-                    type=template.type,
-                    rear_port=RearPort.objects.get(device=self, name=template.rear_port.name),
-                    rear_port_position=template.rear_port_position,
-                ) for template in self.device_type.frontport_templates.all()
-            ])
             DeviceBay.objects.bulk_create(
-                [DeviceBay(device=self, name=template.name) for template in
-                 self.device_type.device_bay_templates.all()]
+                [x.instantiate(self) for x in self.device_type.device_bay_templates.all()]
             )
 
         # Update Site and Rack assignment for any child Devices

+ 131 - 1
netbox/dcim/tests/test_models.py

@@ -1,6 +1,5 @@
 from django.test import TestCase
 
-from dcim.constants import *
 from dcim.models import *
 
 
@@ -152,6 +151,137 @@ class RackTestCase(TestCase):
         self.assertTrue(pdu)
 
 
+class DeviceTestCase(TestCase):
+
+    def setUp(self):
+
+        self.site = Site.objects.create(name='Test Site 1', slug='test-site-1')
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        self.device_type = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
+        )
+        self.device_role = DeviceRole.objects.create(
+            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
+        )
+
+        # Create DeviceType components
+        ConsolePortTemplate(
+            device_type=self.device_type,
+            name='Console Port 1'
+        ).save()
+
+        ConsoleServerPortTemplate(
+            device_type=self.device_type,
+            name='Console Server Port 1'
+        ).save()
+
+        ppt = PowerPortTemplate(
+            device_type=self.device_type,
+            name='Power Port 1',
+            maximum_draw=1000,
+            allocated_draw=500
+        )
+        ppt.save()
+
+        PowerOutletTemplate(
+            device_type=self.device_type,
+            name='Power Outlet 1',
+            power_port=ppt,
+            feed_leg=POWERFEED_LEG_A
+        ).save()
+
+        InterfaceTemplate(
+            device_type=self.device_type,
+            name='Interface 1',
+            type=IFACE_TYPE_1GE_FIXED,
+            mgmt_only=True
+        ).save()
+
+        rpt = RearPortTemplate(
+            device_type=self.device_type,
+            name='Rear Port 1',
+            type=PORT_TYPE_8P8C,
+            positions=8
+        )
+        rpt.save()
+
+        FrontPortTemplate(
+            device_type=self.device_type,
+            name='Front Port 1',
+            type=PORT_TYPE_8P8C,
+            rear_port=rpt,
+            rear_port_position=2
+        ).save()
+
+        DeviceBayTemplate(
+            device_type=self.device_type,
+            name='Device Bay 1'
+        ).save()
+
+    def test_device_creation(self):
+        """
+        Ensure that all Device components are copied automatically from the DeviceType.
+        """
+        d = Device(
+            site=self.site,
+            device_type=self.device_type,
+            device_role=self.device_role,
+            name='Test Device 1'
+        )
+        d.save()
+
+        ConsolePort.objects.get(
+            device=d,
+            name='Console Port 1'
+        )
+
+        ConsoleServerPort.objects.get(
+            device=d,
+            name='Console Server Port 1'
+        )
+
+        pp = PowerPort.objects.get(
+            device=d,
+            name='Power Port 1',
+            maximum_draw=1000,
+            allocated_draw=500
+        )
+
+        PowerOutlet.objects.get(
+            device=d,
+            name='Power Outlet 1',
+            power_port=pp,
+            feed_leg=POWERFEED_LEG_A
+        )
+
+        Interface.objects.get(
+            device=d,
+            name='Interface 1',
+            type=IFACE_TYPE_1GE_FIXED,
+            mgmt_only=True
+        )
+
+        rp = RearPort.objects.get(
+            device=d,
+            name='Rear Port 1',
+            type=PORT_TYPE_8P8C,
+            positions=8
+        )
+
+        FrontPort.objects.get(
+            device=d,
+            name='Front Port 1',
+            type=PORT_TYPE_8P8C,
+            rear_port=rp,
+            rear_port_position=2
+        )
+
+        DeviceBay.objects.get(
+            device=d,
+            name='Device Bay 1'
+        )
+
+
 class CableTestCase(TestCase):
 
     def setUp(self):