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

Merge pull request #2316 from digitalocean/develop

Release v2.4.1
Jeremy Stretch 7 лет назад
Родитель
Сommit
ea7386b04b

+ 1 - 0
netbox/_reports

@@ -0,0 +1 @@
+Subproject commit b3a449437792668041d5cfb9cd6d025e1a5b3470

+ 3 - 3
netbox/dcim/api/serializers.py

@@ -362,7 +362,7 @@ class NestedPlatformSerializer(WritableNestedSerializer):
 #
 
 # Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency
-class DeviceIPAddressSerializer(serializers.ModelSerializer):
+class DeviceIPAddressSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
 
     class Meta:
@@ -371,7 +371,7 @@ class DeviceIPAddressSerializer(serializers.ModelSerializer):
 
 
 # Cannot import virtualization.api.NestedClusterSerializer due to circular dependency
-class NestedClusterSerializer(serializers.ModelSerializer):
+class NestedClusterSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
 
     class Meta:
@@ -380,7 +380,7 @@ class NestedClusterSerializer(serializers.ModelSerializer):
 
 
 # Cannot import NestedVirtualChassisSerializer due to circular dependency
-class DeviceVirtualChassisSerializer(serializers.ModelSerializer):
+class DeviceVirtualChassisSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
     master = NestedDeviceSerializer()
 

+ 34 - 6
netbox/dcim/tests/test_api.py

@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 from django.urls import reverse
+from netaddr import IPNetwork
 from rest_framework import status
 
 from dcim.constants import (
@@ -13,9 +14,10 @@ from dcim.models import (
     InventoryItem, Platform, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup,
     RackReservation, RackRole, Region, Site, VirtualChassis,
 )
-from ipam.models import VLAN
+from ipam.models import IPAddress, VLAN
 from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
 from utilities.testing import APITestCase
+from virtualization.models import Cluster, ClusterType
 
 
 class RegionTest(APITestCase):
@@ -1680,14 +1682,28 @@ class DeviceTest(APITestCase):
         self.devicerole2 = DeviceRole.objects.create(
             name='Test Device Role 2', slug='test-device-role-2', color='00ff00'
         )
+        cluster_type = ClusterType.objects.create(name='Test Cluster Type 1', slug='test-cluster-type-1')
+        self.cluster1 = Cluster.objects.create(name='Test Cluster 1', type=cluster_type)
         self.device1 = Device.objects.create(
-            device_type=self.devicetype1, device_role=self.devicerole1, name='Test Device 1', site=self.site1
+            device_type=self.devicetype1,
+            device_role=self.devicerole1,
+            name='Test Device 1',
+            site=self.site1,
+            cluster=self.cluster1
         )
         self.device2 = Device.objects.create(
-            device_type=self.devicetype1, device_role=self.devicerole1, name='Test Device 2', site=self.site1
+            device_type=self.devicetype1,
+            device_role=self.devicerole1,
+            name='Test Device 2',
+            site=self.site1,
+            cluster=self.cluster1
         )
         self.device3 = Device.objects.create(
-            device_type=self.devicetype1, device_role=self.devicerole1, name='Test Device 3', site=self.site1
+            device_type=self.devicetype1,
+            device_role=self.devicerole1,
+            name='Test Device 3',
+            site=self.site1,
+            cluster=self.cluster1
         )
 
     def test_get_device(self):
@@ -1696,6 +1712,8 @@ class DeviceTest(APITestCase):
         response = self.client.get(url, **self.header)
 
         self.assertEqual(response.data['name'], self.device1.name)
+        self.assertEqual(response.data['device_role']['id'], self.devicerole1.pk)
+        self.assertEqual(response.data['cluster']['id'], self.cluster1.pk)
 
     def test_list_devices(self):
 
@@ -1711,6 +1729,7 @@ class DeviceTest(APITestCase):
             'device_role': self.devicerole1.pk,
             'name': 'Test Device 4',
             'site': self.site1.pk,
+            'cluster': self.cluster1.pk,
         }
 
         url = reverse('dcim-api:device-list')
@@ -1722,7 +1741,8 @@ class DeviceTest(APITestCase):
         self.assertEqual(device4.device_type_id, data['device_type'])
         self.assertEqual(device4.device_role_id, data['device_role'])
         self.assertEqual(device4.name, data['name'])
-        self.assertEqual(device4.site_id, data['site'])
+        self.assertEqual(device4.site.pk, data['site'])
+        self.assertEqual(device4.cluster.pk, data['cluster'])
 
     def test_create_device_bulk(self):
 
@@ -1758,11 +1778,17 @@ class DeviceTest(APITestCase):
 
     def test_update_device(self):
 
+        interface = Interface.objects.create(name='Test Interface 1', device=self.device1)
+        ip4_address = IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), interface=interface)
+        ip6_address = IPAddress.objects.create(address=IPNetwork('2001:db8::1/64'), interface=interface)
+
         data = {
             'device_type': self.devicetype2.pk,
             'device_role': self.devicerole2.pk,
             'name': 'Test Device X',
             'site': self.site2.pk,
+            'primary_ip4': ip4_address.pk,
+            'primary_ip6': ip6_address.pk,
         }
 
         url = reverse('dcim-api:device-detail', kwargs={'pk': self.device1.pk})
@@ -1774,7 +1800,9 @@ class DeviceTest(APITestCase):
         self.assertEqual(device1.device_type_id, data['device_type'])
         self.assertEqual(device1.device_role_id, data['device_role'])
         self.assertEqual(device1.name, data['name'])
-        self.assertEqual(device1.site_id, data['site'])
+        self.assertEqual(device1.site.pk, data['site'])
+        self.assertEqual(device1.primary_ip4.pk, data['primary_ip4'])
+        self.assertEqual(device1.primary_ip6.pk, data['primary_ip6'])
 
     def test_delete_device(self):
 

+ 2 - 4
netbox/extras/reports.py

@@ -16,16 +16,14 @@ def is_report(obj):
     """
     Returns True if the given object is a Report.
     """
-    if obj in Report.__subclasses__():
-        return True
-    return False
+    return obj in Report.__subclasses__()
 
 
 def get_report(module_name, report_name):
     """
     Return a specific report from within a module.
     """
-    module = importlib.import_module('reports.{}'.format(module_name))
+    module = importlib.import_module(module_name)
     report = getattr(module, report_name, None)
     if report is None:
         return None

+ 1 - 1
netbox/netbox/settings.py

@@ -22,7 +22,7 @@ if sys.version_info[0] < 3:
         DeprecationWarning
     )
 
-VERSION = '2.4.0'
+VERSION = '2.4.1'
 
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 

+ 0 - 0
netbox/reports/__init__.py


+ 1 - 1
netbox/templates/dcim/device.html

@@ -553,7 +553,7 @@
                             </button>
                         {% endif %}
                         {% if interfaces and perms.dcim.delete_interface %}
-                            <button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' pk=device.pk %}" class="btn btn-danger btn-xs">
+                            <button type="submit" name="_delete" formaction="{% url 'dcim:interface_bulk_delete' pk=device.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs">
                                 <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                             </button>
                         {% endif %}

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

@@ -9,12 +9,12 @@
             <div class="panel-footer">
                 {% if table.rows %}
                     {% if edit_url %}
-                        <button type="submit" name="_edit" formaction="{% url edit_url pk=devicetype.pk %}" class="btn btn-xs btn-warning">
+                        <button type="submit" name="_edit" formaction="{% url edit_url pk=devicetype.pk %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-warning">
                             <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit Selected
                         </button>
                     {% endif %}
                     {% if delete_url %}
-                        <button type="submit" name="_delete" formaction="{% url delete_url pk=devicetype.pk %}" class="btn btn-xs btn-danger">
+                        <button type="submit" name="_delete" formaction="{% url delete_url pk=devicetype.pk %}?return_url={{ devicetype.get_absolute_url }}" class="btn btn-xs btn-danger">
                             <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete Selected
                         </button>
                     {% endif %}

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

@@ -111,7 +111,7 @@
                     </a>
                 {% endif %}
             {% endif %}
-            <a href="{% if iface.device_id %}{% url 'dcim:interface_edit' pk=iface.pk %}{% else %}{% url 'virtualization:interface_edit' pk=iface.pk %}{% endif %}" class="btn btn-info btn-xs" title="Edit interface">
+            <a href="{% if iface.device_id %}{% url 'dcim:interface_edit' pk=iface.pk %}{% else %}{% url 'virtualization:interface_edit' pk=iface.pk %}{% endif %}?return_url={{ device.get_absolute_url }}" class="btn btn-info btn-xs" title="Edit interface">
                 <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
             </a>
         {% endif %}

+ 1 - 1
netbox/templates/inc/custom_fields_panel.html

@@ -1,4 +1,4 @@
-{% with custom_fields=obj.custom_fields %}
+{% with custom_fields=obj.get_custom_fields %}
     {% if custom_fields %}
         <div class="panel panel-default">
             <div class="panel-heading">

+ 2 - 2
netbox/templates/virtualization/virtualmachine.html

@@ -282,12 +282,12 @@
                         <button type="submit" name="_rename" formaction="{% url 'dcim:interface_bulk_rename' %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-warning btn-xs">
                             <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Rename
                         </button>
-                        <button type="submit" name="_edit" formaction="{% url 'virtualization:interface_bulk_edit' pk=virtualmachine.pk %}" class="btn btn-warning btn-xs">
+                        <button type="submit" name="_edit" formaction="{% url 'virtualization:interface_bulk_edit' pk=virtualmachine.pk %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-warning btn-xs">
                             <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
                         </button>
                     {% endif %}
                     {% if interfaces and perms.dcim.delete_interface %}
-                        <button type="submit" name="_delete" formaction="{% url 'virtualization:interface_bulk_delete' pk=virtualmachine.pk %}" class="btn btn-danger btn-xs">
+                        <button type="submit" name="_delete" formaction="{% url 'virtualization:interface_bulk_delete' pk=virtualmachine.pk %}?return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-danger btn-xs">
                             <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                         </button>
                     {% endif %}

+ 2 - 2
netbox/utilities/utils.py

@@ -101,8 +101,8 @@ def serialize_object(obj, extra=None):
         }
 
     # Include any tags
-    # if hasattr(obj, 'tags'):
-    #     data['tags'] = [tag.name for tag in obj.tags.all()]
+    if hasattr(obj, 'tags'):
+        data['tags'] = [tag.name for tag in obj.tags.all()]
 
     # Append any extra data
     if extra is not None:

+ 1 - 1
netbox/virtualization/api/serializers.py

@@ -82,7 +82,7 @@ class NestedClusterSerializer(WritableNestedSerializer):
 #
 
 # Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency
-class VirtualMachineIPAddressSerializer(serializers.ModelSerializer):
+class VirtualMachineIPAddressSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
 
     class Meta:

+ 10 - 1
netbox/virtualization/tests/test_api.py

@@ -1,11 +1,12 @@
 from __future__ import unicode_literals
 
 from django.urls import reverse
+from netaddr import IPNetwork
 from rest_framework import status
 
 from dcim.constants import IFACE_FF_VIRTUAL, IFACE_MODE_TAGGED
 from dcim.models import Interface
-from ipam.models import VLAN
+from ipam.models import IPAddress, VLAN
 from utilities.testing import APITestCase
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
@@ -367,6 +368,10 @@ class VirtualMachineTest(APITestCase):
 
     def test_update_virtualmachine(self):
 
+        interface = Interface.objects.create(name='Test Interface 1', virtual_machine=self.virtualmachine1)
+        ip4_address = IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), interface=interface)
+        ip6_address = IPAddress.objects.create(address=IPNetwork('2001:db8::1/64'), interface=interface)
+
         cluster2 = Cluster.objects.create(
             name='Test Cluster 2',
             type=ClusterType.objects.first(),
@@ -375,6 +380,8 @@ class VirtualMachineTest(APITestCase):
         data = {
             'name': 'Test Virtual Machine X',
             'cluster': cluster2.pk,
+            'primary_ip4': ip4_address.pk,
+            'primary_ip6': ip6_address.pk,
         }
 
         url = reverse('virtualization-api:virtualmachine-detail', kwargs={'pk': self.virtualmachine1.pk})
@@ -385,6 +392,8 @@ class VirtualMachineTest(APITestCase):
         virtualmachine1 = VirtualMachine.objects.get(pk=response.data['id'])
         self.assertEqual(virtualmachine1.name, data['name'])
         self.assertEqual(virtualmachine1.cluster.pk, data['cluster'])
+        self.assertEqual(virtualmachine1.primary_ip4.pk, data['primary_ip4'])
+        self.assertEqual(virtualmachine1.primary_ip6.pk, data['primary_ip6'])
 
     def test_delete_virtualmachine(self):