mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Merge branch 'develop' into 3525
This commit is contained in:
commit
d33e10b4ce
@ -69,6 +69,14 @@ If the new field will be included in the object list view, add a column to the m
|
|||||||
|
|
||||||
Edit the object's view template to display the new field. There may also be a custom add/edit form template that needs to be updated.
|
Edit the object's view template to display the new field. There may also be a custom add/edit form template that needs to be updated.
|
||||||
|
|
||||||
### 11. Adjust API and model tests
|
### 11. Create/extend test cases
|
||||||
|
|
||||||
Extend the model and/or API tests to verify that the new field and any accompanying validation logic perform as expected. This is especially important for relational fields.
|
Create or extend the relevant test cases to verify that the new field and any accompanying validation logic perform as expected. This is especially important for relational fields. NetBox incorporates various test suites, including:
|
||||||
|
|
||||||
|
* API serializer/view tests
|
||||||
|
* Filter tests
|
||||||
|
* Form tests
|
||||||
|
* Model tests
|
||||||
|
* View tests
|
||||||
|
|
||||||
|
Be diligent to ensure all of the relevant test suites are adapted or extended as necessary to test any new functionality.
|
||||||
|
@ -1,20 +1,34 @@
|
|||||||
# v2.6.12 (FUTURE)
|
# v2.6.13 (FUTURE)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
|
||||||
* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger
|
* [#3525](https://github.com/netbox-community/netbox/issues/3525) - Enable IP address filtering with multiple address terms
|
||||||
* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering the link
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#3914](https://github.com/netbox-community/netbox/issues/3914) - Fix interface filter field when unauthenticated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# v2.6.12 (2020-01-13)
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
* [#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
|
* [#2598](https://github.com/netbox-community/netbox/issues/2598) - Toggle the display of child prefixes/IP addresses
|
||||||
* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address
|
* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address to interfaces
|
||||||
* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Add filter field for device interfaces
|
* [#3021](https://github.com/netbox-community/netbox/issues/3021) - Add `tenant` filter field for cables
|
||||||
* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations
|
* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Enable filtering of interfaces by name on the device view
|
||||||
* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate the circuits at the provider details view
|
* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations view
|
||||||
* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace
|
* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate assigned circuits at the provider details view
|
||||||
* [#3525](https://github.com/netbox-community/netbox/issues/3525) - Enable ipaddress filtering with multiple address terms
|
* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total path length to cable trace
|
||||||
* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Add word expansion during interface creation
|
* [#3491](https://github.com/netbox-community/netbox/issues/3491) - Include content of response on webhook error
|
||||||
* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Search by DNS name when assigning IP address
|
* [#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
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
@ -22,12 +36,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
|
* [#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 of an address
|
* [#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) - Fixed min/max to ASN input field at the site creation page
|
* [#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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -739,7 +739,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')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -2804,6 +2804,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={
|
||||||
@ -2857,6 +2858,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',
|
||||||
@ -2888,6 +2890,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={
|
||||||
@ -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(
|
rack_id = FilterChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
label='Rack',
|
label='Rack',
|
||||||
|
@ -3027,15 +3027,14 @@ class Cable(ChangeLoggedModel):
|
|||||||
('termination_b_type', 'termination_b_id'),
|
('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):
|
def __str__(self):
|
||||||
if self.label:
|
return self.label or '#{}'.format(self._pk)
|
||||||
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
|
|
||||||
|
|
||||||
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),
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
@ -1100,7 +1101,7 @@ class DeviceTestCase(TestCase):
|
|||||||
Cluster.objects.bulk_create(clusters)
|
Cluster.objects.bulk_create(clusters)
|
||||||
|
|
||||||
devices = (
|
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 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]),
|
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'}
|
# params = {'device_bays': 'false'}
|
||||||
# self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
# 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):
|
class ConsolePortTestCase(TestCase):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
@ -2121,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]),
|
||||||
@ -2133,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 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),
|
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),
|
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),
|
||||||
@ -2216,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()
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
|
@ -15,7 +15,7 @@ $('button.toggle-ips').click(function() {
|
|||||||
$('input.interface-filter').on('input', function() {
|
$('input.interface-filter').on('input', function() {
|
||||||
var filter = new RegExp(this.value);
|
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
|
// Slice off 'interface_' at the start of the ID
|
||||||
if (filter && filter.test(interface.id.slice(10))) {
|
if (filter && filter.test(interface.id.slice(10))) {
|
||||||
// Match the toggle in case the filter now matches the interface
|
// Match the toggle in case the filter now matches the interface
|
||||||
|
@ -112,7 +112,9 @@
|
|||||||
{% if utilization %}
|
{% if utilization %}
|
||||||
<td>
|
<td>
|
||||||
{{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA
|
{{ utilization.allocated }}VA / {{ powerfeed.available_power }}VA
|
||||||
|
{% if powerfeed.available_power > 0 %}
|
||||||
{% utilization_graph utilization.allocated|percentage:powerfeed.available_power %}
|
{% 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>
|
||||||
|
@ -2,7 +2,7 @@ import django_filters
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dcim.models import DeviceRole, Interface, Platform, Region, Site
|
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 tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
MultiValueMACAddressFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
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(
|
id__in = NumericInFilter(
|
||||||
field_name='id',
|
field_name='id',
|
||||||
lookup_expr='in'
|
lookup_expr='in'
|
||||||
|
@ -203,7 +203,7 @@ class VirtualMachineTestCase(TestCase):
|
|||||||
DeviceRole.objects.bulk_create(roles)
|
DeviceRole.objects.bulk_create(roles)
|
||||||
|
|
||||||
vms = (
|
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 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),
|
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']}
|
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)
|
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):
|
class InterfaceTestCase(TestCase):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
|
Loading…
Reference in New Issue
Block a user