diff --git a/docs/additional-features/napalm.md b/docs/additional-features/napalm.md
new file mode 100644
index 000000000..c8e8b8b3a
--- /dev/null
+++ b/docs/additional-features/napalm.md
@@ -0,0 +1,65 @@
+# NAPALM
+
+NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to fetch live data from devices and return it to a requester via its REST API.
+
+!!! info
+ To enable the integration, the NAPALM library must be installed. See [installation steps](../../installation/2-netbox/#napalm-automation-optional) for more information.
+
+```
+GET /api/dcim/devices/1/napalm/?method=get_environment
+
+{
+ "get_environment": {
+ ...
+ }
+}
+```
+
+## Authentication
+
+By default, the [`NAPALM_USERNAME`](../../configuration/optional-settings/#napalm_username) and [`NAPALM_PASSWORD`](../../configuration/optional-settings/#napalm_password) are used for NAPALM authentication. They can be overridden for an individual API call through the `X-NAPALM-Username` and `X-NAPALM-Password` headers.
+
+```
+$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \
+-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \
+-H "Content-Type: application/json" \
+-H "Accept: application/json; indent=4" \
+-H "X-NAPALM-Username: foo" \
+-H "X-NAPALM-Password: bar"
+```
+
+## Method Support
+
+The list of supported NAPALM methods depends on the [NAPALM driver](https://napalm.readthedocs.io/en/latest/support/index.html#general-support-matrix) configured for the platform of a device. NetBox only supports [get](https://napalm.readthedocs.io/en/latest/support/index.html#getters-support-matrix) methods.
+
+## Multiple Methods
+
+More than one method in an API call can be invoked by adding multiple `method` parameters. For example:
+
+```
+GET /api/dcim/devices/1/napalm/?method=get_ntp_servers&method=get_ntp_peers
+
+{
+ "get_ntp_servers": {
+ ...
+ },
+ "get_ntp_peers": {
+ ...
+ }
+}
+```
+
+## Optional Arguments
+
+The behavior of NAPALM drivers can be adjusted according to the [optional arguments](https://napalm.readthedocs.io/en/latest/support/index.html#optional-arguments). NetBox exposes those arguments using headers prefixed with `X-NAPALM-`.
+
+
+For instance, the SSH port is changed to 2222 in this API call:
+
+```
+$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \
+-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \
+-H "Content-Type: application/json" \
+-H "Accept: application/json; indent=4" \
+-H "X-NAPALM-port: 2222"
+```
diff --git a/docs/release-notes/version-2.6.md b/docs/release-notes/version-2.6.md
index 2bf55d857..ee6ea2e4d 100644
--- a/docs/release-notes/version-2.6.md
+++ b/docs/release-notes/version-2.6.md
@@ -1,3 +1,29 @@
+# v2.6.12 (FUTURE)
+
+## 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
+* [#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
+* [#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
+* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total length to cable trace
+* [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
+
+## Bug Fixes
+
+* [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface
+* [#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
+* [#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
+* [#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
+
+---
+
# v2.6.11 (2020-01-03)
## Bug Fixes
diff --git a/mkdocs.yml b/mkdocs.yml
index cc44921b6..b493a799b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -35,6 +35,7 @@ pages:
- Custom Scripts: 'additional-features/custom-scripts.md'
- Export Templates: 'additional-features/export-templates.md'
- Graphs: 'additional-features/graphs.md'
+ - NAPALM: 'additional-features/napalm.md'
- Prometheus Metrics: 'additional-features/prometheus-metrics.md'
- Reports: 'additional-features/reports.md'
- Tags: 'additional-features/tags.md'
diff --git a/netbox/circuits/filters.py b/netbox/circuits/filters.py
index 0fff6fda0..cb439082d 100644
--- a/netbox/circuits/filters.py
+++ b/netbox/circuits/filters.py
@@ -8,6 +8,13 @@ from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilte
from .choices import *
from .models import Circuit, CircuitTermination, CircuitType, Provider
+__all__ = (
+ 'CircuitFilter',
+ 'CircuitTerminationFilter',
+ 'CircuitTypeFilter',
+ 'ProviderFilter',
+)
+
class ProviderFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter(
diff --git a/netbox/circuits/tests/test_filters.py b/netbox/circuits/tests/test_filters.py
new file mode 100644
index 000000000..a715ad757
--- /dev/null
+++ b/netbox/circuits/tests/test_filters.py
@@ -0,0 +1,287 @@
+from django.test import TestCase
+
+from circuits.constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_OFFLINE, CIRCUIT_STATUS_PLANNED
+from circuits.filters import *
+from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
+from dcim.models import Region, Site
+
+
+class ProviderTestCase(TestCase):
+ queryset = Provider.objects.all()
+ filterset = ProviderFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ providers = (
+ Provider(name='Provider 1', slug='provider-1', asn=65001, account='1234'),
+ Provider(name='Provider 2', slug='provider-2', asn=65002, account='2345'),
+ Provider(name='Provider 3', slug='provider-3', asn=65003, account='3456'),
+ Provider(name='Provider 4', slug='provider-4', asn=65004, account='4567'),
+ Provider(name='Provider 5', slug='provider-5', asn=65005, account='5678'),
+ )
+ Provider.objects.bulk_create(providers)
+
+ regions = (
+ Region(name='Test Region 1', slug='test-region-1'),
+ Region(name='Test Region 2', slug='test-region-2'),
+ )
+ # Can't use bulk_create for models with MPTT fields
+ for r in regions:
+ r.save()
+
+ sites = (
+ Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
+ Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
+ )
+ Site.objects.bulk_create(sites)
+
+ circuit_types = (
+ CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'),
+ CircuitType(name='Test Circuit Type 2', slug='test-circuit-type-2'),
+ )
+ CircuitType.objects.bulk_create(circuit_types)
+
+ circuits = (
+ Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'),
+ Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 1'),
+ )
+ Circuit.objects.bulk_create(circuits)
+
+ CircuitTermination.objects.bulk_create((
+ CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000),
+ CircuitTermination(circuit=circuits[1], site=sites[0], term_side='A', port_speed=1000),
+ ))
+
+ def test_name(self):
+ params = {'name': ['Provider 1', 'Provider 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['provider-1', 'provider-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_asn(self):
+ params = {'asn': ['65001', '65002']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_account(self):
+ params = {'account': ['1234', '2345']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class CircuitTypeTestCase(TestCase):
+ queryset = CircuitType.objects.all()
+ filterset = CircuitTypeFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ CircuitType.objects.bulk_create((
+ CircuitType(name='Circuit Type 1', slug='circuit-type-1'),
+ CircuitType(name='Circuit Type 2', slug='circuit-type-2'),
+ CircuitType(name='Circuit Type 3', slug='circuit-type-3'),
+ ))
+
+ def test_id(self):
+ params = {'id': [self.queryset.first().pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_name(self):
+ params = {'name': ['Circuit Type 1']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_slug(self):
+ params = {'slug': ['circuit-type-1']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class CircuitTestCase(TestCase):
+ queryset = Circuit.objects.all()
+ filterset = CircuitFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Test Region 1', slug='test-region-1'),
+ Region(name='Test Region 2', slug='test-region-2'),
+ Region(name='Test Region 3', slug='test-region-3'),
+ )
+ # Can't use bulk_create for models with MPTT fields
+ for r in regions:
+ r.save()
+
+ sites = (
+ Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
+ Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
+ Site(name='Test Site 3', slug='test-site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ circuit_types = (
+ CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'),
+ CircuitType(name='Test Circuit Type 2', slug='test-circuit-type-2'),
+ )
+ CircuitType.objects.bulk_create(circuit_types)
+
+ providers = (
+ Provider(name='Provider 1', slug='provider-1'),
+ Provider(name='Provider 2', slug='provider-2'),
+ )
+ Provider.objects.bulk_create(providers)
+
+ circuits = (
+ Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', commit_rate=1000, status=CIRCUIT_STATUS_ACTIVE),
+ Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', commit_rate=2000, status=CIRCUIT_STATUS_ACTIVE),
+ Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', commit_rate=3000, status=CIRCUIT_STATUS_PLANNED),
+ Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 4', install_date='2020-01-04', commit_rate=4000, status=CIRCUIT_STATUS_PLANNED),
+ Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', commit_rate=5000, status=CIRCUIT_STATUS_OFFLINE),
+ Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 6', install_date='2020-01-06', commit_rate=6000, status=CIRCUIT_STATUS_OFFLINE),
+ )
+ Circuit.objects.bulk_create(circuits)
+
+ circuit_terminations = ((
+ CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000),
+ CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A', port_speed=1000),
+ CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=1000),
+ ))
+ CircuitTermination.objects.bulk_create(circuit_terminations)
+
+ def test_cid(self):
+ params = {'cid': ['Test Circuit 1', 'Test Circuit 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_install_date(self):
+ params = {'install_date': ['2020-01-01', '2020-01-02']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_commit_rate(self):
+ params = {'commit_rate': ['1000', '2000']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_provider(self):
+ provider = Provider.objects.first()
+ params = {'provider_id': [provider.pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+ params = {'provider': [provider.slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_type(self):
+ circuit_type = CircuitType.objects.first()
+ params = {'type_id': [circuit_type.pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+ params = {'type': [circuit_type.slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_status(self):
+ params = {'status': [CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_PLANNED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class CircuitTerminationTestCase(TestCase):
+ queryset = CircuitTermination.objects.all()
+ filterset = CircuitTerminationFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ sites = (
+ Site(name='Test Site 1', slug='test-site-1'),
+ Site(name='Test Site 2', slug='test-site-2'),
+ Site(name='Test Site 3', slug='test-site-3'),
+ )
+ Site.objects.bulk_create(sites)
+
+ circuit_types = (
+ CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'),
+ )
+ CircuitType.objects.bulk_create(circuit_types)
+
+ providers = (
+ Provider(name='Provider 1', slug='provider-1'),
+ )
+ Provider.objects.bulk_create(providers)
+
+ circuits = (
+ Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'),
+ Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 2'),
+ Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 3'),
+ )
+ Circuit.objects.bulk_create(circuits)
+
+ circuit_terminations = ((
+ CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC'),
+ CircuitTermination(circuit=circuits[0], site=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF'),
+ CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'),
+ CircuitTermination(circuit=circuits[1], site=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'),
+ CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'),
+ CircuitTermination(circuit=circuits[2], site=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'),
+ ))
+ CircuitTermination.objects.bulk_create(circuit_terminations)
+
+ def test_term_side(self):
+ params = {'term_side': 'A'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_port_speed(self):
+ params = {'port_speed': ['1000', '2000']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_upstream_speed(self):
+ params = {'upstream_speed': ['1000', '2000']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_xconnect_id(self):
+ params = {'xconnect_id': ['ABC', 'DEF']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_circuit_id(self):
+ circuits = Circuit.objects.all()[:2]
+ params = {'circuit_id': [circuits[0].pk, circuits[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py
index 13c8a66df..664eb88f1 100644
--- a/netbox/dcim/api/serializers.py
+++ b/netbox/dcim/api/serializers.py
@@ -412,6 +412,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
return obj.get_config_context()
+class DeviceNAPALMSerializer(serializers.Serializer):
+ method = serializers.DictField()
+
+
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
type = ChoiceField(
diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py
index 891bf1b50..68fd62eb2 100644
--- a/netbox/dcim/api/views.py
+++ b/netbox/dcim/api/views.py
@@ -397,6 +397,17 @@ class DeviceViewSet(CustomFieldModelViewSet):
return Response(serializer.data)
+ @swagger_auto_schema(
+ manual_parameters=[
+ Parameter(
+ name='method',
+ in_='query',
+ required=True,
+ type=openapi.TYPE_STRING
+ )
+ ],
+ responses={'200': serializers.DeviceNAPALMSerializer}
+ )
@action(detail=True, url_path='napalm')
def napalm(self, request, pk):
"""
@@ -435,13 +446,29 @@ class DeviceViewSet(CustomFieldModelViewSet):
napalm_methods = request.GET.getlist('method')
response = OrderedDict([(m, None) for m in napalm_methods])
ip_address = str(device.primary_ip.address.ip)
+ username = settings.NAPALM_USERNAME
+ password = settings.NAPALM_PASSWORD
optional_args = settings.NAPALM_ARGS.copy()
if device.platform.napalm_args is not None:
optional_args.update(device.platform.napalm_args)
+
+ # Update NAPALM parameters according to the request headers
+ for header in request.headers:
+ if header[:9].lower() != 'x-napalm-':
+ continue
+
+ key = header[9:]
+ if key.lower() == 'username':
+ username = request.headers[header]
+ elif key.lower() == 'password':
+ password = request.headers[header]
+ elif key:
+ optional_args[key.lower()] = request.headers[header]
+
d = driver(
hostname=ip_address,
- username=settings.NAPALM_USERNAME,
- password=settings.NAPALM_PASSWORD,
+ username=username,
+ password=password,
timeout=settings.NAPALM_TIMEOUT,
optional_args=optional_args
)
diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py
index 969a61d01..98ecf2fb4 100644
--- a/netbox/dcim/filters.py
+++ b/netbox/dcim/filters.py
@@ -22,6 +22,45 @@ from .models import (
)
+__all__ = (
+ 'CableFilter',
+ 'ConsoleConnectionFilter',
+ 'ConsolePortFilter',
+ 'ConsolePortTemplateFilter',
+ 'ConsoleServerPortFilter',
+ 'ConsoleServerPortTemplateFilter',
+ 'DeviceBayFilter',
+ 'DeviceBayTemplateFilter',
+ 'DeviceFilter',
+ 'DeviceRoleFilter',
+ 'DeviceTypeFilter',
+ 'FrontPortFilter',
+ 'FrontPortTemplateFilter',
+ 'InterfaceConnectionFilter',
+ 'InterfaceFilter',
+ 'InterfaceTemplateFilter',
+ 'InventoryItemFilter',
+ 'ManufacturerFilter',
+ 'PlatformFilter',
+ 'PowerConnectionFilter',
+ 'PowerFeedFilter',
+ 'PowerOutletFilter',
+ 'PowerOutletTemplateFilter',
+ 'PowerPanelFilter',
+ 'PowerPortFilter',
+ 'PowerPortTemplateFilter',
+ 'RackFilter',
+ 'RackGroupFilter',
+ 'RackReservationFilter',
+ 'RackRoleFilter',
+ 'RearPortFilter',
+ 'RearPortTemplateFilter',
+ 'RegionFilter',
+ 'SiteFilter',
+ 'VirtualChassisFilter',
+)
+
+
class RegionFilter(NameSlugSearchFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
@@ -667,7 +706,8 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
queryset=Device.objects.all(),
label='Device (ID)',
)
- device = django_filters.ModelChoiceFilter(
+ device = django_filters.ModelMultipleChoiceFilter(
+ field_name='device__name',
queryset=Device.objects.all(),
to_field_name='name',
label='Device (name)',
diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py
index 019511eaa..bd59a614f 100644
--- a/netbox/dcim/forms.py
+++ b/netbox/dcim/forms.py
@@ -102,6 +102,17 @@ class InterfaceCommonForm:
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
self.cleaned_data['tagged_vlans'] = []
+ # Validate tagged VLANs; must be a global VLAN or in the same site
+ elif self.cleaned_data['mode'] == IFACE_MODE_TAGGED:
+ valid_sites = [None, self.cleaned_data['device'].site]
+ invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
+
+ if invalid_vlans:
+ raise forms.ValidationError({
+ 'tagged_vlans': "The tagged VLANs ({}) must belong to the same site as the interface's parent "
+ "device/VM, or they must be global".format(', '.join(invalid_vlans))
+ })
+
class BulkRenameForm(forms.Form):
"""
@@ -728,6 +739,34 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
)
+#
+# Rack elevations
+#
+
+class RackElevationFilterForm(RackFilterForm):
+ field_order = ['q', 'region', 'site', 'group_id', 'id', 'status', 'role', 'tenant_group', 'tenant']
+ id = ChainedModelChoiceField(
+ queryset=Rack.objects.all(),
+ label='Rack',
+ chains=(
+ ('site', 'site'),
+ ('group_id', 'group_id'),
+ ),
+ required=False,
+ widget=APISelectMultiple(
+ api_url='/api/dcim/racks/',
+ display_field='display_name',
+ )
+ )
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Filter the rack field based on the site and group
+ self.fields['site'].widget.add_filter_for('id', 'site')
+ self.fields['group_id'].widget.add_filter_for('id', 'group_id')
+
+
#
# Rack reservations
#
@@ -2546,36 +2585,6 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
type=InterfaceTypeChoices.TYPE_LAG
)
- # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
- vlan_choices = []
- global_vlans = VLAN.objects.filter(site=None, group=None)
- vlan_choices.append(
- ('Global', [(vlan.pk, vlan) for vlan in global_vlans])
- )
- for group in VLANGroup.objects.filter(site=None):
- global_group_vlans = VLAN.objects.filter(group=group)
- vlan_choices.append(
- (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
- )
-
- site = getattr(self.instance.parent, 'site', None)
- if site is not None:
-
- # Add non-grouped site VLANs
- site_vlans = VLAN.objects.filter(site=site, group=None)
- vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
-
- # Add grouped site VLANs
- for group in VLANGroup.objects.filter(site=site):
- site_group_vlans = VLAN.objects.filter(group=group)
- vlan_choices.append((
- '{} / {}'.format(group.site.name, group.name),
- [(vlan.pk, vlan) for vlan in site_group_vlans]
- ))
-
- self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
- self.fields['tagged_vlans'].choices = vlan_choices
-
class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
name_pattern = ExpandableNameField(
@@ -2657,36 +2666,6 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
else:
self.fields['lag'].queryset = Interface.objects.none()
- # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
- vlan_choices = []
- global_vlans = VLAN.objects.filter(site=None, group=None)
- vlan_choices.append(
- ('Global', [(vlan.pk, vlan) for vlan in global_vlans])
- )
- for group in VLANGroup.objects.filter(site=None):
- global_group_vlans = VLAN.objects.filter(group=group)
- vlan_choices.append(
- (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
- )
-
- site = getattr(self.parent, 'site', None)
- if site is not None:
-
- # Add non-grouped site VLANs
- site_vlans = VLAN.objects.filter(site=site, group=None)
- vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
-
- # Add grouped site VLANs
- for group in VLANGroup.objects.filter(site=site):
- site_group_vlans = VLAN.objects.filter(group=group)
- vlan_choices.append((
- '{} / {}'.format(group.site.name, group.name),
- [(vlan.pk, vlan) for vlan in site_group_vlans]
- ))
-
- self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
- self.fields['tagged_vlans'].choices = vlan_choices
-
class InterfaceCSVForm(forms.ModelForm):
device = FlexibleModelChoiceField(
@@ -2836,36 +2815,6 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
else:
self.fields['lag'].choices = []
- # Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
- vlan_choices = []
- global_vlans = VLAN.objects.filter(site=None, group=None)
- vlan_choices.append(
- ('Global', [(vlan.pk, vlan) for vlan in global_vlans])
- )
- for group in VLANGroup.objects.filter(site=None):
- global_group_vlans = VLAN.objects.filter(group=group)
- vlan_choices.append(
- (group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
- )
- if self.parent_obj is not None:
- site = getattr(self.parent_obj, 'site', None)
- if site is not None:
-
- # Add non-grouped site VLANs
- site_vlans = VLAN.objects.filter(site=site, group=None)
- vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
-
- # Add grouped site VLANs
- for group in VLANGroup.objects.filter(site=site):
- site_group_vlans = VLAN.objects.filter(group=group)
- vlan_choices.append((
- '{} / {}'.format(group.site.name, group.name),
- [(vlan.pk, vlan) for vlan in site_group_vlans]
- ))
-
- self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
- self.fields['tagged_vlans'].choices = vlan_choices
-
class InterfaceBulkRenameForm(BulkRenameForm):
pk = forms.ModelMultipleChoiceField(
diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py
index 8796ea1ea..a7d865167 100644
--- a/netbox/dcim/models.py
+++ b/netbox/dcim/models.py
@@ -2970,260 +2970,6 @@ class VirtualChassis(ChangeLoggedModel):
)
-#
-# Cables
-#
-
-class Cable(ChangeLoggedModel):
- """
- A physical connection between two endpoints.
- """
- termination_a_type = models.ForeignKey(
- to=ContentType,
- limit_choices_to={'model__in': CABLE_TERMINATION_TYPES},
- on_delete=models.PROTECT,
- related_name='+'
- )
- termination_a_id = models.PositiveIntegerField()
- termination_a = GenericForeignKey(
- ct_field='termination_a_type',
- fk_field='termination_a_id'
- )
- termination_b_type = models.ForeignKey(
- to=ContentType,
- limit_choices_to={'model__in': CABLE_TERMINATION_TYPES},
- on_delete=models.PROTECT,
- related_name='+'
- )
- termination_b_id = models.PositiveIntegerField()
- termination_b = GenericForeignKey(
- ct_field='termination_b_type',
- fk_field='termination_b_id'
- )
- type = models.CharField(
- max_length=50,
- choices=CableTypeChoices,
- blank=True
- )
- status = models.CharField(
- max_length=50,
- choices=CableStatusChoices,
- default=CableStatusChoices.STATUS_CONNECTED
- )
- label = models.CharField(
- max_length=100,
- blank=True
- )
- color = ColorField(
- blank=True
- )
- length = models.PositiveSmallIntegerField(
- blank=True,
- null=True
- )
- length_unit = models.CharField(
- max_length=50,
- choices=CableLengthUnitChoices,
- blank=True,
- )
- # Stores the normalized length (in meters) for database ordering
- _abs_length = models.DecimalField(
- max_digits=10,
- decimal_places=4,
- blank=True,
- null=True
- )
- # Cache the associated device (where applicable) for the A and B terminations. This enables filtering of Cables by
- # their associated Devices.
- _termination_a_device = models.ForeignKey(
- to=Device,
- on_delete=models.CASCADE,
- related_name='+',
- blank=True,
- null=True
- )
- _termination_b_device = models.ForeignKey(
- to=Device,
- on_delete=models.CASCADE,
- related_name='+',
- blank=True,
- null=True
- )
-
- csv_headers = [
- 'termination_a_type', 'termination_a_id', 'termination_b_type', 'termination_b_id', 'type', 'status', 'label',
- 'color', 'length', 'length_unit',
- ]
-
- class Meta:
- ordering = ['pk']
- unique_together = (
- ('termination_a_type', 'termination_a_id'),
- ('termination_b_type', 'termination_b_id'),
- )
-
- 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
-
- def get_absolute_url(self):
- return reverse('dcim:cable', args=[self.pk])
-
- def clean(self):
-
- # Validate that termination A exists
- if not hasattr(self, 'termination_a_type'):
- raise ValidationError('Termination A type has not been specified')
- try:
- self.termination_a_type.model_class().objects.get(pk=self.termination_a_id)
- except ObjectDoesNotExist:
- raise ValidationError({
- 'termination_a': 'Invalid ID for type {}'.format(self.termination_a_type)
- })
-
- # Validate that termination B exists
- if not hasattr(self, 'termination_b_type'):
- raise ValidationError('Termination B type has not been specified')
- try:
- self.termination_b_type.model_class().objects.get(pk=self.termination_b_id)
- except ObjectDoesNotExist:
- raise ValidationError({
- 'termination_b': 'Invalid ID for type {}'.format(self.termination_b_type)
- })
-
- type_a = self.termination_a_type.model
- type_b = self.termination_b_type.model
-
- # Validate interface types
- if type_a == 'interface' and self.termination_a.type in NONCONNECTABLE_IFACE_TYPES:
- raise ValidationError({
- 'termination_a_id': 'Cables cannot be terminated to {} interfaces'.format(
- self.termination_a.get_type_display()
- )
- })
- if type_b == 'interface' and self.termination_b.type in NONCONNECTABLE_IFACE_TYPES:
- raise ValidationError({
- 'termination_b_id': 'Cables cannot be terminated to {} interfaces'.format(
- self.termination_b.get_type_display()
- )
- })
-
- # Check that termination types are compatible
- if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
- raise ValidationError("Incompatible termination types: {} and {}".format(
- self.termination_a_type, self.termination_b_type
- ))
-
- # A component with multiple positions must be connected to a component with an equal number of positions
- term_a_positions = getattr(self.termination_a, 'positions', 1)
- term_b_positions = getattr(self.termination_b, 'positions', 1)
- if term_a_positions != term_b_positions:
- raise ValidationError(
- "{} has {} positions and {} has {}. Both terminations must have the same number of positions.".format(
- self.termination_a, term_a_positions, self.termination_b, term_b_positions
- )
- )
-
- # A termination point cannot be connected to itself
- if self.termination_a == self.termination_b:
- raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type))
-
- # A front port cannot be connected to its corresponding rear port
- if (
- type_a in ['frontport', 'rearport'] and
- type_b in ['frontport', 'rearport'] and
- (
- getattr(self.termination_a, 'rear_port', None) == self.termination_b or
- getattr(self.termination_b, 'rear_port', None) == self.termination_a
- )
- ):
- raise ValidationError("A front port cannot be connected to it corresponding rear port")
-
- # Check for an existing Cable connected to either termination object
- if self.termination_a.cable not in (None, self):
- raise ValidationError("{} already has a cable attached (#{})".format(
- self.termination_a, self.termination_a.cable_id
- ))
- if self.termination_b.cable not in (None, self):
- raise ValidationError("{} already has a cable attached (#{})".format(
- self.termination_b, self.termination_b.cable_id
- ))
-
- # Validate length and length_unit
- if self.length is not None and not self.length_unit:
- raise ValidationError("Must specify a unit when setting a cable length")
- elif self.length is None:
- self.length_unit = ''
-
- def save(self, *args, **kwargs):
-
- # Store the given length (if any) in meters for use in database ordering
- if self.length and self.length_unit:
- self._abs_length = to_meters(self.length, self.length_unit)
-
- # Store the parent Device for the A and B terminations (if applicable) to enable filtering
- if hasattr(self.termination_a, 'device'):
- self._termination_a_device = self.termination_a.device
- if hasattr(self.termination_b, 'device'):
- self._termination_b_device = self.termination_b.device
-
- super().save(*args, **kwargs)
-
- def to_csv(self):
- return (
- '{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model),
- self.termination_a_id,
- '{}.{}'.format(self.termination_b_type.app_label, self.termination_b_type.model),
- self.termination_b_id,
- self.get_type_display(),
- self.get_status_display(),
- self.label,
- self.color,
- self.length,
- self.length_unit,
- )
-
- def get_status_class(self):
- return 'success' if self.status else 'info'
-
- def get_compatible_types(self):
- """
- Return all termination types compatible with termination A.
- """
- if self.termination_a is None:
- return
- return COMPATIBLE_TERMINATION_TYPES[self.termination_a._meta.model_name]
-
- def get_path_endpoints(self):
- """
- Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be
- None.
- """
- a_path = self.termination_b.trace()
- b_path = self.termination_a.trace()
-
- # Determine overall path status (connected or planned)
- if self.status == CableStatusChoices.STATUS_PLANNED:
- path_status = CONNECTION_STATUS_PLANNED
- else:
- path_status = CONNECTION_STATUS_CONNECTED
- for segment in a_path[1:] + b_path[1:]:
- if segment[1] is None or segment[1].status == CableStatusChoices.STATUS_PLANNED:
- path_status = CONNECTION_STATUS_PLANNED
- break
-
- a_endpoint = a_path[-1][2]
- b_endpoint = b_path[-1][2]
-
- return a_endpoint, b_endpoint, path_status
-
-
#
# Power
#
@@ -3423,3 +3169,259 @@ class PowerFeed(ChangeLoggedModel, CableTermination, CustomFieldModel):
def get_status_class(self):
return self.STATUS_CLASS_MAP.get(self.status)
+
+
+#
+# Cables
+#
+
+class Cable(ChangeLoggedModel):
+ """
+ A physical connection between two endpoints.
+ """
+ termination_a_type = models.ForeignKey(
+ to=ContentType,
+ limit_choices_to={'model__in': CABLE_TERMINATION_TYPES},
+ on_delete=models.PROTECT,
+ related_name='+'
+ )
+ termination_a_id = models.PositiveIntegerField()
+ termination_a = GenericForeignKey(
+ ct_field='termination_a_type',
+ fk_field='termination_a_id'
+ )
+ termination_b_type = models.ForeignKey(
+ to=ContentType,
+ limit_choices_to={'model__in': CABLE_TERMINATION_TYPES},
+ on_delete=models.PROTECT,
+ related_name='+'
+ )
+ termination_b_id = models.PositiveIntegerField()
+ termination_b = GenericForeignKey(
+ ct_field='termination_b_type',
+ fk_field='termination_b_id'
+ )
+ type = models.CharField(
+ max_length=50,
+ choices=CableTypeChoices,
+ blank=True
+ )
+ status = models.CharField(
+ max_length=50,
+ choices=CableStatusChoices,
+ default=CableStatusChoices.STATUS_CONNECTED
+ )
+ label = models.CharField(
+ max_length=100,
+ blank=True
+ )
+ color = ColorField(
+ blank=True
+ )
+ length = models.PositiveSmallIntegerField(
+ blank=True,
+ null=True
+ )
+ length_unit = models.CharField(
+ max_length=50,
+ choices=CableLengthUnitChoices,
+ blank=True,
+ )
+ # Stores the normalized length (in meters) for database ordering
+ _abs_length = models.DecimalField(
+ max_digits=10,
+ decimal_places=4,
+ blank=True,
+ null=True
+ )
+ # Cache the associated device (where applicable) for the A and B terminations. This enables filtering of Cables by
+ # their associated Devices.
+ _termination_a_device = models.ForeignKey(
+ to=Device,
+ on_delete=models.CASCADE,
+ related_name='+',
+ blank=True,
+ null=True
+ )
+ _termination_b_device = models.ForeignKey(
+ to=Device,
+ on_delete=models.CASCADE,
+ related_name='+',
+ blank=True,
+ null=True
+ )
+
+ csv_headers = [
+ 'termination_a_type', 'termination_a_id', 'termination_b_type', 'termination_b_id', 'type', 'status', 'label',
+ 'color', 'length', 'length_unit',
+ ]
+
+ class Meta:
+ ordering = ['pk']
+ unique_together = (
+ ('termination_a_type', 'termination_a_id'),
+ ('termination_b_type', 'termination_b_id'),
+ )
+
+ 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
+
+ def get_absolute_url(self):
+ return reverse('dcim:cable', args=[self.pk])
+
+ def clean(self):
+
+ # Validate that termination A exists
+ if not hasattr(self, 'termination_a_type'):
+ raise ValidationError('Termination A type has not been specified')
+ try:
+ self.termination_a_type.model_class().objects.get(pk=self.termination_a_id)
+ except ObjectDoesNotExist:
+ raise ValidationError({
+ 'termination_a': 'Invalid ID for type {}'.format(self.termination_a_type)
+ })
+
+ # Validate that termination B exists
+ if not hasattr(self, 'termination_b_type'):
+ raise ValidationError('Termination B type has not been specified')
+ try:
+ self.termination_b_type.model_class().objects.get(pk=self.termination_b_id)
+ except ObjectDoesNotExist:
+ raise ValidationError({
+ 'termination_b': 'Invalid ID for type {}'.format(self.termination_b_type)
+ })
+
+ type_a = self.termination_a_type.model
+ type_b = self.termination_b_type.model
+
+ # Validate interface types
+ if type_a == 'interface' and self.termination_a.type in NONCONNECTABLE_IFACE_TYPES:
+ raise ValidationError({
+ 'termination_a_id': 'Cables cannot be terminated to {} interfaces'.format(
+ self.termination_a.get_type_display()
+ )
+ })
+ if type_b == 'interface' and self.termination_b.type in NONCONNECTABLE_IFACE_TYPES:
+ raise ValidationError({
+ 'termination_b_id': 'Cables cannot be terminated to {} interfaces'.format(
+ self.termination_b.get_type_display()
+ )
+ })
+
+ # Check that termination types are compatible
+ if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
+ raise ValidationError("Incompatible termination types: {} and {}".format(
+ self.termination_a_type, self.termination_b_type
+ ))
+
+ # A component with multiple positions must be connected to a component with an equal number of positions
+ term_a_positions = getattr(self.termination_a, 'positions', 1)
+ term_b_positions = getattr(self.termination_b, 'positions', 1)
+ if term_a_positions != term_b_positions:
+ raise ValidationError(
+ "{} has {} positions and {} has {}. Both terminations must have the same number of positions.".format(
+ self.termination_a, term_a_positions, self.termination_b, term_b_positions
+ )
+ )
+
+ # A termination point cannot be connected to itself
+ if self.termination_a == self.termination_b:
+ raise ValidationError("Cannot connect {} to itself".format(self.termination_a_type))
+
+ # A front port cannot be connected to its corresponding rear port
+ if (
+ type_a in ['frontport', 'rearport'] and
+ type_b in ['frontport', 'rearport'] and
+ (
+ getattr(self.termination_a, 'rear_port', None) == self.termination_b or
+ getattr(self.termination_b, 'rear_port', None) == self.termination_a
+ )
+ ):
+ raise ValidationError("A front port cannot be connected to it corresponding rear port")
+
+ # Check for an existing Cable connected to either termination object
+ if self.termination_a.cable not in (None, self):
+ raise ValidationError("{} already has a cable attached (#{})".format(
+ self.termination_a, self.termination_a.cable_id
+ ))
+ if self.termination_b.cable not in (None, self):
+ raise ValidationError("{} already has a cable attached (#{})".format(
+ self.termination_b, self.termination_b.cable_id
+ ))
+
+ # Validate length and length_unit
+ if self.length is not None and not self.length_unit:
+ raise ValidationError("Must specify a unit when setting a cable length")
+ elif self.length is None:
+ self.length_unit = ''
+
+ def save(self, *args, **kwargs):
+
+ # Store the given length (if any) in meters for use in database ordering
+ if self.length and self.length_unit:
+ self._abs_length = to_meters(self.length, self.length_unit)
+ else:
+ self._abs_length = None
+
+ # Store the parent Device for the A and B terminations (if applicable) to enable filtering
+ if hasattr(self.termination_a, 'device'):
+ self._termination_a_device = self.termination_a.device
+ if hasattr(self.termination_b, 'device'):
+ self._termination_b_device = self.termination_b.device
+
+ super().save(*args, **kwargs)
+
+ def to_csv(self):
+ return (
+ '{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model),
+ self.termination_a_id,
+ '{}.{}'.format(self.termination_b_type.app_label, self.termination_b_type.model),
+ self.termination_b_id,
+ self.get_type_display(),
+ self.get_status_display(),
+ self.label,
+ self.color,
+ self.length,
+ self.length_unit,
+ )
+
+ def get_status_class(self):
+ return 'success' if self.status else 'info'
+
+ def get_compatible_types(self):
+ """
+ Return all termination types compatible with termination A.
+ """
+ if self.termination_a is None:
+ return
+ return COMPATIBLE_TERMINATION_TYPES[self.termination_a._meta.model_name]
+
+ def get_path_endpoints(self):
+ """
+ Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be
+ None.
+ """
+ a_path = self.termination_b.trace()
+ b_path = self.termination_a.trace()
+
+ # Determine overall path status (connected or planned)
+ if self.status == CableStatusChoices.STATUS_PLANNED:
+ path_status = CONNECTION_STATUS_PLANNED
+ else:
+ path_status = CONNECTION_STATUS_CONNECTED
+ for segment in a_path[1:] + b_path[1:]:
+ if segment[1] is None or segment[1].status == CableStatusChoices.STATUS_PLANNED:
+ path_status = CONNECTION_STATUS_PLANNED
+ break
+
+ a_endpoint = a_path[-1][2]
+ b_endpoint = b_path[-1][2]
+
+ return a_endpoint, b_endpoint, path_status
diff --git a/netbox/dcim/tests/test_filters.py b/netbox/dcim/tests/test_filters.py
new file mode 100644
index 000000000..1feacc5c5
--- /dev/null
+++ b/netbox/dcim/tests/test_filters.py
@@ -0,0 +1,2381 @@
+from django.contrib.auth.models import User
+from django.test import TestCase
+
+from dcim.constants import *
+from dcim.filters import *
+from dcim.models import (
+ Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
+ DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
+ InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet,
+ PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
+ VirtualChassis,
+)
+from ipam.models import IPAddress
+from virtualization.models import Cluster, ClusterType
+
+
+class RegionTestCase(TestCase):
+ queryset = Region.objects.all()
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ child_regions = (
+ Region(name='Region 1A', slug='region-1a', parent=regions[0]),
+ Region(name='Region 1B', slug='region-1b', parent=regions[0]),
+ Region(name='Region 2A', slug='region-2a', parent=regions[1]),
+ Region(name='Region 2B', slug='region-2b', parent=regions[1]),
+ Region(name='Region 3A', slug='region-3a', parent=regions[2]),
+ Region(name='Region 3B', slug='region-3b', parent=regions[2]),
+ )
+ for region in child_regions:
+ region.save()
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Region 1', 'Region 2']}
+ self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['region-1', 'region-2']}
+ self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 2)
+
+ def test_parent(self):
+ parent_regions = Region.objects.filter(parent__isnull=True)[:2]
+ params = {'parent_id': [parent_regions[0].pk, parent_regions[1].pk]}
+ self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 4)
+ params = {'parent': [parent_regions[0].slug, parent_regions[1].slug]}
+ self.assertEqual(RegionFilter(params, self.queryset).qs.count(), 4)
+
+
+class SiteTestCase(TestCase):
+ queryset = Site.objects.all()
+ filterset = SiteFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0], status=SITE_STATUS_ACTIVE, facility='Facility 1', asn=65001, latitude=10, longitude=10, contact_name='Contact 1', contact_phone='123-555-0001', contact_email='contact1@example.com'),
+ Site(name='Site 2', slug='site-2', region=regions[1], status=SITE_STATUS_PLANNED, facility='Facility 2', asn=65002, latitude=20, longitude=20, contact_name='Contact 2', contact_phone='123-555-0002', contact_email='contact2@example.com'),
+ Site(name='Site 3', slug='site-3', region=regions[2], status=SITE_STATUS_RETIRED, facility='Facility 3', asn=65003, latitude=30, longitude=30, contact_name='Contact 3', contact_phone='123-555-0003', contact_email='contact3@example.com'),
+ )
+ Site.objects.bulk_create(sites)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Site 1', 'Site 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['site-1', 'site-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_facility(self):
+ params = {'facility': ['Facility 1', 'Facility 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_asn(self):
+ params = {'asn': [65001, 65002]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_latitude(self):
+ params = {'latitude': [10, 20]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_longitude(self):
+ params = {'longitude': [10, 20]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_contact_name(self):
+ params = {'contact_name': ['Contact 1', 'Contact 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_contact_phone(self):
+ params = {'contact_phone': ['123-555-0001', '123-555-0002']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_contact_email(self):
+ params = {'contact_email': ['contact1@example.com', 'contact2@example.com']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_status(self):
+ params = {'status': [SITE_STATUS_ACTIVE, SITE_STATUS_PLANNED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class RackGroupTestCase(TestCase):
+ queryset = RackGroup.objects.all()
+ filterset = RackGroupFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0]),
+ Site(name='Site 2', slug='site-2', region=regions[1]),
+ Site(name='Site 3', slug='site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ rack_groups = (
+ RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
+ RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
+ RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
+ )
+ RackGroup.objects.bulk_create(rack_groups)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Rack Group 1', 'Rack Group 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['rack-group-1', 'rack-group-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class RackRoleTestCase(TestCase):
+ queryset = RackRole.objects.all()
+ filterset = RackRoleFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ rack_roles = (
+ RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'),
+ RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'),
+ RackRole(name='Rack Role 3', slug='rack-role-3', color='0000ff'),
+ )
+ RackRole.objects.bulk_create(rack_roles)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Rack Role 1', 'Rack Role 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['rack-role-1', 'rack-role-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_color(self):
+ params = {'color': ['ff0000', '00ff00']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class RackTestCase(TestCase):
+ queryset = Rack.objects.all()
+ filterset = RackFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0]),
+ Site(name='Site 2', slug='site-2', region=regions[1]),
+ Site(name='Site 3', slug='site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ rack_groups = (
+ RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
+ RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
+ RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
+ )
+ RackGroup.objects.bulk_create(rack_groups)
+
+ rack_roles = (
+ RackRole(name='Rack Role 1', slug='rack-role-1'),
+ RackRole(name='Rack Role 2', slug='rack-role-2'),
+ RackRole(name='Rack Role 3', slug='rack-role-3'),
+ )
+ RackRole.objects.bulk_create(rack_roles)
+
+ racks = (
+ Rack(name='Rack 1', facility_id='rack-1', site=sites[0], group=rack_groups[0], status=RACK_STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RACK_TYPE_2POST, width=RACK_WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=LENGTH_UNIT_MILLIMETER),
+ Rack(name='Rack 2', facility_id='rack-2', site=sites[1], group=rack_groups[1], status=RACK_STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RACK_TYPE_4POST, width=RACK_WIDTH_19IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=LENGTH_UNIT_MILLIMETER),
+ Rack(name='Rack 3', facility_id='rack-3', site=sites[2], group=rack_groups[2], status=RACK_STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RACK_TYPE_CABINET, width=RACK_WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=LENGTH_UNIT_INCH),
+ )
+ Rack.objects.bulk_create(racks)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Rack 1', 'Rack 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_facility_id(self):
+ params = {'facility_id': ['rack-1', 'rack-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_asset_tag(self):
+ params = {'asset_tag': ['1001', '1002']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_type(self):
+ # TODO: Test for multiple values
+ params = {'type': RACK_TYPE_2POST}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_width(self):
+ # TODO: Test for multiple values
+ params = {'width': RACK_WIDTH_19IN}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_u_height(self):
+ params = {'u_height': [42, 43]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_desc_units(self):
+ params = {'desc_units': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'desc_units': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_outer_width(self):
+ params = {'outer_width': [100, 200]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_outer_depth(self):
+ params = {'outer_depth': [100, 200]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_outer_unit(self):
+ self.assertEqual(Rack.objects.filter(outer_unit__isnull=False).count(), 3)
+ params = {'outer_unit': LENGTH_UNIT_MILLIMETER}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_group(self):
+ groups = RackGroup.objects.all()[:2]
+ params = {'group_id': [groups[0].pk, groups[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'group': [groups[0].slug, groups[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_status(self):
+ params = {'status': [RACK_STATUS_ACTIVE, RACK_STATUS_PLANNED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_role(self):
+ roles = RackRole.objects.all()[:2]
+ params = {'role_id': [roles[0].pk, roles[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'role': [roles[0].slug, roles[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_serial(self):
+ params = {'serial': 'ABC'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'serial': 'abc'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class RackReservationTestCase(TestCase):
+ queryset = RackReservation.objects.all()
+ filterset = RackReservationFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ sites = (
+ Site(name='Site 1', slug='site-1'),
+ Site(name='Site 2', slug='site-2'),
+ Site(name='Site 3', slug='site-3'),
+ )
+ Site.objects.bulk_create(sites)
+
+ rack_groups = (
+ RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
+ RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
+ RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
+ )
+ RackGroup.objects.bulk_create(rack_groups)
+
+ racks = (
+ Rack(name='Rack 1', site=sites[0], group=rack_groups[0]),
+ Rack(name='Rack 2', site=sites[1], group=rack_groups[1]),
+ Rack(name='Rack 3', site=sites[2], group=rack_groups[2]),
+ )
+ Rack.objects.bulk_create(racks)
+
+ users = (
+ User(username='User 1'),
+ User(username='User 2'),
+ User(username='User 3'),
+ )
+ User.objects.bulk_create(users)
+
+ reservations = (
+ RackReservation(rack=racks[0], units=[1, 2, 3], user=users[0]),
+ RackReservation(rack=racks[1], units=[4, 5, 6], user=users[1]),
+ RackReservation(rack=racks[2], units=[7, 8, 9], user=users[2]),
+ )
+ RackReservation.objects.bulk_create(reservations)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_group(self):
+ groups = RackGroup.objects.all()[:2]
+ params = {'group_id': [groups[0].pk, groups[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'group': [groups[0].slug, groups[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_user(self):
+ users = User.objects.all()[:2]
+ params = {'user_id': [users[0].pk, users[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ # TODO: Filtering by username is broken
+ # params = {'user': [users[0].username, users[1].username]}
+ # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class ManufacturerTestCase(TestCase):
+ queryset = Manufacturer.objects.all()
+ filterset = ManufacturerFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturers = (
+ Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
+ Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
+ Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
+ )
+ Manufacturer.objects.bulk_create(manufacturers)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Manufacturer 1', 'Manufacturer 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['manufacturer-1', 'manufacturer-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class DeviceTypeTestCase(TestCase):
+ queryset = DeviceType.objects.all()
+ filterset = DeviceTypeFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturers = (
+ Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
+ Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
+ Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
+ )
+ Manufacturer.objects.bulk_create(manufacturers)
+
+ device_types = (
+ DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, subdevice_role=None),
+ DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SUBDEVICE_ROLE_PARENT),
+ DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SUBDEVICE_ROLE_CHILD),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ # Add component templates for filtering
+ ConsolePortTemplate.objects.bulk_create((
+ ConsolePortTemplate(device_type=device_types[0], name='Console Port 1'),
+ ConsolePortTemplate(device_type=device_types[1], name='Console Port 2'),
+ ))
+ ConsoleServerPortTemplate.objects.bulk_create((
+ ConsoleServerPortTemplate(device_type=device_types[0], name='Console Server Port 1'),
+ ConsoleServerPortTemplate(device_type=device_types[1], name='Console Server Port 2'),
+ ))
+ PowerPortTemplate.objects.bulk_create((
+ PowerPortTemplate(device_type=device_types[0], name='Power Port 1'),
+ PowerPortTemplate(device_type=device_types[1], name='Power Port 2'),
+ ))
+ PowerOutletTemplate.objects.bulk_create((
+ PowerOutletTemplate(device_type=device_types[0], name='Power Outlet 1'),
+ PowerOutletTemplate(device_type=device_types[1], name='Power Outlet 2'),
+ ))
+ InterfaceTemplate.objects.bulk_create((
+ InterfaceTemplate(device_type=device_types[0], name='Interface 1'),
+ InterfaceTemplate(device_type=device_types[1], name='Interface 2'),
+ ))
+ rear_ports = (
+ RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PORT_TYPE_8P8C),
+ RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PORT_TYPE_8P8C),
+ )
+ RearPortTemplate.objects.bulk_create(rear_ports)
+ FrontPortTemplate.objects.bulk_create((
+ FrontPortTemplate(device_type=device_types[0], name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=rear_ports[0]),
+ FrontPortTemplate(device_type=device_types[1], name='Front Port 2', type=PORT_TYPE_8P8C, rear_port=rear_ports[1]),
+ ))
+ DeviceBayTemplate.objects.bulk_create((
+ DeviceBayTemplate(device_type=device_types[0], name='Device Bay 1'),
+ DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'),
+ ))
+
+ def test_model(self):
+ params = {'model': ['Model 1', 'Model 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['model-1', 'model-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_part_number(self):
+ params = {'part_number': ['Part Number 1', 'Part Number 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_u_height(self):
+ params = {'u_height': [1, 2]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_is_full_depth(self):
+ params = {'is_full_depth': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'is_full_depth': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_subdevice_role(self):
+ params = {'subdevice_role': SUBDEVICE_ROLE_PARENT}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_manufacturer(self):
+ manufacturers = Manufacturer.objects.all()[:2]
+ params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_console_ports(self):
+ params = {'console_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'console_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_console_server_ports(self):
+ params = {'console_server_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'console_server_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_power_ports(self):
+ params = {'power_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'power_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_power_outlets(self):
+ params = {'power_outlets': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'power_outlets': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_interfaces(self):
+ params = {'interfaces': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'interfaces': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_pass_through_ports(self):
+ params = {'pass_through_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'pass_through_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ # TODO: Add device_bay filter
+ # def test_device_bays(self):
+ # params = {'device_bays': 'true'}
+ # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ # params = {'device_bays': 'false'}
+ # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class ConsolePortTemplateTestCase(TestCase):
+ queryset = ConsolePortTemplate.objects.all()
+ filterset = ConsolePortTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ ConsolePortTemplate.objects.bulk_create((
+ ConsolePortTemplate(device_type=device_types[0], name='Console Port 1'),
+ ConsolePortTemplate(device_type=device_types[1], name='Console Port 2'),
+ ConsolePortTemplate(device_type=device_types[2], name='Console Port 3'),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Console Port 1', 'Console Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class ConsoleServerPortTemplateTestCase(TestCase):
+ queryset = ConsoleServerPortTemplate.objects.all()
+ filterset = ConsoleServerPortTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ ConsoleServerPortTemplate.objects.bulk_create((
+ ConsoleServerPortTemplate(device_type=device_types[0], name='Console Server Port 1'),
+ ConsoleServerPortTemplate(device_type=device_types[1], name='Console Server Port 2'),
+ ConsoleServerPortTemplate(device_type=device_types[2], name='Console Server Port 3'),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class PowerPortTemplateTestCase(TestCase):
+ queryset = PowerPortTemplate.objects.all()
+ filterset = PowerPortTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ PowerPortTemplate.objects.bulk_create((
+ PowerPortTemplate(device_type=device_types[0], name='Power Port 1', maximum_draw=100, allocated_draw=50),
+ PowerPortTemplate(device_type=device_types[1], name='Power Port 2', maximum_draw=200, allocated_draw=100),
+ PowerPortTemplate(device_type=device_types[2], name='Power Port 3', maximum_draw=300, allocated_draw=150),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Power Port 1', 'Power Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_maximum_draw(self):
+ params = {'maximum_draw': [100, 200]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_allocated_draw(self):
+ params = {'allocated_draw': [50, 100]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class PowerOutletTemplateTestCase(TestCase):
+ queryset = PowerOutletTemplate.objects.all()
+ filterset = PowerOutletTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ PowerOutletTemplate.objects.bulk_create((
+ PowerOutletTemplate(device_type=device_types[0], name='Power Outlet 1', feed_leg=POWERFEED_LEG_A),
+ PowerOutletTemplate(device_type=device_types[1], name='Power Outlet 2', feed_leg=POWERFEED_LEG_B),
+ PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3', feed_leg=POWERFEED_LEG_C),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_feed_leg(self):
+ # TODO: Support filtering for multiple values
+ params = {'feed_leg': POWERFEED_LEG_A}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class InterfaceTemplateTestCase(TestCase):
+ queryset = InterfaceTemplate.objects.all()
+ filterset = InterfaceTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ InterfaceTemplate.objects.bulk_create((
+ InterfaceTemplate(device_type=device_types[0], name='Interface 1', type=IFACE_TYPE_1GE_FIXED, mgmt_only=True),
+ InterfaceTemplate(device_type=device_types[1], name='Interface 2', type=IFACE_TYPE_1GE_GBIC, mgmt_only=False),
+ InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=IFACE_TYPE_1GE_SFP, mgmt_only=False),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Interface 1', 'Interface 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_type(self):
+ # TODO: Support filtering for multiple values
+ params = {'type': IFACE_TYPE_1GE_FIXED}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_mgmt_only(self):
+ params = {'mgmt_only': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'mgmt_only': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class FrontPortTemplateTestCase(TestCase):
+ queryset = FrontPortTemplate.objects.all()
+ filterset = FrontPortTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ rear_ports = (
+ RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PORT_TYPE_8P8C),
+ RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PORT_TYPE_8P8C),
+ RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PORT_TYPE_8P8C),
+ )
+ RearPortTemplate.objects.bulk_create(rear_ports)
+
+ FrontPortTemplate.objects.bulk_create((
+ FrontPortTemplate(device_type=device_types[0], name='Front Port 1', rear_port=rear_ports[0], type=PORT_TYPE_8P8C),
+ FrontPortTemplate(device_type=device_types[1], name='Front Port 2', rear_port=rear_ports[1], type=PORT_TYPE_110_PUNCH),
+ FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2], type=PORT_TYPE_BNC),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Front Port 1', 'Front Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_type(self):
+ # TODO: Support filtering for multiple values
+ params = {'type': PORT_TYPE_8P8C}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class RearPortTemplateTestCase(TestCase):
+ queryset = RearPortTemplate.objects.all()
+ filterset = RearPortTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ RearPortTemplate.objects.bulk_create((
+ RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PORT_TYPE_8P8C, positions=1),
+ RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PORT_TYPE_110_PUNCH, positions=2),
+ RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PORT_TYPE_BNC, positions=3),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Rear Port 1', 'Rear Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_type(self):
+ # TODO: Support filtering for multiple values
+ params = {'type': PORT_TYPE_8P8C}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_positions(self):
+ params = {'positions': [1, 2]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class DeviceBayTemplateTestCase(TestCase):
+ queryset = DeviceBayTemplate.objects.all()
+ filterset = DeviceBayTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+
+ device_types = (
+ DeviceType(manufacturer=manufacturer, model='Model 1', slug='model-1'),
+ DeviceType(manufacturer=manufacturer, model='Model 2', slug='model-2'),
+ DeviceType(manufacturer=manufacturer, model='Model 3', slug='model-3'),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ DeviceBayTemplate.objects.bulk_create((
+ DeviceBayTemplate(device_type=device_types[0], name='Device Bay 1'),
+ DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'),
+ DeviceBayTemplate(device_type=device_types[2], name='Device Bay 3'),
+ ))
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Device Bay 1', 'Device Bay 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype_id(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class DeviceRoleTestCase(TestCase):
+ queryset = DeviceRole.objects.all()
+ filterset = DeviceRoleFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ device_roles = (
+ DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000', vm_role=True),
+ DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00', vm_role=True),
+ DeviceRole(name='Device Role 3', slug='device-role-3', color='0000ff', vm_role=False),
+ )
+ DeviceRole.objects.bulk_create(device_roles)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Device Role 1', 'Device Role 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['device-role-1', 'device-role-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_color(self):
+ params = {'color': ['ff0000', '00ff00']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_vm_role(self):
+ params = {'vm_role': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'vm_role': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class PlatformTestCase(TestCase):
+ queryset = Platform.objects.all()
+ filterset = PlatformFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturers = (
+ Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
+ Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
+ Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
+ )
+ Manufacturer.objects.bulk_create(manufacturers)
+
+ platforms = (
+ Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0], napalm_driver='driver-1'),
+ Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturers[1], napalm_driver='driver-2'),
+ Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturers[2], napalm_driver='driver-3'),
+ )
+ Platform.objects.bulk_create(platforms)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Platform 1', 'Platform 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['platform-1', 'platform-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_napalm_driver(self):
+ params = {'napalm_driver': ['driver-1', 'driver-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_manufacturer(self):
+ manufacturers = Manufacturer.objects.all()[:2]
+ params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class DeviceTestCase(TestCase):
+ queryset = Device.objects.all()
+ filterset = DeviceFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturers = (
+ Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
+ Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
+ Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
+ )
+ Manufacturer.objects.bulk_create(manufacturers)
+
+ device_types = (
+ DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', is_full_depth=True),
+ DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', is_full_depth=True),
+ DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', is_full_depth=False),
+ )
+ DeviceType.objects.bulk_create(device_types)
+
+ device_roles = (
+ DeviceRole(name='Device Role 1', slug='device-role-1'),
+ DeviceRole(name='Device Role 2', slug='device-role-2'),
+ DeviceRole(name='Device Role 3', slug='device-role-3'),
+ )
+ DeviceRole.objects.bulk_create(device_roles)
+
+ platforms = (
+ Platform(name='Platform 1', slug='platform-1'),
+ Platform(name='Platform 2', slug='platform-2'),
+ Platform(name='Platform 3', slug='platform-3'),
+ )
+ Platform.objects.bulk_create(platforms)
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0]),
+ Site(name='Site 2', slug='site-2', region=regions[1]),
+ Site(name='Site 3', slug='site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ rack_groups = (
+ RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
+ RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
+ RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
+ )
+ RackGroup.objects.bulk_create(rack_groups)
+
+ racks = (
+ Rack(name='Rack 1', site=sites[0], group=rack_groups[0]),
+ Rack(name='Rack 2', site=sites[1], group=rack_groups[1]),
+ Rack(name='Rack 3', site=sites[2], group=rack_groups[2]),
+ )
+ Rack.objects.bulk_create(racks)
+
+ cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
+ clusters = (
+ Cluster(name='Cluster 1', type=cluster_type),
+ Cluster(name='Cluster 2', type=cluster_type),
+ Cluster(name='Cluster 3', type=cluster_type),
+ )
+ 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 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.objects.bulk_create(devices)
+
+ # Add components for filtering
+ ConsolePort.objects.bulk_create((
+ ConsolePort(device=devices[0], name='Console Port 1'),
+ ConsolePort(device=devices[1], name='Console Port 2'),
+ ))
+ ConsoleServerPort.objects.bulk_create((
+ ConsoleServerPort(device=devices[0], name='Console Server Port 1'),
+ ConsoleServerPort(device=devices[1], name='Console Server Port 2'),
+ ))
+ PowerPort.objects.bulk_create((
+ PowerPort(device=devices[0], name='Power Port 1'),
+ PowerPort(device=devices[1], name='Power Port 2'),
+ ))
+ PowerOutlet.objects.bulk_create((
+ PowerOutlet(device=devices[0], name='Power Outlet 1'),
+ PowerOutlet(device=devices[1], name='Power Outlet 2'),
+ ))
+ interfaces = (
+ Interface(device=devices[0], name='Interface 1', mac_address='00-00-00-00-00-01'),
+ Interface(device=devices[1], name='Interface 2', mac_address='00-00-00-00-00-02'),
+ )
+ Interface.objects.bulk_create(interfaces)
+ rear_ports = (
+ RearPort(device=devices[0], name='Rear Port 1', type=PORT_TYPE_8P8C),
+ RearPort(device=devices[1], name='Rear Port 2', type=PORT_TYPE_8P8C),
+ )
+ RearPort.objects.bulk_create(rear_ports)
+ FrontPort.objects.bulk_create((
+ FrontPort(device=devices[0], name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=rear_ports[0]),
+ FrontPort(device=devices[1], name='Front Port 2', type=PORT_TYPE_8P8C, rear_port=rear_ports[1]),
+ ))
+ DeviceBay.objects.bulk_create((
+ DeviceBay(device=devices[0], name='Device Bay 1'),
+ DeviceBay(device=devices[1], name='Device Bay 2'),
+ ))
+
+ # Assign primary IPs for filtering
+ ipaddresses = (
+ IPAddress(family=4, address='192.0.2.1/24', interface=interfaces[0]),
+ IPAddress(family=4, address='192.0.2.2/24', interface=interfaces[1]),
+ )
+ IPAddress.objects.bulk_create(ipaddresses)
+ Device.objects.filter(pk=devices[0].pk).update(primary_ip4=ipaddresses[0])
+ Device.objects.filter(pk=devices[1].pk).update(primary_ip4=ipaddresses[1])
+
+ # VirtualChassis assignment for filtering
+ virtual_chassis = VirtualChassis.objects.create(master=devices[0])
+ Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1)
+ Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Device 1', 'Device 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_asset_tag(self):
+ params = {'asset_tag': ['1001', '1002']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_face(self):
+ params = {'face': RACK_FACE_FRONT}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_position(self):
+ params = {'position': [1, 2]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_vc_position(self):
+ params = {'vc_position': [1, 2]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_vc_priority(self):
+ params = {'vc_priority': [1, 2]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_manufacturer(self):
+ manufacturers = Manufacturer.objects.all()[:2]
+ params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicetype(self):
+ device_types = DeviceType.objects.all()[:2]
+ params = {'device_type_id': [device_types[0].pk, device_types[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_devicerole(self):
+ device_roles = DeviceRole.objects.all()[:2]
+ params = {'role_id': [device_roles[0].pk, device_roles[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'role': [device_roles[0].slug, device_roles[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_platform(self):
+ platforms = Platform.objects.all()[:2]
+ params = {'platform_id': [platforms[0].pk, platforms[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'platform': [platforms[0].slug, platforms[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_rackgroup(self):
+ rack_groups = RackGroup.objects.all()[:2]
+ params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_rack(self):
+ racks = Rack.objects.all()[:2]
+ params = {'rack_id': [racks[0].pk, racks[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cluster(self):
+ clusters = Cluster.objects.all()[:2]
+ params = {'cluster_id': [clusters[0].pk, clusters[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_model(self):
+ params = {'model': ['model-1', 'model-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_status(self):
+ params = {'status': [DEVICE_STATUS_ACTIVE, DEVICE_STATUS_STAGED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_is_full_depth(self):
+ params = {'is_full_depth': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'is_full_depth': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_mac_address(self):
+ 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_serial(self):
+ params = {'serial': 'ABC'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'serial': 'abc'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_has_primary_ip(self):
+ params = {'has_primary_ip': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'has_primary_ip': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_virtual_chassis_id(self):
+ params = {'virtual_chassis_id': [VirtualChassis.objects.first().pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_virtual_chassis_member(self):
+ params = {'virtual_chassis_member': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'virtual_chassis_member': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_console_ports(self):
+ params = {'console_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'console_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_console_server_ports(self):
+ params = {'console_server_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'console_server_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_power_ports(self):
+ params = {'power_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'power_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_power_outlets(self):
+ params = {'power_outlets': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'power_outlets': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_interfaces(self):
+ params = {'interfaces': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'interfaces': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_pass_through_ports(self):
+ params = {'pass_through_ports': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'pass_through_ports': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ # TODO: Add device_bay filter
+ # def test_device_bays(self):
+ # params = {'device_bays': 'true'}
+ # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ # params = {'device_bays': 'false'}
+ # self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class ConsolePortTestCase(TestCase):
+ queryset = ConsolePort.objects.all()
+ filterset = ConsolePortFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections
+ )
+ Device.objects.bulk_create(devices)
+
+ console_server_ports = (
+ ConsoleServerPort(device=devices[3], name='Console Server Port 1'),
+ ConsoleServerPort(device=devices[3], name='Console Server Port 2'),
+ )
+ ConsoleServerPort.objects.bulk_create(console_server_ports)
+
+ console_ports = (
+ ConsolePort(device=devices[0], name='Console Port 1', description='First'),
+ ConsolePort(device=devices[1], name='Console Port 2', description='Second'),
+ ConsolePort(device=devices[2], name='Console Port 3', description='Third'),
+ )
+ ConsolePort.objects.bulk_create(console_ports)
+
+ # Cables
+ Cable(termination_a=console_ports[0], termination_b=console_server_ports[0]).save()
+ Cable(termination_a=console_ports[1], termination_b=console_server_ports[1]).save()
+ # Third port is not connected
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Console Port 1', 'Console Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ # TODO: Fix boolean value
+ def test_connection_status(self):
+ params = {'connection_status': 'True'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cabled(self):
+ params = {'cabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'cabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class ConsoleServerPortTestCase(TestCase):
+ queryset = ConsoleServerPort.objects.all()
+ filterset = ConsoleServerPortFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections
+ )
+ Device.objects.bulk_create(devices)
+
+ console_ports = (
+ ConsolePort(device=devices[3], name='Console Server Port 1'),
+ ConsolePort(device=devices[3], name='Console Server Port 2'),
+ )
+ ConsolePort.objects.bulk_create(console_ports)
+
+ console_server_ports = (
+ ConsoleServerPort(device=devices[0], name='Console Server Port 1', description='First'),
+ ConsoleServerPort(device=devices[1], name='Console Server Port 2', description='Second'),
+ ConsoleServerPort(device=devices[2], name='Console Server Port 3', description='Third'),
+ )
+ ConsoleServerPort.objects.bulk_create(console_server_ports)
+
+ # Cables
+ Cable(termination_a=console_server_ports[0], termination_b=console_ports[0]).save()
+ Cable(termination_a=console_server_ports[1], termination_b=console_ports[1]).save()
+ # Third port is not connected
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ # TODO: Fix boolean value
+ def test_connection_status(self):
+ params = {'connection_status': 'True'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cabled(self):
+ params = {'cabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'cabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class PowerPortTestCase(TestCase):
+ queryset = PowerPort.objects.all()
+ filterset = PowerPortFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections
+ )
+ Device.objects.bulk_create(devices)
+
+ power_outlets = (
+ PowerOutlet(device=devices[3], name='Power Outlet 1'),
+ PowerOutlet(device=devices[3], name='Power Outlet 2'),
+ )
+ PowerOutlet.objects.bulk_create(power_outlets)
+
+ power_ports = (
+ PowerPort(device=devices[0], name='Power Port 1', maximum_draw=100, allocated_draw=50, description='First'),
+ PowerPort(device=devices[1], name='Power Port 2', maximum_draw=200, allocated_draw=100, description='Second'),
+ PowerPort(device=devices[2], name='Power Port 3', maximum_draw=300, allocated_draw=150, description='Third'),
+ )
+ PowerPort.objects.bulk_create(power_ports)
+
+ # Cables
+ Cable(termination_a=power_ports[0], termination_b=power_outlets[0]).save()
+ Cable(termination_a=power_ports[1], termination_b=power_outlets[1]).save()
+ # Third port is not connected
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Power Port 1', 'Power Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_maximum_draw(self):
+ params = {'maximum_draw': [100, 200]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_allocated_draw(self):
+ params = {'allocated_draw': [50, 100]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ # TODO: Fix boolean value
+ def test_connection_status(self):
+ params = {'connection_status': 'True'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cabled(self):
+ params = {'cabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'cabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class PowerOutletTestCase(TestCase):
+ queryset = PowerOutlet.objects.all()
+ filterset = PowerOutletFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections
+ )
+ Device.objects.bulk_create(devices)
+
+ power_ports = (
+ PowerPort(device=devices[3], name='Power Outlet 1'),
+ PowerPort(device=devices[3], name='Power Outlet 2'),
+ )
+ PowerPort.objects.bulk_create(power_ports)
+
+ power_outlets = (
+ PowerOutlet(device=devices[0], name='Power Outlet 1', feed_leg=POWERFEED_LEG_A, description='First'),
+ PowerOutlet(device=devices[1], name='Power Outlet 2', feed_leg=POWERFEED_LEG_B, description='Second'),
+ PowerOutlet(device=devices[2], name='Power Outlet 3', feed_leg=POWERFEED_LEG_C, description='Third'),
+ )
+ PowerOutlet.objects.bulk_create(power_outlets)
+
+ # Cables
+ Cable(termination_a=power_outlets[0], termination_b=power_ports[0]).save()
+ Cable(termination_a=power_outlets[1], termination_b=power_ports[1]).save()
+ # Third port is not connected
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_feed_leg(self):
+ # TODO: Support filtering for multiple values
+ params = {'feed_leg': POWERFEED_LEG_A}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ # TODO: Fix boolean value
+ def test_connection_status(self):
+ params = {'connection_status': 'True'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cabled(self):
+ params = {'cabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'cabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class InterfaceTestCase(TestCase):
+ queryset = Interface.objects.all()
+ filterset = InterfaceFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections
+ )
+ Device.objects.bulk_create(devices)
+
+ interfaces = (
+ Interface(device=devices[0], name='Interface 1', type=IFACE_TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=IFACE_MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First'),
+ Interface(device=devices[1], name='Interface 2', type=IFACE_TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=IFACE_MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second'),
+ Interface(device=devices[2], name='Interface 3', type=IFACE_TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=IFACE_MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third'),
+ Interface(device=devices[3], name='Interface 4', type=IFACE_TYPE_OTHER, enabled=True, mgmt_only=True),
+ Interface(device=devices[3], name='Interface 5', type=IFACE_TYPE_OTHER, enabled=True, mgmt_only=True),
+ Interface(device=devices[3], name='Interface 6', type=IFACE_TYPE_OTHER, enabled=False, mgmt_only=False),
+ )
+ Interface.objects.bulk_create(interfaces)
+
+ # Cables
+ Cable(termination_a=interfaces[0], termination_b=interfaces[3]).save()
+ Cable(termination_a=interfaces[1], termination_b=interfaces[4]).save()
+ # Third pair is not connected
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_name(self):
+ params = {'name': ['Interface 1', 'Interface 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ # TODO: Fix boolean value
+ def test_connection_status(self):
+ params = {'connection_status': 'True'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_enabled(self):
+ params = {'enabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'enabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_mtu(self):
+ params = {'mtu': [100, 200]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_mgmt_only(self):
+ params = {'mgmt_only': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'mgmt_only': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_mode(self):
+ params = {'mode': IFACE_MODE_ACCESS}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cabled(self):
+ params = {'cabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'cabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_kind(self):
+ params = {'kind': 'physical'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
+ params = {'kind': 'virtual'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
+
+ def test_mac_address(self):
+ 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_type(self):
+ params = {'type': [IFACE_TYPE_1GE_FIXED, IFACE_TYPE_1GE_GBIC]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class FrontPortTestCase(TestCase):
+ queryset = FrontPort.objects.all()
+ filterset = FrontPortFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections
+ )
+ Device.objects.bulk_create(devices)
+
+ rear_ports = (
+ RearPort(device=devices[0], name='Rear Port 1', type=PORT_TYPE_8P8C, positions=6),
+ RearPort(device=devices[1], name='Rear Port 2', type=PORT_TYPE_8P8C, positions=6),
+ RearPort(device=devices[2], name='Rear Port 3', type=PORT_TYPE_8P8C, positions=6),
+ RearPort(device=devices[3], name='Rear Port 4', type=PORT_TYPE_8P8C, positions=6),
+ RearPort(device=devices[3], name='Rear Port 5', type=PORT_TYPE_8P8C, positions=6),
+ RearPort(device=devices[3], name='Rear Port 6', type=PORT_TYPE_8P8C, positions=6),
+ )
+ RearPort.objects.bulk_create(rear_ports)
+
+ front_ports = (
+ FrontPort(device=devices[0], name='Front Port 1', type=PORT_TYPE_8P8C, rear_port=rear_ports[0], rear_port_position=1, description='First'),
+ FrontPort(device=devices[1], name='Front Port 2', type=PORT_TYPE_110_PUNCH, rear_port=rear_ports[1], rear_port_position=2, description='Second'),
+ FrontPort(device=devices[2], name='Front Port 3', type=PORT_TYPE_BNC, rear_port=rear_ports[2], rear_port_position=3, description='Third'),
+ FrontPort(device=devices[3], name='Front Port 4', type=PORT_TYPE_FC, rear_port=rear_ports[3], rear_port_position=1),
+ FrontPort(device=devices[3], name='Front Port 5', type=PORT_TYPE_FC, rear_port=rear_ports[4], rear_port_position=1),
+ FrontPort(device=devices[3], name='Front Port 6', type=PORT_TYPE_FC, rear_port=rear_ports[5], rear_port_position=1),
+ )
+ FrontPort.objects.bulk_create(front_ports)
+
+ # Cables
+ Cable(termination_a=front_ports[0], termination_b=front_ports[3]).save()
+ Cable(termination_a=front_ports[1], termination_b=front_ports[4]).save()
+ # Third port is not connected
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Front Port 1', 'Front Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_type(self):
+ # TODO: Test for multiple values
+ params = {'type': PORT_TYPE_8P8C}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cabled(self):
+ params = {'cabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'cabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class RearPortTestCase(TestCase):
+ queryset = RearPort.objects.all()
+ filterset = RearPortFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ Device(name=None, device_type=device_type, device_role=device_role, site=site), # For cable connections
+ )
+ Device.objects.bulk_create(devices)
+
+ rear_ports = (
+ RearPort(device=devices[0], name='Rear Port 1', type=PORT_TYPE_8P8C, positions=1, description='First'),
+ RearPort(device=devices[1], name='Rear Port 2', type=PORT_TYPE_110_PUNCH, positions=2, description='Second'),
+ RearPort(device=devices[2], name='Rear Port 3', type=PORT_TYPE_BNC, positions=3, description='Third'),
+ RearPort(device=devices[3], name='Rear Port 4', type=PORT_TYPE_FC, positions=4),
+ RearPort(device=devices[3], name='Rear Port 5', type=PORT_TYPE_FC, positions=5),
+ RearPort(device=devices[3], name='Rear Port 6', type=PORT_TYPE_FC, positions=6),
+ )
+ RearPort.objects.bulk_create(rear_ports)
+
+ # Cables
+ Cable(termination_a=rear_ports[0], termination_b=rear_ports[3]).save()
+ Cable(termination_a=rear_ports[1], termination_b=rear_ports[4]).save()
+ # Third port is not connected
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Rear Port 1', 'Rear Port 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_type(self):
+ # TODO: Test for multiple values
+ params = {'type': PORT_TYPE_8P8C}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_positions(self):
+ params = {'positions': [1, 2]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_cabled(self):
+ params = {'cabled': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'cabled': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class DeviceBayTestCase(TestCase):
+ queryset = DeviceBay.objects.all()
+ filterset = DeviceBayFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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=site),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=site),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=site),
+ )
+ Device.objects.bulk_create(devices)
+
+ device_bays = (
+ DeviceBay(device=devices[0], name='Device Bay 1', description='First'),
+ DeviceBay(device=devices[1], name='Device Bay 2', description='Second'),
+ DeviceBay(device=devices[2], name='Device Bay 3', description='Third'),
+ )
+ DeviceBay.objects.bulk_create(device_bays)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Device Bay 1', 'Device Bay 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_description(self):
+ params = {'description': ['First', 'Second']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class InventoryItemTestCase(TestCase):
+ queryset = InventoryItem.objects.all()
+ filterset = InventoryItemFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturers = (
+ Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
+ Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
+ Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
+ )
+ Manufacturer.objects.bulk_create(manufacturers)
+
+ device_type = DeviceType.objects.create(manufacturer=manufacturers[0], model='Model 1', slug='model-1')
+ device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0]),
+ Site(name='Site 2', slug='site-2', region=regions[1]),
+ Site(name='Site 3', slug='site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ devices = (
+ Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0]),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1]),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2]),
+ )
+ Device.objects.bulk_create(devices)
+
+ inventory_items = (
+ InventoryItem(device=devices[0], manufacturer=manufacturers[0], name='Inventory Item 1', part_id='1001', serial='ABC', asset_tag='1001', discovered=True, description='First'),
+ InventoryItem(device=devices[1], manufacturer=manufacturers[1], name='Inventory Item 2', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, description='Second'),
+ InventoryItem(device=devices[2], manufacturer=manufacturers[2], name='Inventory Item 3', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, description='Third'),
+ )
+ InventoryItem.objects.bulk_create(inventory_items)
+
+ child_inventory_items = (
+ InventoryItem(device=devices[0], name='Inventory Item 1A', parent=inventory_items[0]),
+ InventoryItem(device=devices[1], name='Inventory Item 2A', parent=inventory_items[1]),
+ InventoryItem(device=devices[2], name='Inventory Item 3A', parent=inventory_items[2]),
+ )
+ InventoryItem.objects.bulk_create(child_inventory_items)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Inventory Item 1', 'Inventory Item 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_part_id(self):
+ params = {'part_id': ['1001', '1002']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_asset_tag(self):
+ params = {'asset_tag': ['1001', '1002']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_discovered(self):
+ # TODO: Fix boolean value
+ params = {'discovered': True}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'discovered': False}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_device(self):
+ # TODO: Allow multiple values
+ device = Device.objects.first()
+ params = {'device_id': device.pk}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': device.name}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_parent_id(self):
+ parent_items = InventoryItem.objects.filter(parent__isnull=True)[:2]
+ params = {'parent_id': [parent_items[0].pk, parent_items[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_manufacturer(self):
+ manufacturers = Manufacturer.objects.all()[:2]
+ params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_serial(self):
+ params = {'serial': 'ABC'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'serial': 'abc'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class VirtualChassisTestCase(TestCase):
+ queryset = VirtualChassis.objects.all()
+ filterset = VirtualChassisFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0]),
+ Site(name='Site 2', slug='site-2', region=regions[1]),
+ Site(name='Site 3', slug='site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ devices = (
+ Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], vc_position=1),
+ Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], vc_position=2),
+ Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], vc_position=1),
+ Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], vc_position=2),
+ Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], vc_position=1),
+ Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], vc_position=2),
+ )
+ Device.objects.bulk_create(devices)
+
+ virtual_chassis = (
+ VirtualChassis(master=devices[0], domain='Domain 1'),
+ VirtualChassis(master=devices[2], domain='Domain 2'),
+ VirtualChassis(master=devices[4], domain='Domain 3'),
+ )
+ VirtualChassis.objects.bulk_create(virtual_chassis)
+
+ Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis[0])
+ Device.objects.filter(pk=devices[3].pk).update(virtual_chassis=virtual_chassis[1])
+ Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=virtual_chassis[2])
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_domain(self):
+ params = {'domain': ['Domain 1', 'Domain 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class CableTestCase(TestCase):
+ queryset = Cable.objects.all()
+ filterset = CableFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ sites = (
+ Site(name='Site 1', slug='site-1'),
+ Site(name='Site 2', slug='site-2'),
+ Site(name='Site 3', slug='site-3'),
+ )
+ Site.objects.bulk_create(sites)
+
+ racks = (
+ Rack(name='Rack 1', site=sites[0]),
+ Rack(name='Rack 2', site=sites[1]),
+ Rack(name='Rack 3', site=sites[2]),
+ )
+ Rack.objects.bulk_create(racks)
+
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
+ 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 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),
+ )
+ Device.objects.bulk_create(devices)
+
+ interfaces = (
+ Interface(device=devices[0], name='Interface 1', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[0], name='Interface 2', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[1], name='Interface 3', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[1], name='Interface 4', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[2], name='Interface 5', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[2], name='Interface 6', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[3], name='Interface 7', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[3], name='Interface 8', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[4], name='Interface 9', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[4], name='Interface 10', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[5], name='Interface 11', type=IFACE_TYPE_1GE_FIXED),
+ Interface(device=devices[5], name='Interface 12', type=IFACE_TYPE_1GE_FIXED),
+ )
+ Interface.objects.bulk_create(interfaces)
+
+ # Cables
+ Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CABLE_TYPE_CAT3, status=CONNECTION_STATUS_CONNECTED, color='aa1409', length=10, length_unit=LENGTH_UNIT_FOOT).save()
+ Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CABLE_TYPE_CAT3, status=CONNECTION_STATUS_CONNECTED, color='aa1409', length=20, length_unit=LENGTH_UNIT_FOOT).save()
+ Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3', type=CABLE_TYPE_CAT5E, status=CONNECTION_STATUS_CONNECTED, color='f44336', length=30, length_unit=LENGTH_UNIT_FOOT).save()
+ Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CABLE_TYPE_CAT5E, status=CONNECTION_STATUS_PLANNED, color='f44336', length=40, length_unit=LENGTH_UNIT_FOOT).save()
+ Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CABLE_TYPE_CAT6, status=CONNECTION_STATUS_PLANNED, color='e91e63', length=10, length_unit=LENGTH_UNIT_METER).save()
+ Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CABLE_TYPE_CAT6, status=CONNECTION_STATUS_PLANNED, color='e91e63', length=20, length_unit=LENGTH_UNIT_METER).save()
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_label(self):
+ params = {'label': ['Cable 1', 'Cable 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_length(self):
+ params = {'length': [10, 20]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_length_unit(self):
+ params = {'length_unit': LENGTH_UNIT_FOOT}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_type(self):
+ params = {'type': [CABLE_TYPE_CAT3, CABLE_TYPE_CAT5E]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_status(self):
+ params = {'status': [CONNECTION_STATUS_CONNECTED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_color(self):
+ params = {'color': ['aa1409', 'f44336']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_rack(self):
+ racks = Rack.objects.all()[:2]
+ params = {'rack_id': [racks[0].pk, racks[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+ params = {'rack': [racks[0].name, racks[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+
+ def test_site(self):
+ site = Site.objects.all()[:2]
+ params = {'site_id': [site[0].pk, site[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+ params = {'site': [site[0].slug, site[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+
+
+class PowerPanelTestCase(TestCase):
+ queryset = PowerPanel.objects.all()
+ filterset = PowerPanelFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0]),
+ Site(name='Site 2', slug='site-2', region=regions[1]),
+ Site(name='Site 3', slug='site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ rack_groups = (
+ RackGroup(name='Rack Group 1', slug='rack-group-1', site=sites[0]),
+ RackGroup(name='Rack Group 2', slug='rack-group-2', site=sites[1]),
+ RackGroup(name='Rack Group 3', slug='rack-group-3', site=sites[2]),
+ )
+ RackGroup.objects.bulk_create(rack_groups)
+
+ power_panels = (
+ PowerPanel(name='Power Panel 1', site=sites[0], rack_group=rack_groups[0]),
+ PowerPanel(name='Power Panel 2', site=sites[1], rack_group=rack_groups[1]),
+ PowerPanel(name='Power Panel 3', site=sites[2], rack_group=rack_groups[2]),
+ )
+ PowerPanel.objects.bulk_create(power_panels)
+
+ def test_name(self):
+ params = {'name': ['Power Panel 1', 'Power Panel 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_rack_group(self):
+ rack_groups = RackGroup.objects.all()[:2]
+ params = {'rack_group_id': [rack_groups[0].pk, rack_groups[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class PowerFeedTestCase(TestCase):
+ queryset = PowerFeed.objects.all()
+ filterset = PowerFeedFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Region 1', slug='region-1'),
+ Region(name='Region 2', slug='region-2'),
+ Region(name='Region 3', slug='region-3'),
+ )
+ for region in regions:
+ region.save()
+
+ sites = (
+ Site(name='Site 1', slug='site-1', region=regions[0]),
+ Site(name='Site 2', slug='site-2', region=regions[1]),
+ Site(name='Site 3', slug='site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ racks = (
+ Rack(name='Rack 1', site=sites[0]),
+ Rack(name='Rack 2', site=sites[1]),
+ Rack(name='Rack 3', site=sites[2]),
+ )
+ Rack.objects.bulk_create(racks)
+
+ power_panels = (
+ PowerPanel(name='Power Panel 1', site=sites[0]),
+ PowerPanel(name='Power Panel 2', site=sites[1]),
+ PowerPanel(name='Power Panel 3', site=sites[2]),
+ )
+ PowerPanel.objects.bulk_create(power_panels)
+
+ power_feeds = (
+ PowerFeed(power_panel=power_panels[0], rack=racks[0], name='Power Feed 1', status=POWERFEED_STATUS_ACTIVE, type=POWERFEED_TYPE_PRIMARY, supply=POWERFEED_SUPPLY_AC, phase=POWERFEED_PHASE_3PHASE, voltage=100, amperage=100, max_utilization=10),
+ PowerFeed(power_panel=power_panels[1], rack=racks[1], name='Power Feed 2', status=POWERFEED_STATUS_FAILED, type=POWERFEED_TYPE_PRIMARY, supply=POWERFEED_SUPPLY_AC, phase=POWERFEED_PHASE_3PHASE, voltage=200, amperage=200, max_utilization=20),
+ PowerFeed(power_panel=power_panels[2], rack=racks[2], name='Power Feed 3', status=POWERFEED_STATUS_OFFLINE, type=POWERFEED_TYPE_REDUNDANT, supply=POWERFEED_SUPPLY_DC, phase=POWERFEED_PHASE_SINGLE, voltage=300, amperage=300, max_utilization=30),
+ )
+ PowerFeed.objects.bulk_create(power_feeds)
+
+ def test_name(self):
+ params = {'name': ['Power Feed 1', 'Power Feed 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_status(self):
+ # TODO: Test for multiple values
+ params = {'status': POWERFEED_STATUS_ACTIVE}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_type(self):
+ params = {'type': POWERFEED_TYPE_PRIMARY}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_supply(self):
+ params = {'supply': POWERFEED_SUPPLY_AC}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_phase(self):
+ params = {'phase': POWERFEED_PHASE_3PHASE}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_voltage(self):
+ params = {'voltage': [100, 200]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_amperage(self):
+ params = {'amperage': [100, 200]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_max_utilization(self):
+ params = {'max_utilization': [10, 20]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_power_panel_id(self):
+ power_panels = PowerPanel.objects.all()[:2]
+ params = {'power_panel_id': [power_panels[0].pk, power_panels[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_rack_id(self):
+ racks = Rack.objects.all()[:2]
+ params = {'rack_id': [racks[0].pk, racks[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+# TODO: Connection filters
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index d89a08f2e..84bb116b0 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -386,7 +386,7 @@ class RackElevationListView(PermissionRequiredMixin, View):
'page': page,
'total_count': total_count,
'face_id': face_id,
- 'filter_form': forms.RackFilterForm(request.GET),
+ 'filter_form': forms.RackElevationFilterForm(request.GET),
})
@@ -1900,10 +1900,13 @@ class CableTraceView(PermissionRequiredMixin, View):
def get(self, request, model, pk):
obj = get_object_or_404(model, pk=pk)
+ trace = obj.trace(follow_circuits=True)
+ total_length = sum([entry[1]._abs_length for entry in trace if entry[1] and entry[1]._abs_length])
return render(request, 'dcim/cable_trace.html', {
'obj': obj,
- 'trace': obj.trace(follow_circuits=True),
+ 'trace': trace,
+ 'total_length': total_length,
})
diff --git a/netbox/extras/filters.py b/netbox/extras/filters.py
index 001d0ce9e..5938bcc5b 100644
--- a/netbox/extras/filters.py
+++ b/netbox/extras/filters.py
@@ -8,6 +8,20 @@ from .choices import *
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
+__all__ = (
+ 'ConfigContextFilter',
+ 'CreatedUpdatedFilterSet',
+ 'CustomFieldFilter',
+ 'CustomFieldFilterSet',
+ 'ExportTemplateFilter',
+ 'GraphFilter',
+ 'LocalConfigContextFilter',
+ 'ObjectChangeFilter',
+ 'TagFilter',
+ 'TopologyMapFilter',
+)
+
+
class CustomFieldFilter(django_filters.Filter):
"""
Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name.
diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py
index f43a2e36e..fed003bed 100644
--- a/netbox/extras/scripts.py
+++ b/netbox/extras/scripts.py
@@ -268,12 +268,12 @@ class BaseScript:
def run(self, data):
raise NotImplementedError("The script must define a run() method.")
- def as_form(self, data=None, files=None):
+ def as_form(self, data=None, files=None, initial=None):
"""
Return a Django form suitable for populating the context data required to run this Script.
"""
vars = self._get_vars()
- form = ScriptForm(vars, data, files, commit_default=getattr(self.Meta, 'commit_default', True))
+ form = ScriptForm(vars, data, files, initial=initial, commit_default=getattr(self.Meta, 'commit_default', True))
return form
diff --git a/netbox/extras/templatetags/custom_links.py b/netbox/extras/templatetags/custom_links.py
index 8c927a0ae..7dae81a1f 100644
--- a/netbox/extras/templatetags/custom_links.py
+++ b/netbox/extras/templatetags/custom_links.py
@@ -68,8 +68,9 @@ def custom_links(obj):
text_rendered = render_jinja2(cl.text, context)
if text_rendered:
link_target = ' target="_blank"' if cl.new_window else ''
+ link_rendered = render_jinja2(cl.url, context)
links_rendered.append(
- GROUP_LINK.format(cl.url, link_target, cl.text)
+ GROUP_LINK.format(link_rendered, link_target, text_rendered)
)
except Exception as e:
links_rendered.append(
diff --git a/netbox/extras/tests/test_filters.py b/netbox/extras/tests/test_filters.py
new file mode 100644
index 000000000..6c6b8145b
--- /dev/null
+++ b/netbox/extras/tests/test_filters.py
@@ -0,0 +1,181 @@
+from django.contrib.contenttypes.models import ContentType
+from django.test import TestCase
+
+from dcim.models import DeviceRole, Platform, Region, Site
+from extras.constants import *
+from extras.filters import *
+from extras.models import ConfigContext, ExportTemplate, Graph
+from tenancy.models import Tenant, TenantGroup
+
+
+class GraphTestCase(TestCase):
+ queryset = Graph.objects.all()
+ filterset = GraphFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ graphs = (
+ Graph(name='Graph 1', type=GRAPH_TYPE_DEVICE, source='http://example.com/1'),
+ Graph(name='Graph 2', type=GRAPH_TYPE_INTERFACE, source='http://example.com/2'),
+ Graph(name='Graph 3', type=GRAPH_TYPE_SITE, source='http://example.com/3'),
+ )
+ Graph.objects.bulk_create(graphs)
+
+ def test_name(self):
+ params = {'name': 'Graph 1'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_type(self):
+ params = {'type': GRAPH_TYPE_DEVICE}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+
+class ExportTemplateTestCase(TestCase):
+ queryset = ExportTemplate.objects.all()
+ filterset = ExportTemplateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device'])
+
+ export_templates = (
+ ExportTemplate(name='Export Template 1', content_type=content_types[0], template_language=TEMPLATE_LANGUAGE_DJANGO, template_code='TESTING'),
+ ExportTemplate(name='Export Template 2', content_type=content_types[1], template_language=TEMPLATE_LANGUAGE_JINJA2, template_code='TESTING'),
+ ExportTemplate(name='Export Template 3', content_type=content_types[2], template_language=TEMPLATE_LANGUAGE_JINJA2, template_code='TESTING'),
+ )
+ ExportTemplate.objects.bulk_create(export_templates)
+
+ def test_name(self):
+ params = {'name': 'Export Template 1'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_content_type(self):
+ params = {'content_type': ContentType.objects.get(model='site').pk}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_template_language(self):
+ params = {'template_language': TEMPLATE_LANGUAGE_JINJA2}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class ConfigContextTestCase(TestCase):
+ queryset = ConfigContext.objects.all()
+ filterset = ConfigContextFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Test Region 1', slug='test-region-1'),
+ Region(name='Test Region 2', slug='test-region-2'),
+ Region(name='Test Region 3', slug='test-region-3'),
+ )
+ # Can't use bulk_create for models with MPTT fields
+ for r in regions:
+ r.save()
+
+ sites = (
+ Site(name='Test Site 1', slug='test-site-1'),
+ Site(name='Test Site 2', slug='test-site-2'),
+ Site(name='Test Site 3', slug='test-site-3'),
+ )
+ Site.objects.bulk_create(sites)
+
+ device_roles = (
+ DeviceRole(name='Device Role 1', slug='device-role-1'),
+ DeviceRole(name='Device Role 2', slug='device-role-2'),
+ DeviceRole(name='Device Role 3', slug='device-role-3'),
+ )
+ DeviceRole.objects.bulk_create(device_roles)
+
+ platforms = (
+ Platform(name='Platform 1', slug='platform-1'),
+ Platform(name='Platform 2', slug='platform-2'),
+ Platform(name='Platform 3', slug='platform-3'),
+ )
+ Platform.objects.bulk_create(platforms)
+
+ tenant_groups = (
+ TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
+ TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
+ TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
+ )
+ TenantGroup.objects.bulk_create(tenant_groups)
+
+ tenants = (
+ Tenant(name='Tenant 1', slug='tenant-1'),
+ Tenant(name='Tenant 2', slug='tenant-2'),
+ Tenant(name='Tenant 3', slug='tenant-3'),
+ )
+ Tenant.objects.bulk_create(tenants)
+
+ for i in range(0, 3):
+ is_active = bool(i % 2)
+ c = ConfigContext.objects.create(
+ name='Config Context {}'.format(i + 1),
+ is_active=is_active,
+ data='{"foo": 123}'
+ )
+ c.regions.set([regions[i]])
+ c.sites.set([sites[i]])
+ c.roles.set([device_roles[i]])
+ c.platforms.set([platforms[i]])
+ c.tenant_groups.set([tenant_groups[i]])
+ c.tenants.set([tenants[i]])
+
+ def test_name(self):
+ params = {'name': 'Config Context 1'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_is_active(self):
+ params = {'is_active': True}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'is_active': False}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_role(self):
+ device_roles = DeviceRole.objects.all()[:2]
+ params = {'role_id': [device_roles[0].pk, device_roles[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'role': [device_roles[0].slug, device_roles[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_platform(self):
+ platforms = Platform.objects.all()[:2]
+ params = {'platform_id': [platforms[0].pk, platforms[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'platform': [platforms[0].slug, platforms[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_tenant_group(self):
+ tenant_groups = TenantGroup.objects.all()[:2]
+ params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_tenant_(self):
+ tenants = Tenant.objects.all()[:2]
+ params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'tenant': [tenants[0].slug, tenants[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+# TODO: ObjectChangeFilter test
diff --git a/netbox/extras/views.py b/netbox/extras/views.py
index 1b52c5dbb..df1f38d11 100644
--- a/netbox/extras/views.py
+++ b/netbox/extras/views.py
@@ -392,7 +392,7 @@ class ScriptView(PermissionRequiredMixin, View):
def get(self, request, module, name):
script = self._get_script(module, name)
- form = script.as_form()
+ form = script.as_form(initial=request.GET)
return render(request, 'extras/script.html', {
'module': module,
diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py
index 46d2a45fd..dd0f568a8 100644
--- a/netbox/ipam/filters.py
+++ b/netbox/ipam/filters.py
@@ -13,6 +13,19 @@ from .choices import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
+__all__ = (
+ 'AggregateFilter',
+ 'IPAddressFilter',
+ 'PrefixFilter',
+ 'RIRFilter',
+ 'RoleFilter',
+ 'ServiceFilter',
+ 'VLANFilter',
+ 'VLANGroupFilter',
+ 'VRFFilter',
+)
+
+
class VRFFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter(
field_name='id',
diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py
index 759215e0b..92c45f20a 100644
--- a/netbox/ipam/models.py
+++ b/netbox/ipam/models.py
@@ -200,6 +200,12 @@ class Aggregate(ChangeLoggedModel, CustomFieldModel):
# Clear host bits from prefix
self.prefix = self.prefix.cidr
+ # /0 masks are not acceptable
+ if self.prefix.prefixlen == 0:
+ raise ValidationError({
+ 'prefix': "Cannot create aggregate with /0 mask."
+ })
+
# Ensure that the aggregate being added is not covered by an existing aggregate
covering_aggregates = Aggregate.objects.filter(prefix__net_contains_or_equals=str(self.prefix))
if self.pk:
@@ -386,6 +392,12 @@ class Prefix(ChangeLoggedModel, CustomFieldModel):
if self.prefix:
+ # /0 masks are not acceptable
+ if self.prefix.prefixlen == 0:
+ raise ValidationError({
+ 'prefix': "Cannot create prefix with /0 mask."
+ })
+
# Disallow host masks
if self.prefix.version == 4 and self.prefix.prefixlen == 32:
raise ValidationError({
@@ -681,6 +693,12 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
if self.address:
+ # /0 masks are not acceptable
+ if self.address.prefixlen == 0:
+ raise ValidationError({
+ 'address': "Cannot create IP address with /0 mask."
+ })
+
# Enforce unique IP space (if applicable)
if self.role not in IPADDRESS_ROLES_NONUNIQUE and ((
self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE
diff --git a/netbox/ipam/tests/test_filters.py b/netbox/ipam/tests/test_filters.py
new file mode 100644
index 000000000..5ae912bc6
--- /dev/null
+++ b/netbox/ipam/tests/test_filters.py
@@ -0,0 +1,645 @@
+from django.test import TestCase
+
+from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site
+from ipam.constants import *
+from ipam.filters import *
+from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
+from virtualization.models import Cluster, ClusterType, VirtualMachine
+
+
+class VRFTestCase(TestCase):
+ queryset = VRF.objects.all()
+ filterset = VRFFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ vrfs = (
+ VRF(name='VRF 1', rd='65000:100', enforce_unique=False),
+ VRF(name='VRF 2', rd='65000:200', enforce_unique=False),
+ VRF(name='VRF 3', rd='65000:300', enforce_unique=False),
+ VRF(name='VRF 4', rd='65000:400', enforce_unique=True),
+ VRF(name='VRF 5', rd='65000:500', enforce_unique=True),
+ VRF(name='VRF 6', rd='65000:600', enforce_unique=True),
+ )
+ VRF.objects.bulk_create(vrfs)
+
+ def test_name(self):
+ params = {'name': ['VRF 1', 'VRF 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_rd(self):
+ params = {'rd': ['65000:100', '65000:200']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_enforce_unique(self):
+ params = {'enforce_unique': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+ params = {'enforce_unique': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+
+class RIRTestCase(TestCase):
+ queryset = RIR.objects.all()
+ filterset = RIRFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ rirs = (
+ RIR(name='RIR 1', slug='rir-1', is_private=False),
+ RIR(name='RIR 2', slug='rir-2', is_private=False),
+ RIR(name='RIR 3', slug='rir-3', is_private=False),
+ RIR(name='RIR 4', slug='rir-4', is_private=True),
+ RIR(name='RIR 5', slug='rir-5', is_private=True),
+ RIR(name='RIR 6', slug='rir-6', is_private=True),
+ )
+ RIR.objects.bulk_create(rirs)
+
+ def test_name(self):
+ params = {'name': ['RIR 1', 'RIR 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['rir-1', 'rir-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_is_private(self):
+ params = {'is_private': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+ params = {'is_private': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+
+class AggregateTestCase(TestCase):
+ queryset = Aggregate.objects.all()
+ filterset = AggregateFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ rirs = (
+ RIR(name='RIR 1', slug='rir-1'),
+ RIR(name='RIR 2', slug='rir-2'),
+ RIR(name='RIR 3', slug='rir-3'),
+ )
+ RIR.objects.bulk_create(rirs)
+
+ aggregates = (
+ Aggregate(family=4, prefix='10.1.0.0/16', rir=rirs[0], date_added='2020-01-01'),
+ Aggregate(family=4, prefix='10.2.0.0/16', rir=rirs[0], date_added='2020-01-02'),
+ Aggregate(family=4, prefix='10.3.0.0/16', rir=rirs[1], date_added='2020-01-03'),
+ Aggregate(family=6, prefix='2001:db8:1::/48', rir=rirs[1], date_added='2020-01-04'),
+ Aggregate(family=6, prefix='2001:db8:2::/48', rir=rirs[2], date_added='2020-01-05'),
+ Aggregate(family=6, prefix='2001:db8:3::/48', rir=rirs[2], date_added='2020-01-06'),
+ )
+ Aggregate.objects.bulk_create(aggregates)
+
+ def test_family(self):
+ params = {'family': '4'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_date_added(self):
+ params = {'date_added': ['2020-01-01', '2020-01-02']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ # TODO: Test for multiple values
+ def test_prefix(self):
+ params = {'prefix': '10.1.0.0/16'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_rir(self):
+ rirs = RIR.objects.all()[:2]
+ params = {'rir_id': [rirs[0].pk, rirs[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'rir': [rirs[0].slug, rirs[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+
+class RoleTestCase(TestCase):
+ queryset = Role.objects.all()
+ filterset = RoleFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ roles = (
+ Role(name='Role 1', slug='role-1'),
+ Role(name='Role 2', slug='role-2'),
+ Role(name='Role 3', slug='role-3'),
+ )
+ Role.objects.bulk_create(roles)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Role 1', 'Role 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['role-1', 'role-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class PrefixTestCase(TestCase):
+ queryset = Prefix.objects.all()
+ filterset = PrefixFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Test Region 1', slug='test-region-1'),
+ Region(name='Test Region 2', slug='test-region-2'),
+ Region(name='Test Region 3', slug='test-region-3'),
+ )
+ # Can't use bulk_create for models with MPTT fields
+ for r in regions:
+ r.save()
+
+ sites = (
+ Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
+ Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
+ Site(name='Test Site 3', slug='test-site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ vrfs = (
+ VRF(name='VRF 1', rd='65000:100'),
+ VRF(name='VRF 2', rd='65000:200'),
+ VRF(name='VRF 3', rd='65000:300'),
+ )
+ VRF.objects.bulk_create(vrfs)
+
+ vlans = (
+ VLAN(vid=1, name='VLAN 1'),
+ VLAN(vid=2, name='VLAN 2'),
+ VLAN(vid=3, name='VLAN 3'),
+ )
+ VLAN.objects.bulk_create(vlans)
+
+ roles = (
+ Role(name='Role 1', slug='role-1'),
+ Role(name='Role 2', slug='role-2'),
+ Role(name='Role 3', slug='role-3'),
+ )
+ Role.objects.bulk_create(roles)
+
+ prefixes = (
+ Prefix(family=4, prefix='10.0.0.0/24', site=None, vrf=None, vlan=None, role=None, is_pool=True),
+ Prefix(family=4, prefix='10.0.1.0/24', site=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0]),
+ Prefix(family=4, prefix='10.0.2.0/24', site=sites[1], vrf=vrfs[1], vlan=vlans[1], role=roles[1], status=PREFIX_STATUS_DEPRECATED),
+ Prefix(family=4, prefix='10.0.3.0/24', site=sites[2], vrf=vrfs[2], vlan=vlans[2], role=roles[2], status=PREFIX_STATUS_RESERVED),
+ Prefix(family=6, prefix='2001:db8::/64', site=None, vrf=None, vlan=None, role=None, is_pool=True),
+ Prefix(family=6, prefix='2001:db8:0:1::/64', site=sites[0], vrf=vrfs[0], vlan=vlans[0], role=roles[0]),
+ Prefix(family=6, prefix='2001:db8:0:2::/64', site=sites[1], vrf=vrfs[1], vlan=vlans[1], role=roles[1], status=PREFIX_STATUS_DEPRECATED),
+ Prefix(family=6, prefix='2001:db8:0:3::/64', site=sites[2], vrf=vrfs[2], vlan=vlans[2], role=roles[2], status=PREFIX_STATUS_RESERVED),
+ Prefix(family=4, prefix='10.0.0.0/16'),
+ Prefix(family=6, prefix='2001:db8::/32'),
+ )
+ Prefix.objects.bulk_create(prefixes)
+
+ def test_family(self):
+ params = {'family': '6'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+
+ def test_is_pool(self):
+ params = {'is_pool': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'is_pool': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_within(self):
+ params = {'within': '10.0.0.0/16'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_within_include(self):
+ params = {'within_include': '10.0.0.0/16'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
+
+ def test_contains(self):
+ params = {'contains': '10.0.1.0/24'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'contains': '2001:db8:0:1::/64'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_mask_length(self):
+ params = {'mask_length': '24'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_vrf(self):
+ vrfs = VRF.objects.all()[:2]
+ params = {'vrf_id': [vrfs[0].pk, vrfs[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'vrf': [vrfs[0].rd, vrfs[1].rd]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_vlan(self):
+ vlans = VLAN.objects.all()[:2]
+ params = {'vlan_id': [vlans[0].pk, vlans[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ # TODO: Test for multiple values
+ params = {'vlan_vid': vlans[0].vid}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_role(self):
+ roles = Role.objects.all()[:2]
+ params = {'role_id': [roles[0].pk, roles[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'role': [roles[0].slug, roles[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_status(self):
+ params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+
+class IPAddressTestCase(TestCase):
+ queryset = IPAddress.objects.all()
+ filterset = IPAddressFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ vrfs = (
+ VRF(name='VRF 1', rd='65000:100'),
+ VRF(name='VRF 2', rd='65000:200'),
+ VRF(name='VRF 3', rd='65000:300'),
+ )
+ VRF.objects.bulk_create(vrfs)
+
+ site = Site.objects.create(name='Site 1', slug='site-1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
+ device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
+
+ devices = (
+ Device(device_type=device_type, name='Device 1', site=site, device_role=device_role),
+ Device(device_type=device_type, name='Device 2', site=site, device_role=device_role),
+ Device(device_type=device_type, name='Device 3', site=site, device_role=device_role),
+ )
+ Device.objects.bulk_create(devices)
+
+ clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
+ cluster = Cluster.objects.create(type=clustertype, name='Cluster 1')
+
+ virtual_machines = (
+ VirtualMachine(name='Virtual Machine 1', cluster=cluster),
+ VirtualMachine(name='Virtual Machine 2', cluster=cluster),
+ VirtualMachine(name='Virtual Machine 3', cluster=cluster),
+ )
+ VirtualMachine.objects.bulk_create(virtual_machines)
+
+ interfaces = (
+ Interface(device=devices[0], name='Interface 1'),
+ Interface(device=devices[1], name='Interface 2'),
+ Interface(device=devices[2], name='Interface 3'),
+ Interface(virtual_machine=virtual_machines[0], name='Interface 1'),
+ Interface(virtual_machine=virtual_machines[1], name='Interface 2'),
+ Interface(virtual_machine=virtual_machines[2], name='Interface 3'),
+ )
+ Interface.objects.bulk_create(interfaces)
+
+ ipaddresses = (
+ IPAddress(family=4, address='10.0.0.1/24', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-a'),
+ IPAddress(family=4, address='10.0.0.2/24', vrf=vrfs[0], interface=interfaces[0], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'),
+ IPAddress(family=4, address='10.0.0.3/24', vrf=vrfs[1], interface=interfaces[1], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'),
+ IPAddress(family=4, address='10.0.0.4/24', vrf=vrfs[2], interface=interfaces[2], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'),
+ IPAddress(family=6, address='2001:db8::1/64', vrf=None, interface=None, status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-a'),
+ IPAddress(family=6, address='2001:db8::2/64', vrf=vrfs[0], interface=interfaces[3], status=IPADDRESS_STATUS_ACTIVE, role=None, dns_name='ipaddress-b'),
+ IPAddress(family=6, address='2001:db8::3/64', vrf=vrfs[1], interface=interfaces[4], status=IPADDRESS_STATUS_RESERVED, role=IPADDRESS_ROLE_VIP, dns_name='ipaddress-c'),
+ IPAddress(family=6, address='2001:db8::4/64', vrf=vrfs[2], interface=interfaces[5], status=IPADDRESS_STATUS_DEPRECATED, role=IPADDRESS_ROLE_SECONDARY, dns_name='ipaddress-d'),
+ )
+ IPAddress.objects.bulk_create(ipaddresses)
+
+ def test_family(self):
+ params = {'family': '6'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_dns_name(self):
+ params = {'dns_name': ['ipaddress-a', 'ipaddress-b']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_parent(self):
+ params = {'parent': '10.0.0.0/24'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'parent': '2001:db8::/64'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def filter_address(self):
+ # Check IPv4 and IPv6, with and without a mask
+ params = {'address': '10.0.0.1/24'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'address': '10.0.0.1'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'address': '2001:db8::1/64'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'address': '2001:db8::1'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_mask_length(self):
+ params = {'mask_length': '24'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_vrf(self):
+ vrfs = VRF.objects.all()[:2]
+ params = {'vrf_id': [vrfs[0].pk, vrfs[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'vrf': [vrfs[0].rd, vrfs[1].rd]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ # TODO: Test for multiple values
+ def test_device(self):
+ device = Device.objects.first()
+ params = {'device_id': device.pk}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+ params = {'device': device.name}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+ def test_virtual_machine(self):
+ vms = VirtualMachine.objects.all()[:2]
+ params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'virtual_machine': [vms[0].name, vms[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_interface(self):
+ interfaces = Interface.objects.all()[:2]
+ params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'interface': ['Interface 1', 'Interface 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_assigned_to_interface(self):
+ params = {'assigned_to_interface': 'true'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
+ params = {'assigned_to_interface': 'false'}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_status(self):
+ params = {'status': [PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_role(self):
+ params = {'role': [IPADDRESS_ROLE_SECONDARY, IPADDRESS_ROLE_VIP]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+
+class VLANGroupTestCase(TestCase):
+ queryset = VLANGroup.objects.all()
+ filterset = VLANGroupFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Test Region 1', slug='test-region-1'),
+ Region(name='Test Region 2', slug='test-region-2'),
+ Region(name='Test Region 3', slug='test-region-3'),
+ )
+ # Can't use bulk_create for models with MPTT fields
+ for r in regions:
+ r.save()
+
+ sites = (
+ Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
+ Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
+ Site(name='Test Site 3', slug='test-site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ vlan_groups = (
+ VLANGroup(name='VLAN Group 1', slug='vlan-group-1', site=sites[0]),
+ VLANGroup(name='VLAN Group 2', slug='vlan-group-2', site=sites[1]),
+ VLANGroup(name='VLAN Group 3', slug='vlan-group-3', site=sites[2]),
+ VLANGroup(name='VLAN Group 4', slug='vlan-group-4', site=None),
+ )
+ VLANGroup.objects.bulk_create(vlan_groups)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['VLAN Group 1', 'VLAN Group 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['vlan-group-1', 'vlan-group-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class VLANTestCase(TestCase):
+ queryset = VLAN.objects.all()
+ filterset = VLANFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ regions = (
+ Region(name='Test Region 1', slug='test-region-1'),
+ Region(name='Test Region 2', slug='test-region-2'),
+ Region(name='Test Region 3', slug='test-region-3'),
+ )
+ # Can't use bulk_create for models with MPTT fields
+ for r in regions:
+ r.save()
+
+ sites = (
+ Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
+ Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
+ Site(name='Test Site 3', slug='test-site-3', region=regions[2]),
+ )
+ Site.objects.bulk_create(sites)
+
+ roles = (
+ Role(name='Role 1', slug='role-1'),
+ Role(name='Role 2', slug='role-2'),
+ Role(name='Role 3', slug='role-3'),
+ )
+ Role.objects.bulk_create(roles)
+
+ groups = (
+ VLANGroup(name='VLAN Group 1', slug='vlan-group-1', site=sites[0]),
+ VLANGroup(name='VLAN Group 2', slug='vlan-group-2', site=sites[1]),
+ VLANGroup(name='VLAN Group 3', slug='vlan-group-3', site=None),
+ )
+ VLANGroup.objects.bulk_create(groups)
+
+ vlans = (
+ VLAN(vid=101, name='VLAN 101', site=sites[0], group=groups[0], role=roles[0], status=VLAN_STATUS_ACTIVE),
+ VLAN(vid=102, name='VLAN 102', site=sites[0], group=groups[0], role=roles[0], status=VLAN_STATUS_ACTIVE),
+ VLAN(vid=201, name='VLAN 201', site=sites[1], group=groups[1], role=roles[1], status=VLAN_STATUS_DEPRECATED),
+ VLAN(vid=202, name='VLAN 202', site=sites[1], group=groups[1], role=roles[1], status=VLAN_STATUS_DEPRECATED),
+ VLAN(vid=301, name='VLAN 301', site=sites[2], group=groups[2], role=roles[2], status=VLAN_STATUS_RESERVED),
+ VLAN(vid=302, name='VLAN 302', site=sites[2], group=groups[2], role=roles[2], status=VLAN_STATUS_RESERVED),
+ )
+ VLAN.objects.bulk_create(vlans)
+
+ def test_name(self):
+ params = {'name': ['VLAN 101', 'VLAN 102']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_rd(self):
+ params = {'vid': ['101', '201', '301']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_region(self):
+ regions = Region.objects.all()[:2]
+ params = {'region_id': [regions[0].pk, regions[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'region': [regions[0].slug, regions[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_site(self):
+ sites = Site.objects.all()[:2]
+ params = {'site_id': [sites[0].pk, sites[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'site': [sites[0].slug, sites[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_group(self):
+ groups = VLANGroup.objects.all()[:2]
+ params = {'group_id': [groups[0].pk, groups[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'group': [groups[0].slug, groups[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_role(self):
+ roles = Role.objects.all()[:2]
+ params = {'role_id': [roles[0].pk, roles[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+ params = {'role': [roles[0].slug, roles[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_status(self):
+ params = {'status': [VLAN_STATUS_ACTIVE, VLAN_STATUS_DEPRECATED]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+
+class ServiceTestCase(TestCase):
+ queryset = Service.objects.all()
+ filterset = ServiceFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site-1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
+ device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
+
+ devices = (
+ Device(device_type=device_type, name='Device 1', site=site, device_role=device_role),
+ Device(device_type=device_type, name='Device 2', site=site, device_role=device_role),
+ Device(device_type=device_type, name='Device 3', site=site, device_role=device_role),
+ )
+ Device.objects.bulk_create(devices)
+
+ clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
+ cluster = Cluster.objects.create(type=clustertype, name='Cluster 1')
+
+ virtual_machines = (
+ VirtualMachine(name='Virtual Machine 1', cluster=cluster),
+ VirtualMachine(name='Virtual Machine 2', cluster=cluster),
+ VirtualMachine(name='Virtual Machine 3', cluster=cluster),
+ )
+ VirtualMachine.objects.bulk_create(virtual_machines)
+
+ services = (
+ Service(device=devices[0], name='Service 1', protocol=IP_PROTOCOL_TCP, port=1001),
+ Service(device=devices[1], name='Service 2', protocol=IP_PROTOCOL_TCP, port=1002),
+ Service(device=devices[2], name='Service 3', protocol=IP_PROTOCOL_UDP, port=1003),
+ Service(virtual_machine=virtual_machines[0], name='Service 4', protocol=IP_PROTOCOL_TCP, port=2001),
+ Service(virtual_machine=virtual_machines[1], name='Service 5', protocol=IP_PROTOCOL_TCP, port=2002),
+ Service(virtual_machine=virtual_machines[2], name='Service 6', protocol=IP_PROTOCOL_UDP, port=2003),
+ )
+ Service.objects.bulk_create(services)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:3]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_name(self):
+ params = {'name': ['Service 1', 'Service 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_protocol(self):
+ params = {'protocol': IP_PROTOCOL_TCP}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
+
+ def test_port(self):
+ params = {'port': ['1001', '1002', '1003']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_virtual_machine(self):
+ vms = VirtualMachine.objects.all()[:2]
+ params = {'virtual_machine_id': [vms[0].pk, vms[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'virtual_machine': [vms[0].name, vms[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py
index 362d6173c..f9792fd05 100644
--- a/netbox/ipam/views.py
+++ b/netbox/ipam/views.py
@@ -333,7 +333,10 @@ class AggregateView(PermissionRequiredMixin, View):
).annotate_depth(
limit=0
)
- child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
+
+ # Add available prefixes to the table if requested
+ if request.GET.get('show_available', 'true') == 'true':
+ child_prefixes = add_available_prefixes(aggregate.prefix, child_prefixes)
prefix_table = tables.PrefixDetailTable(child_prefixes)
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
@@ -356,6 +359,7 @@ class AggregateView(PermissionRequiredMixin, View):
'aggregate': aggregate,
'prefix_table': prefix_table,
'permissions': permissions,
+ 'show_available': request.GET.get('show_available', 'true') == 'true',
})
@@ -511,8 +515,8 @@ class PrefixPrefixesView(PermissionRequiredMixin, View):
'site', 'vlan', 'role',
).annotate_depth(limit=0)
- # Annotate available prefixes
- if child_prefixes:
+ # Add available prefixes to the table if requested
+ if child_prefixes and request.GET.get('show_available', 'true') == 'true':
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
prefix_table = tables.PrefixDetailTable(child_prefixes)
@@ -539,6 +543,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View):
'permissions': permissions,
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
'active_tab': 'prefixes',
+ 'show_available': request.GET.get('show_available', 'true') == 'true',
})
@@ -553,7 +558,10 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View):
ipaddresses = prefix.get_child_ips().prefetch_related(
'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for'
)
- ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
+
+ # Add available IP addresses to the table if requested
+ if request.GET.get('show_available', 'true') == 'true':
+ ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool)
ip_table = tables.IPAddressTable(ipaddresses)
if request.user.has_perm('ipam.change_ipaddress') or request.user.has_perm('ipam.delete_ipaddress'):
@@ -579,6 +587,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View):
'permissions': permissions,
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
'active_tab': 'ip-addresses',
+ 'show_available': request.GET.get('show_available', 'true') == 'true',
})
@@ -677,7 +686,14 @@ class IPAddressView(PermissionRequiredMixin, View):
).filter(
vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address)
)
- related_ips_table = tables.IPAddressTable(list(related_ips), orderable=False)
+
+ related_ips_table = tables.IPAddressTable(related_ips, orderable=False)
+
+ paginate = {
+ 'paginator_class': EnhancedPaginator,
+ 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
+ }
+ RequestConfig(request, paginate).configure(related_ips_table)
return render(request, 'ipam/ipaddress.html', {
'ipaddress': ipaddress,
diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js
index ab3c03169..58783b5d0 100644
--- a/netbox/project-static/js/forms.js
+++ b/netbox/project-static/js/forms.js
@@ -7,7 +7,7 @@ $(document).ready(function() {
// "Toggle" checkbox for object lists (PK column)
$('input:checkbox.toggle').click(function() {
- $(this).closest('table').find('input:checkbox[name=pk]').prop('checked', $(this).prop('checked'));
+ $(this).closest('table').find('input:checkbox[name=pk]:visible').prop('checked', $(this).prop('checked'));
// Show the "select all" box if present
if ($(this).is(':checked')) {
@@ -398,4 +398,43 @@ $(document).ready(function() {
// Account for the header height when hash-scrolling
window.addEventListener('load', headerOffsetScroll);
window.addEventListener('hashchange', headerOffsetScroll);
+
+ // Offset between the preview window and the window edges
+ const IMAGE_PREVIEW_OFFSET_X = 20;
+ const IMAGE_PREVIEW_OFFSET_Y = 10;
+
+ // Preview an image attachment when the link is hovered over
+ $('a.image-preview').on('mouseover', function(e) {
+ // Twice the offset to account for all sides of the picture
+ var maxWidth = window.innerWidth - (e.clientX + (IMAGE_PREVIEW_OFFSET_X * 2));
+ var maxHeight = window.innerHeight - (e.clientY + (IMAGE_PREVIEW_OFFSET_Y * 2));
+ var img = $('').attr('id', 'image-preview-window').css({
+ display: 'none',
+ position: 'absolute',
+ maxWidth: maxWidth + 'px',
+ maxHeight: maxHeight + 'px',
+ left: e.pageX + IMAGE_PREVIEW_OFFSET_X + 'px',
+ top: e.pageY + IMAGE_PREVIEW_OFFSET_Y + 'px',
+ boxShadow: '0 0px 12px 3px rgba(0, 0, 0, 0.4)',
+ });
+
+ // Remove any existing preview windows and add the current one
+ $('#image-preview-window').remove();
+ $('body').append(img);
+
+ // Once loaded, show the preview if the image is indeed an image
+ img.on('load', function(e) {
+ if (e.target.complete && e.target.naturalWidth) {
+ $('#image-preview-window').fadeIn('fast');
+ }
+ });
+
+ // Begin loading
+ img.attr('src', e.target.href);
+ });
+
+ // Fade the image out; it will be deleted when another one is previewed
+ $('a.image-preview').on('mouseout', function() {
+ $('#image-preview-window').fadeOut('fast');
+ });
});
diff --git a/netbox/project-static/js/interface_toggles.js b/netbox/project-static/js/interface_toggles.js
new file mode 100644
index 000000000..a3649558a
--- /dev/null
+++ b/netbox/project-static/js/interface_toggles.js
@@ -0,0 +1,30 @@
+// Toggle the display of IP addresses under interfaces
+$('button.toggle-ips').click(function() {
+ var selected = $(this).attr('selected');
+ if (selected) {
+ $('#interfaces_table tr.ipaddresses').hide();
+ } else {
+ $('#interfaces_table tr.ipaddresses').show();
+ }
+ $(this).attr('selected', !selected);
+ $(this).children('span').toggleClass('glyphicon-check glyphicon-unchecked');
+ return false;
+});
+
+// Inteface filtering
+$('input.interface-filter').on('input', function() {
+ var filter = new RegExp(this.value);
+
+ for (interface of $(this).closest('form').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
+ $(interface).find('input:checkbox[name=pk]').prop('checked', $('input.toggle').prop('checked'));
+ $(interface).show();
+ } else {
+ // Uncheck to prevent actions from including it when it doesn't match
+ $(interface).find('input:checkbox[name=pk]').prop('checked', false);
+ $(interface).hide();
+ }
+ }
+});
diff --git a/netbox/secrets/filters.py b/netbox/secrets/filters.py
index bdc643e71..2998b9c18 100644
--- a/netbox/secrets/filters.py
+++ b/netbox/secrets/filters.py
@@ -7,6 +7,12 @@ from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilte
from .models import Secret, SecretRole
+__all__ = (
+ 'SecretFilter',
+ 'SecretRoleFilter',
+)
+
+
class SecretRoleFilter(NameSlugSearchFilterSet):
class Meta:
diff --git a/netbox/secrets/tests/test_filters.py b/netbox/secrets/tests/test_filters.py
new file mode 100644
index 000000000..c378147ff
--- /dev/null
+++ b/netbox/secrets/tests/test_filters.py
@@ -0,0 +1,92 @@
+from django.test import TestCase
+
+from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
+from secrets.filters import *
+from secrets.models import Secret, SecretRole
+
+
+class SecretRoleTestCase(TestCase):
+ queryset = SecretRole.objects.all()
+ filterset = SecretRoleFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ roles = (
+ SecretRole(name='Secret Role 1', slug='secret-role-1'),
+ SecretRole(name='Secret Role 2', slug='secret-role-2'),
+ SecretRole(name='Secret Role 3', slug='secret-role-3'),
+ )
+ SecretRole.objects.bulk_create(roles)
+
+ def test_id(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id': [str(id) for id in id_list]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_name(self):
+ params = {'name': ['Secret Role 1', 'Secret Role 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_slug(self):
+ params = {'slug': ['secret-role-1', 'secret-role-2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+
+class SecretTestCase(TestCase):
+ queryset = Secret.objects.all()
+ filterset = SecretFilter
+
+ @classmethod
+ def setUpTestData(cls):
+
+ site = Site.objects.create(name='Site 1', slug='site-1')
+ manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+ device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
+ device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
+
+ devices = (
+ Device(device_type=device_type, name='Device 1', site=site, device_role=device_role),
+ Device(device_type=device_type, name='Device 2', site=site, device_role=device_role),
+ Device(device_type=device_type, name='Device 3', site=site, device_role=device_role),
+ )
+ Device.objects.bulk_create(devices)
+
+ roles = (
+ SecretRole(name='Secret Role 1', slug='secret-role-1'),
+ SecretRole(name='Secret Role 2', slug='secret-role-2'),
+ SecretRole(name='Secret Role 3', slug='secret-role-3'),
+ )
+ SecretRole.objects.bulk_create(roles)
+
+ secrets = (
+ Secret(device=devices[0], role=roles[0], name='Secret 1', plaintext='SECRET DATA'),
+ Secret(device=devices[1], role=roles[1], name='Secret 2', plaintext='SECRET DATA'),
+ Secret(device=devices[2], role=roles[2], name='Secret 3', plaintext='SECRET DATA'),
+ )
+ # Must call save() to encrypt Secrets
+ for s in secrets:
+ s.save()
+
+ def test_name(self):
+ params = {'name': ['Secret 1', 'Secret 2']}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_id__in(self):
+ id_list = self.queryset.values_list('id', flat=True)[:2]
+ params = {'id__in': ','.join([str(id) for id in id_list])}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_role(self):
+ roles = SecretRole.objects.all()[:2]
+ params = {'role_id': [roles[0].pk, roles[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'role': [roles[0].slug, roles[1].slug]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
+ def test_device(self):
+ devices = Device.objects.all()[:2]
+ params = {'device_id': [devices[0].pk, devices[1].pk]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+ params = {'device': [devices[0].name, devices[1].name]}
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
diff --git a/netbox/templates/dcim/cable_trace.html b/netbox/templates/dcim/cable_trace.html
index c9da88c46..4dd145058 100644
--- a/netbox/templates/dcim/cable_trace.html
+++ b/netbox/templates/dcim/cable_trace.html
@@ -10,7 +10,10 @@
- {{ attachment }} + {{ attachment }} | {{ attachment.size|filesizeformat }} | {{ attachment.created }} | diff --git a/netbox/templates/ipam/aggregate.html b/netbox/templates/ipam/aggregate.html index 2af8e8cc8..66281aace 100644 --- a/netbox/templates/ipam/aggregate.html +++ b/netbox/templates/ipam/aggregate.html @@ -38,6 +38,7 @@