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

Merge branch 'develop' into 2921-tags-select2

hSaria 6 лет назад
Родитель
Сommit
83ee83142a

+ 18 - 14
docs/release-notes/version-2.6.md

@@ -10,17 +10,19 @@
 
 
 ## Enhancements
 ## Enhancements
 
 
-* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger
-* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link
+* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger (OpenAPI)
+* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering over the link
 * [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers
 * [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers
-* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle for showing available prefixes/ip addresses
-* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address
-* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces
-* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations
-* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view
-* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace
-* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation
-* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address
+* [#2589](https://github.com/netbox-community/netbox/issues/2589) - Toggle the display of child prefixes/IP addresses
+* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address to interfaces
+* [#3021](https://github.com/netbox-community/netbox/issues/3021) - Add `tenant` filter field for cables
+* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Enable filtering of interfaces by name on the device view
+* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations view
+* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate assigned circuits at the provider details view
+* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total path length to cable trace
+* [#3491](https://github.com/netbox-community/netbox/issues/3491) - Include content of response on webhook error
+* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Enable word expansion during interface creation
+* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Enable searching by DNS name when assigning IP address
 * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
 * [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
 * [#3891](https://github.com/netbox-community/netbox/issues/3891) - Add `local_context_data` filter for virtual machines
 * [#3891](https://github.com/netbox-community/netbox/issues/3891) - Add `local_context_data` filter for virtual machines
 
 
@@ -30,12 +32,14 @@
 * [#3849](https://github.com/netbox-community/netbox/issues/3849) - Fix ordering of models when dumping data to JSON
 * [#3849](https://github.com/netbox-community/netbox/issues/3849) - Fix ordering of models when dumping data to JSON
 * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view
 * [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view
 * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses
 * [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses
-* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix group custom links rendering
+* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix rendering of grouped custom links
 * [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names
 * [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names
-* [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks
-* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs of an address
-* [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fixed min/max to ASN input field at the site creation page
+* [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks for prefixes and IP addresses
+* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs on the IP address view
+* [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fix minimum/maximum value rendering for site ASN field
 * [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group
 * [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group
+* [#3898](https://github.com/netbox-community/netbox/issues/3898) - Fix references to deleted cables without a label 
+* [#3905](https://github.com/netbox-community/netbox/issues/3905) - Fix divide-by-zero on power feeds with low power values
 
 
 ---
 ---
 
 

+ 8 - 0
netbox/dcim/filters.py

@@ -1050,6 +1050,14 @@ class CableFilter(django_filters.FilterSet):
         method='filter_device',
         method='filter_device',
         field_name='device__site__slug'
         field_name='device__site__slug'
     )
     )
+    tenant_id = MultiValueNumberFilter(
+        method='filter_device',
+        field_name='device__tenant_id'
+    )
+    tenant = MultiValueNumberFilter(
+        method='filter_device',
+        field_name='device__tenant__slug'
+    )
 
 
     class Meta:
     class Meta:
         model = Cable
         model = Cable

+ 15 - 1
netbox/dcim/forms.py

@@ -748,7 +748,7 @@ class RackElevationFilterForm(RackFilterForm):
 
 
         # Filter the rack field based on the site and group
         # Filter the rack field based on the site and group
         self.fields['site'].widget.add_filter_for('id', 'site')
         self.fields['site'].widget.add_filter_for('id', 'site')
-        self.fields['rack_group_id'].widget.add_filter_for('id', 'group_id')
+        self.fields['group_id'].widget.add_filter_for('id', 'group_id')
 
 
 
 
 #
 #
@@ -2821,6 +2821,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f
     termination_b_provider = forms.ModelChoiceField(
     termination_b_provider = forms.ModelChoiceField(
         queryset=Provider.objects.all(),
         queryset=Provider.objects.all(),
         label='Provider',
         label='Provider',
+        required=False,
         widget=APISelect(
         widget=APISelect(
             api_url='/api/circuits/providers/',
             api_url='/api/circuits/providers/',
             filter_for={
             filter_for={
@@ -2874,6 +2875,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
     termination_b_site = forms.ModelChoiceField(
     termination_b_site = forms.ModelChoiceField(
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         label='Site',
         label='Site',
+        required=False,
         widget=APISelect(
         widget=APISelect(
             api_url='/api/dcim/sites/',
             api_url='/api/dcim/sites/',
             display_field='cid',
             display_field='cid',
@@ -2905,6 +2907,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
             ('rack_group', 'termination_b_rackgroup'),
             ('rack_group', 'termination_b_rackgroup'),
         ),
         ),
         label='Power Panel',
         label='Power Panel',
+        required=False,
         widget=APISelect(
         widget=APISelect(
             api_url='/api/dcim/power-panels/',
             api_url='/api/dcim/power-panels/',
             filter_for={
             filter_for={
@@ -3136,6 +3139,17 @@ class CableFilterForm(BootstrapMixin, forms.Form):
             }
             }
         )
         )
     )
     )
+    tenant = FilterChoiceField(
+        queryset=Tenant.objects.all(),
+        to_field_name='slug',
+        widget=APISelectMultiple(
+            api_url="/api/tenancy/tenants/",
+            value_field='slug',
+            filter_for={
+                'device_id': 'tenant',
+            }
+        )
+    )
     rack_id = FilterChoiceField(
     rack_id = FilterChoiceField(
         queryset=Rack.objects.all(),
         queryset=Rack.objects.all(),
         label='Rack',
         label='Rack',

+ 9 - 7
netbox/dcim/models.py

@@ -3027,15 +3027,14 @@ class Cable(ChangeLoggedModel):
             ('termination_b_type', 'termination_b_id'),
             ('termination_b_type', 'termination_b_id'),
         )
         )
 
 
-    def __str__(self):
-        if self.label:
-            return self.label
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
 
 
-        # Save a copy of the PK on the instance since it's nullified if .delete() is called
-        if not hasattr(self, 'id_string'):
-            self.id_string = '#{}'.format(self.pk)
+        # A copy of the PK to be used by __str__ in case the object is deleted
+        self._pk = self.pk
 
 
-        return self.id_string
+    def __str__(self):
+        return self.label or '#{}'.format(self._pk)
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
         return reverse('dcim:cable', args=[self.pk])
         return reverse('dcim:cable', args=[self.pk])
@@ -3142,6 +3141,9 @@ class Cable(ChangeLoggedModel):
 
 
         super().save(*args, **kwargs)
         super().save(*args, **kwargs)
 
 
+        # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk)
+        self._pk = self.pk
+
     def to_csv(self):
     def to_csv(self):
         return (
         return (
             '{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model),
             '{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model),

+ 17 - 3
netbox/dcim/tests/test_filters.py

@@ -11,6 +11,7 @@ from dcim.models import (
     VirtualChassis,
     VirtualChassis,
 )
 )
 from ipam.models import IPAddress
 from ipam.models import IPAddress
+from tenancy.models import Tenant
 from virtualization.models import Cluster, ClusterType
 from virtualization.models import Cluster, ClusterType
 
 
 
 
@@ -2127,6 +2128,12 @@ class CableTestCase(TestCase):
         )
         )
         Site.objects.bulk_create(sites)
         Site.objects.bulk_create(sites)
 
 
+        tenants = (
+            Tenant(name='Tenant 1', slug='tenant-1'),
+            Tenant(name='Tenant 2', slug='tenant-2'),
+        )
+        Tenant.objects.bulk_create(tenants)
+
         racks = (
         racks = (
             Rack(name='Rack 1', site=sites[0]),
             Rack(name='Rack 1', site=sites[0]),
             Rack(name='Rack 2', site=sites[1]),
             Rack(name='Rack 2', site=sites[1]),
@@ -2139,9 +2146,9 @@ class CableTestCase(TestCase):
         device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
         device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
 
 
         devices = (
         devices = (
-            Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1),
-            Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2),
-            Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1),
+            Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1, tenant=tenants[0]),
+            Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2, tenant=tenants[0]),
+            Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1, tenant=tenants[1]),
             Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2),
             Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2),
             Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1),
             Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1),
             Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2),
             Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2),
@@ -2222,6 +2229,13 @@ class CableTestCase(TestCase):
         params = {'site': [site[0].slug, site[1].slug]}
         params = {'site': [site[0].slug, site[1].slug]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
 
 
+    def test_tenant(self):
+        tenant = Tenant.objects.all()[:2]
+        params = {'tenant_id': [tenant[0].pk, tenant[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+        params = {'tenant': [tenant[0].slug, tenant[1].slug]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
 
 
 class PowerPanelTestCase(TestCase):
 class PowerPanelTestCase(TestCase):
     queryset = PowerPanel.objects.all()
     queryset = PowerPanel.objects.all()

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

@@ -325,9 +325,12 @@ class CableTestCase(TestCase):
 
 
     def test_cable_deletion(self):
     def test_cable_deletion(self):
         """
         """
-        When a Cable is deleted, the `cable` field on its termination points must be nullified.
+        When a Cable is deleted, the `cable` field on its termination points must be nullified. The str() method
+        should still return the PK of the string even after being nullified.
         """
         """
         self.cable.delete()
         self.cable.delete()
+        self.assertIsNone(self.cable.pk)
+        self.assertNotEqual(str(self.cable), '#None')
         interface1 = Interface.objects.get(pk=self.interface1.pk)
         interface1 = Interface.objects.get(pk=self.interface1.pk)
         self.assertIsNone(interface1.cable)
         self.assertIsNone(interface1.cable)
         interface2 = Interface.objects.get(pk=self.interface2.pk)
         interface2 = Interface.objects.get(pk=self.interface2.pk)

+ 1 - 1
netbox/extras/webhooks_worker.py

@@ -60,5 +60,5 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque
         return 'Status {} returned, webhook successfully processed.'.format(response.status_code)
         return 'Status {} returned, webhook successfully processed.'.format(response.status_code)
     else:
     else:
         raise requests.exceptions.RequestException(
         raise requests.exceptions.RequestException(
-            "Status {} returned, webhook FAILED to process.".format(response.status_code)
+            "Status {} returned with content '{}', webhook FAILED to process.".format(response.status_code, response.content)
         )
         )

+ 1 - 1
netbox/netbox/settings.py

@@ -12,7 +12,7 @@ from django.core.exceptions import ImproperlyConfigured
 # Environment setup
 # Environment setup
 #
 #
 
 
-VERSION = '2.6.12-dev'
+VERSION = '2.6.13-dev'
 
 
 # Hostname
 # Hostname
 HOSTNAME = platform.node()
 HOSTNAME = platform.node()

+ 3 - 1
netbox/templates/dcim/powerfeed.html

@@ -112,7 +112,9 @@
                         {% if utilization %}
                         {% if utilization %}
                             <td>
                             <td>
                                 {{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA
                                 {{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA
-                                {% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}
+                                {% if powerfeed.available_power > 0 %}
+                                    {% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}
+                                {% endif %}
                             </td>
                             </td>
                         {% else %}
                         {% else %}
                             <td class="text-muted">N/A</td>
                             <td class="text-muted">N/A</td>