Merge branch 'develop' into 3525

This commit is contained in:
Jeremy Stretch
2020-01-14 09:20:02 -05:00
committed by GitHub
13 changed files with 118 additions and 39 deletions

View File

@@ -1050,6 +1050,14 @@ class CableFilter(django_filters.FilterSet):
method='filter_device',
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:
model = Cable

View File

@@ -739,7 +739,7 @@ class RackElevationFilterForm(RackFilterForm):
# Filter the rack field based on the site and group
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')
#
@@ -2804,6 +2804,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f
termination_b_provider = forms.ModelChoiceField(
queryset=Provider.objects.all(),
label='Provider',
required=False,
widget=APISelect(
api_url='/api/circuits/providers/',
filter_for={
@@ -2857,6 +2858,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
termination_b_site = forms.ModelChoiceField(
queryset=Site.objects.all(),
label='Site',
required=False,
widget=APISelect(
api_url='/api/dcim/sites/',
display_field='cid',
@@ -2888,6 +2890,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
('rack_group', 'termination_b_rackgroup'),
),
label='Power Panel',
required=False,
widget=APISelect(
api_url='/api/dcim/power-panels/',
filter_for={
@@ -3119,6 +3122,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(
queryset=Rack.objects.all(),
label='Rack',

View File

@@ -3027,15 +3027,14 @@ class Cable(ChangeLoggedModel):
('termination_b_type', 'termination_b_id'),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# A copy of the PK to be used by __str__ in case the object is deleted
self._pk = self.pk
def __str__(self):
if self.label:
return self.label
# 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)
return self.id_string
return self.label or '#{}'.format(self._pk)
def get_absolute_url(self):
return reverse('dcim:cable', args=[self.pk])
@@ -3142,6 +3141,9 @@ class Cable(ChangeLoggedModel):
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):
return (
'{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model),

View File

@@ -11,6 +11,7 @@ from dcim.models import (
VirtualChassis,
)
from ipam.models import IPAddress
from tenancy.models import Tenant
from virtualization.models import Cluster, ClusterType
@@ -1100,7 +1101,7 @@ class DeviceTestCase(TestCase):
Cluster.objects.bulk_create(clusters)
devices = (
Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=RACK_FACE_FRONT, status=DEVICE_STATUS_ACTIVE, cluster=clusters[0]),
Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=RACK_FACE_FRONT, status=DEVICE_STATUS_ACTIVE, cluster=clusters[0], local_context_data={"foo": 123}),
Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], serial='DEF', asset_tag='1002', site=sites[1], rack=racks[1], position=2, face=RACK_FACE_FRONT, status=DEVICE_STATUS_STAGED, cluster=clusters[1]),
Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], serial='GHI', asset_tag='1003', site=sites[2], rack=racks[2], position=3, face=RACK_FACE_REAR, status=DEVICE_STATUS_FAILED, cluster=clusters[2]),
)
@@ -1328,6 +1329,12 @@ class DeviceTestCase(TestCase):
# params = {'device_bays': 'false'}
# self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_local_context_data(self):
params = {'local_context_data': 'true'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
params = {'local_context_data': 'false'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class ConsolePortTestCase(TestCase):
queryset = ConsolePort.objects.all()
@@ -2121,6 +2128,12 @@ class CableTestCase(TestCase):
)
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 = (
Rack(name='Rack 1', site=sites[0]),
Rack(name='Rack 2', site=sites[1]),
@@ -2133,9 +2146,9 @@ class CableTestCase(TestCase):
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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 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),
@@ -2216,6 +2229,13 @@ class CableTestCase(TestCase):
params = {'site': [site[0].slug, site[1].slug]}
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):
queryset = PowerPanel.objects.all()

View File

@@ -325,9 +325,12 @@ class CableTestCase(TestCase):
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.assertIsNone(self.cable.pk)
self.assertNotEqual(str(self.cable), '#None')
interface1 = Interface.objects.get(pk=self.interface1.pk)
self.assertIsNone(interface1.cable)
interface2 = Interface.objects.get(pk=self.interface2.pk)

View File

@@ -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)
else:
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)
)

View File

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

View File

@@ -15,7 +15,7 @@ $('button.toggle-ips').click(function() {
$('input.interface-filter').on('input', function() {
var filter = new RegExp(this.value);
for (interface of $(this).closest('form').find('tbody > tr')) {
for (interface of $(this).closest('div.panel').find('tbody > tr')) {
// Slice off 'interface_' at the start of the ID
if (filter && filter.test(interface.id.slice(10))) {
// Match the toggle in case the filter now matches the interface

View File

@@ -112,7 +112,9 @@
{% if utilization %}
<td>
{{ 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>
{% else %}
<td class="text-muted">N/A</td>

View File

@@ -2,7 +2,7 @@ import django_filters
from django.db.models import Q
from dcim.models import DeviceRole, Interface, Platform, Region, Site
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet, LocalConfigContextFilter
from tenancy.filtersets import TenancyFilterSet
from utilities.filters import (
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
@@ -99,7 +99,7 @@ class ClusterFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
)
class VirtualMachineFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
class VirtualMachineFilter(LocalConfigContextFilter, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'

View File

@@ -203,7 +203,7 @@ class VirtualMachineTestCase(TestCase):
DeviceRole.objects.bulk_create(roles)
vms = (
VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], platform=platforms[0], role=roles[0], status=DEVICE_STATUS_ACTIVE, vcpus=1, memory=1, disk=1),
VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], platform=platforms[0], role=roles[0], status=DEVICE_STATUS_ACTIVE, vcpus=1, memory=1, disk=1, local_context_data={"foo": 123}),
VirtualMachine(name='Virtual Machine 2', cluster=clusters[1], platform=platforms[1], role=roles[1], status=DEVICE_STATUS_STAGED, vcpus=2, memory=2, disk=2),
VirtualMachine(name='Virtual Machine 3', cluster=clusters[2], platform=platforms[2], role=roles[2], status=DEVICE_STATUS_OFFLINE, vcpus=3, memory=3, disk=3),
)
@@ -300,6 +300,12 @@ class VirtualMachineTestCase(TestCase):
params = {'mac_address': ['00-00-00-00-00-01', '00-00-00-00-00-02']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_local_context_data(self):
params = {'local_context_data': 'true'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
params = {'local_context_data': 'false'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class InterfaceTestCase(TestCase):
queryset = Interface.objects.all()