Merge branch 'netbox-community:develop' into develop

This commit is contained in:
Omripresent 2023-05-31 12:15:19 -04:00 committed by GitHub
commit 40cc97dda1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 427 additions and 120 deletions

View File

@ -68,11 +68,12 @@ Defines how filters are evaluated against custom field values.
Controls how and whether the custom field is displayed within the NetBox user interface. Controls how and whether the custom field is displayed within the NetBox user interface.
| Option | Description | | Option | Description |
|------------|--------------------------------------| |-------------------|--------------------------------------------------|
| Read/write | Display and permit editing (default) | | Read/write | Display and permit editing (default) |
| Read-only | Display field but disallow editing | | Read-only | Display field but disallow editing |
| Hidden | Do not display field in the UI | | Hidden | Do not display field in the UI |
| Hidden (if unset) | Display in the UI only when a value has been set |
### Default ### Default

View File

@ -2,10 +2,23 @@
## v3.5.3 (FUTURE) ## v3.5.3 (FUTURE)
### Enhancements
* [#12470](https://github.com/netbox-community/netbox/issues/12470) - Collapse context data by default when viewing a rendered device configuration
* [#12562](https://github.com/netbox-community/netbox/issues/12562) - Record client IP address when logging authentication failures
* [#12597](https://github.com/netbox-community/netbox/issues/12597) - Add an option to hide custom fields only if unset
* [#12599](https://github.com/netbox-community/netbox/issues/12599) - Apply filter parameters to links in object count dashboard widgets
### Bug Fixes ### Bug Fixes
* [#11539](https://github.com/netbox-community/netbox/issues/11539) - Fix exception when applying "empty" filter lookup with invalid value
* [#11934](https://github.com/netbox-community/netbox/issues/11934) - Prevent reassignment of an IP address designated as primary for its parent object * [#11934](https://github.com/netbox-community/netbox/issues/11934) - Prevent reassignment of an IP address designated as primary for its parent object
* [#12730](https://github.com/netbox-community/netbox/issues/12730) - Fix extraneous contacts listed in object contact assignments view
* [#12627](https://github.com/netbox-community/netbox/issues/12627) - Restore hover preview for embedded image attachment tables
* [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text * [#12694](https://github.com/netbox-community/netbox/issues/12694) - Strip leading & trailing whitespace from custom link URL & text
* [#12715](https://github.com/netbox-community/netbox/issues/12715) - Use contact assignments table to display the contacts assigned to an object
* [#12742](https://github.com/netbox-community/netbox/issues/12742) - Object counts dashboard widget should support URL-compatible query filters
* [#12745](https://github.com/netbox-community/netbox/issues/12745) - Escape display text in API-backed selection widgets
--- ---

View File

@ -1,12 +1,12 @@
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema, OpenApiParameter
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.status import HTTP_400_BAD_REQUEST
from rest_framework.routers import APIRootView from rest_framework.routers import APIRootView
from rest_framework.status import HTTP_400_BAD_REQUEST
from rest_framework.viewsets import ViewSet from rest_framework.viewsets import ViewSet
from circuits.models import Circuit from circuits.models import Circuit
@ -14,7 +14,6 @@ from dcim import filtersets
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
from dcim.models import * from dcim.models import *
from dcim.svg import CableTraceSVG from dcim.svg import CableTraceSVG
from extras.api.nested_serializers import NestedConfigTemplateSerializer
from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin from extras.api.mixins import ConfigContextQuerySetMixin, ConfigTemplateRenderMixin
from ipam.models import Prefix, VLAN from ipam.models import Prefix, VLAN
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
@ -22,6 +21,7 @@ from netbox.api.metadata import ContentTypeMetadata
from netbox.api.pagination import StripCountAnnotationsPaginator from netbox.api.pagination import StripCountAnnotationsPaginator
from netbox.api.renderers import TextRenderer from netbox.api.renderers import TextRenderer
from netbox.api.viewsets import NetBoxModelViewSet from netbox.api.viewsets import NetBoxModelViewSet
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
from netbox.constants import NESTED_SERIALIZER_PREFIX from netbox.constants import NESTED_SERIALIZER_PREFIX
from utilities.api import get_serializer_for_model from utilities.api import get_serializer_for_model
from utilities.utils import count_related from utilities.utils import count_related
@ -386,7 +386,12 @@ class PlatformViewSet(NetBoxModelViewSet):
# Devices/modules # Devices/modules
# #
class DeviceViewSet(ConfigContextQuerySetMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet): class DeviceViewSet(
SequentialBulkCreatesMixin,
ConfigContextQuerySetMixin,
ConfigTemplateRenderMixin,
NetBoxModelViewSet
):
queryset = Device.objects.prefetch_related( queryset = Device.objects.prefetch_related(
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay', 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags', 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',

View File

@ -1219,6 +1219,28 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
to_field_name='name', to_field_name='name',
label=_('Device (name)'), label=_('Device (name)'),
) )
device_type_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__device_type',
queryset=DeviceType.objects.all(),
label=_('Device type (ID)'),
)
device_type = django_filters.ModelMultipleChoiceFilter(
field_name='device__device_type__model',
queryset=DeviceType.objects.all(),
to_field_name='model',
label=_('Device type (model)'),
)
device_role_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__device_role',
queryset=DeviceRole.objects.all(),
label=_('Device role (ID)'),
)
device_role = django_filters.ModelMultipleChoiceFilter(
field_name='device__device_role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label=_('Device role (slug)'),
)
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter( virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
field_name='device__virtual_chassis', field_name='device__virtual_chassis',
queryset=VirtualChassis.objects.all(), queryset=VirtualChassis.objects.all(),

View File

@ -102,13 +102,25 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
required=False, required=False,
label=_('Virtual Chassis') label=_('Virtual Chassis')
) )
device_type_id = DynamicModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
required=False,
label=_('Device type')
)
device_role_id = DynamicModelMultipleChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
label=_('Device role')
)
device_id = DynamicModelMultipleChoiceField( device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(), queryset=Device.objects.all(),
required=False, required=False,
query_params={ query_params={
'site_id': '$site_id', 'site_id': '$site_id',
'location_id': '$location_id', 'location_id': '$location_id',
'virtual_chassis_id': '$virtual_chassis_id' 'virtual_chassis_id': '$virtual_chassis_id',
'device_type_id': '$device_type_id',
'role_id': '$device_role_id'
}, },
label=_('Device') label=_('Device')
) )
@ -1070,7 +1082,8 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'speed')), ('Attributes', ('name', 'label', 'type', 'speed')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
('Connection', ('cabled', 'connected', 'occupied')), ('Connection', ('cabled', 'connected', 'occupied')),
) )
type = forms.MultipleChoiceField( type = forms.MultipleChoiceField(
@ -1089,7 +1102,8 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'speed')), ('Attributes', ('name', 'label', 'type', 'speed')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
('Connection', ('cabled', 'connected', 'occupied')), ('Connection', ('cabled', 'connected', 'occupied')),
) )
type = forms.MultipleChoiceField( type = forms.MultipleChoiceField(
@ -1108,7 +1122,8 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type')), ('Attributes', ('name', 'label', 'type')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
('Connection', ('cabled', 'connected', 'occupied')), ('Connection', ('cabled', 'connected', 'occupied')),
) )
type = forms.MultipleChoiceField( type = forms.MultipleChoiceField(
@ -1123,7 +1138,8 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type')), ('Attributes', ('name', 'label', 'type')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
('Connection', ('cabled', 'connected', 'occupied')), ('Connection', ('cabled', 'connected', 'occupied')),
) )
type = forms.MultipleChoiceField( type = forms.MultipleChoiceField(
@ -1141,8 +1157,8 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
('Addressing', ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')), ('Addressing', ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
('PoE', ('poe_mode', 'poe_type')), ('PoE', ('poe_mode', 'poe_type')),
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')), ('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
'device_id', 'vdc_id')), ('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
('Connection', ('cabled', 'connected', 'occupied')), ('Connection', ('cabled', 'connected', 'occupied')),
) )
vdc_id = DynamicModelMultipleChoiceField( vdc_id = DynamicModelMultipleChoiceField(
@ -1242,7 +1258,8 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'color')), ('Attributes', ('name', 'label', 'type', 'color')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
('Cable', ('cabled', 'occupied')), ('Cable', ('cabled', 'occupied')),
) )
model = FrontPort model = FrontPort
@ -1261,7 +1278,8 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'type', 'color')), ('Attributes', ('name', 'label', 'type', 'color')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
('Cable', ('cabled', 'occupied')), ('Cable', ('cabled', 'occupied')),
) )
type = forms.MultipleChoiceField( type = forms.MultipleChoiceField(
@ -1279,7 +1297,8 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'position')), ('Attributes', ('name', 'label', 'position')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
) )
tag = TagFilterField(model) tag = TagFilterField(model)
position = forms.CharField( position = forms.CharField(
@ -1292,7 +1311,8 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label')), ('Attributes', ('name', 'label')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
) )
tag = TagFilterField(model) tag = TagFilterField(model)
@ -1302,7 +1322,8 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
fieldsets = ( fieldsets = (
(None, ('q', 'filter_id', 'tag')), (None, ('q', 'filter_id', 'tag')),
('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')), ('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')), ('Location', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
('Device', ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
) )
role_id = DynamicModelMultipleChoiceField( role_id = DynamicModelMultipleChoiceField(
queryset=InventoryItemRole.objects.all(), queryset=InventoryItemRole.objects.all(),

View File

@ -101,6 +101,7 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp
choices=[], choices=[],
label=_('Rear ports'), label=_('Rear ports'),
help_text=_('Select one rear port assignment for each front port being created.'), help_text=_('Select one rear port assignment for each front port being created.'),
widget=forms.SelectMultiple(attrs={'size': 6})
) )
# Override fieldsets from FrontPortTemplateForm to omit rear_port_position # Override fieldsets from FrontPortTemplateForm to omit rear_port_position

View File

@ -1115,7 +1115,7 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
device_types = ( device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'), DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'), DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', u_height=2),
) )
DeviceType.objects.bulk_create(device_types) DeviceType.objects.bulk_create(device_types)
@ -1229,6 +1229,39 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
def test_rack_fit(self):
"""
Check that creating multiple devices with overlapping position fails.
"""
device = Device.objects.first()
device_type = DeviceType.objects.all()[1]
data = [
{
'device_type': device_type.pk,
'device_role': device.device_role.pk,
'site': device.site.pk,
'name': 'Test Device 7',
'rack': device.rack.pk,
'face': 'front',
'position': 1
},
{
'device_type': device_type.pk,
'device_role': device.device_role.pk,
'site': device.site.pk,
'name': 'Test Device 8',
'rack': device.rack.pk,
'face': 'front',
'position': 2
}
]
self.add_permissions('dcim.add_device')
url = reverse('dcim-api:device-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
class ModuleTest(APIViewTestCases.APIViewTestCase): class ModuleTest(APIViewTestCases.APIViewTestCase):
model = Module model = Module

View File

@ -12,6 +12,23 @@ from virtualization.models import Cluster, ClusterType
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
class DeviceComponentFilterSetTests:
def test_device_type(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)
params = {'device_type': [device_types[0].model, device_types[1].model]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_device_role(self):
device_role = DeviceRole.objects.all()[:2]
params = {'device_role_id': [device_role[0].pk, device_role[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'device_role': [device_role[0].slug, device_role[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class RegionTestCase(TestCase, ChangeLoggedFilterSetTests): class RegionTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = Region.objects.all() queryset = Region.objects.all()
filterset = RegionFilterSet filterset = RegionFilterSet
@ -1998,7 +2015,7 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests): class ConsolePortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = ConsolePort.objects.all() queryset = ConsolePort.objects.all()
filterset = ConsolePortFilterSet filterset = ConsolePortFilterSet
@ -2027,10 +2044,23 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
DeviceType.objects.bulk_create(device_types)
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -2048,10 +2078,10 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections Device(name=None, device_type=device_types[0], device_role=device_roles[0], site=sites[3]), # For cable connections
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -2165,7 +2195,7 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests): class ConsoleServerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = ConsoleServerPort.objects.all() queryset = ConsoleServerPort.objects.all()
filterset = ConsoleServerPortFilterSet filterset = ConsoleServerPortFilterSet
@ -2194,10 +2224,23 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
DeviceType.objects.bulk_create(device_types)
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -2215,10 +2258,10 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -2332,7 +2375,7 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests): class PowerPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = PowerPort.objects.all() queryset = PowerPort.objects.all()
filterset = PowerPortFilterSet filterset = PowerPortFilterSet
@ -2361,10 +2404,23 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
DeviceType.objects.bulk_create(device_types)
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -2382,10 +2438,10 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -2507,7 +2563,7 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests): class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = PowerOutlet.objects.all() queryset = PowerOutlet.objects.all()
filterset = PowerOutletFilterSet filterset = PowerOutletFilterSet
@ -2536,10 +2592,23 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
DeviceType.objects.bulk_create(device_types)
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -2557,10 +2626,10 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -2678,7 +2747,7 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests): class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = Interface.objects.all() queryset = Interface.objects.all()
filterset = InterfaceFilterSet filterset = InterfaceFilterSet
@ -2707,10 +2776,23 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
DeviceType.objects.bulk_create(device_types)
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -2728,10 +2810,10 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -3101,7 +3183,7 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests): class FrontPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = FrontPort.objects.all() queryset = FrontPort.objects.all()
filterset = FrontPortFilterSet filterset = FrontPortFilterSet
@ -3130,10 +3212,23 @@ class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
DeviceType.objects.bulk_create(device_types)
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -3151,10 +3246,10 @@ class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -3277,7 +3372,7 @@ class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests): class RearPortTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = RearPort.objects.all() queryset = RearPort.objects.all()
filterset = RearPortFilterSet filterset = RearPortFilterSet
@ -3306,10 +3401,23 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1') device_types = (
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
DeviceType.objects.bulk_create(device_types)
module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1') module_type = ModuleType.objects.create(manufacturer=manufacturer, model='Module Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -3327,10 +3435,10 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
Device(name=None, device_type=device_type, device_role=device_role, site=sites[3]), # For cable connections Device(name=None, device_type=device_types[2], device_role=device_roles[2], site=sites[3]), # For cable connections
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -3447,7 +3555,7 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests): class ModuleBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = ModuleBay.objects.all() queryset = ModuleBay.objects.all()
filterset = ModuleBayFilterSet filterset = ModuleBayFilterSet
@ -3476,9 +3584,21 @@ class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') device_types = (
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -3496,9 +3616,9 @@ class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -3564,7 +3684,7 @@ class ModuleBayTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests): class DeviceBayTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
queryset = DeviceBay.objects.all() queryset = DeviceBay.objects.all()
filterset = DeviceBayFilterSet filterset = DeviceBayFilterSet
@ -3593,9 +3713,21 @@ class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests):
Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=groups[2]),
Site(name='Site X', slug='site-x'), Site(name='Site X', slug='site-x'),
)) ))
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1') manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1') device_types = (
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-3'),
)
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)
locations = ( locations = (
Location(name='Location 1', slug='location-1', site=sites[0]), Location(name='Location 1', slug='location-1', site=sites[0]),
@ -3613,9 +3745,9 @@ class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -3694,8 +3826,19 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
) )
Manufacturer.objects.bulk_create(manufacturers) Manufacturer.objects.bulk_create(manufacturers)
device_type = DeviceType.objects.create(manufacturer=manufacturers[0], model='Model 1', slug='model-1') device_types = (
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1') DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'),
DeviceType(manufacturer=manufacturers[0], model='Device Type 2', slug='device-type-2'),
DeviceType(manufacturer=manufacturers[0], model='Device Type 3', slug='device-type-3'),
)
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)
regions = ( regions = (
Region(name='Region 1', slug='region-1'), Region(name='Region 1', slug='region-1'),
@ -3736,9 +3879,9 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
Rack.objects.bulk_create(racks) Rack.objects.bulk_create(racks)
devices = ( devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], location=locations[0], rack=racks[0]), Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], site=sites[0], location=locations[0], rack=racks[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[1], location=locations[1], rack=racks[1]), Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], site=sites[1], location=locations[1], rack=racks[1]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[2], location=locations[2], rack=racks[2]), Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], site=sites[2], location=locations[2], rack=racks[2]),
) )
Device.objects.bulk_create(devices) Device.objects.bulk_create(devices)
@ -3829,6 +3972,20 @@ class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'rack': [racks[0].name, racks[1].name]} params = {'rack': [racks[0].name, racks[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_device_type(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(), 4)
params = {'device_type': [device_types[0].model, device_types[1].model]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_device_role(self):
device_role = DeviceRole.objects.all()[:2]
params = {'device_role_id': [device_role[0].pk, device_role[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'device_role': [device_role[0].slug, device_role[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_device(self): def test_device(self):
devices = Device.objects.all()[:2] devices = Device.objects.all()[:2]
params = {'device_id': [devices[0].pk, devices[1].pk]} params = {'device_id': [devices[0].pk, devices[1].pk]}

View File

@ -56,11 +56,13 @@ class CustomFieldVisibilityChoices(ChoiceSet):
VISIBILITY_READ_WRITE = 'read-write' VISIBILITY_READ_WRITE = 'read-write'
VISIBILITY_READ_ONLY = 'read-only' VISIBILITY_READ_ONLY = 'read-only'
VISIBILITY_HIDDEN = 'hidden' VISIBILITY_HIDDEN = 'hidden'
VISIBILITY_HIDDEN_IFUNSET = 'hidden-ifunset'
CHOICES = ( CHOICES = (
(VISIBILITY_READ_WRITE, 'Read/Write'), (VISIBILITY_READ_WRITE, 'Read/Write'),
(VISIBILITY_READ_ONLY, 'Read-only'), (VISIBILITY_READ_ONLY, 'Read-only'),
(VISIBILITY_HIDDEN, 'Hidden'), (VISIBILITY_HIDDEN, 'Hidden'),
(VISIBILITY_HIDDEN_IFUNSET, 'Hidden (if unset)'),
) )

View File

@ -10,8 +10,9 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Q from django.db.models import Q
from django.http import QueryDict
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import NoReverseMatch, reverse from django.urls import NoReverseMatch, resolve, reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from extras.utils import FeatureQuery from extras.utils import FeatureQuery
@ -149,7 +150,7 @@ class ObjectCountsWidget(DashboardWidget):
filters = forms.JSONField( filters = forms.JSONField(
required=False, required=False,
label='Object filters', label='Object filters',
help_text=_("Only objects matching the specified filters will be counted") help_text=_("Filters to apply when counting the number of objects")
) )
def clean_filters(self): def clean_filters(self):
@ -158,13 +159,6 @@ class ObjectCountsWidget(DashboardWidget):
dict(data) dict(data)
except TypeError: except TypeError:
raise forms.ValidationError("Invalid format. Object filters must be passed as a dictionary.") raise forms.ValidationError("Invalid format. Object filters must be passed as a dictionary.")
for model in get_models_from_content_types(self.cleaned_data.get('models')):
try:
# Validate the filters by creating a QuerySet
model.objects.filter(**data).none()
except Exception:
model_name = model._meta.verbose_name_plural
raise forms.ValidationError(f"Invalid filter specification for {model_name}.")
return data return data
def render(self, request): def render(self, request):
@ -172,13 +166,19 @@ class ObjectCountsWidget(DashboardWidget):
for model in get_models_from_content_types(self.config['models']): for model in get_models_from_content_types(self.config['models']):
permission = get_permission_for_model(model, 'view') permission = get_permission_for_model(model, 'view')
if request.user.has_perm(permission): if request.user.has_perm(permission):
url = reverse(get_viewname(model, 'list'))
qs = model.objects.restrict(request.user, 'view') qs = model.objects.restrict(request.user, 'view')
# Apply any specified filters
if filters := self.config.get('filters'): if filters := self.config.get('filters'):
qs = qs.filter(**filters) params = QueryDict(mutable=True)
params.update(filters)
filterset = getattr(resolve(url).func.view_class, 'filterset', None)
qs = filterset(params, qs).qs
url = f'{url}?{params.urlencode()}'
object_count = qs.count object_count = qs.count
counts.append((model, object_count)) counts.append((model, object_count, url))
else: else:
counts.append((model, None)) counts.append((model, None, None))
return render_to_string(self.template_name, { return render_to_string(self.template_name, {
'counts': counts, 'counts': counts,

View File

@ -22,6 +22,14 @@ __all__ = (
'WebhookTable', 'WebhookTable',
) )
IMAGEATTACHMENT_IMAGE = '''
{% if record.image %}
<a class="image-preview" href="{{ record.image.url }}" target="_blank">{{ record }}</a>
{% else %}
&mdash;
{% endif %}
'''
class CustomFieldTable(NetBoxTable): class CustomFieldTable(NetBoxTable):
name = tables.Column( name = tables.Column(
@ -96,6 +104,9 @@ class ImageAttachmentTable(NetBoxTable):
parent = tables.Column( parent = tables.Column(
linkify=True linkify=True
) )
image = tables.TemplateColumn(
template_code=IMAGEATTACHMENT_IMAGE,
)
size = tables.Column( size = tables.Column(
orderable=False, orderable=False,
verbose_name='Size (bytes)' verbose_name='Size (bytes)'

View File

@ -15,11 +15,12 @@ from utilities.api import get_serializer_for_model
__all__ = ( __all__ = (
'BriefModeMixin', 'BriefModeMixin',
'BulkDestroyModelMixin',
'BulkUpdateModelMixin', 'BulkUpdateModelMixin',
'CustomFieldsMixin', 'CustomFieldsMixin',
'ExportTemplatesMixin', 'ExportTemplatesMixin',
'BulkDestroyModelMixin',
'ObjectValidationMixin', 'ObjectValidationMixin',
'SequentialBulkCreatesMixin',
) )
@ -94,6 +95,30 @@ class ExportTemplatesMixin:
return super().list(request, *args, **kwargs) return super().list(request, *args, **kwargs)
class SequentialBulkCreatesMixin:
"""
Perform bulk creation of new objects sequentially, rather than all at once. This ensures that any validation
which depends on the evaluation of existing objects (such as checking for free space within a rack) functions
appropriately.
"""
@transaction.atomic
def create(self, request, *args, **kwargs):
if not isinstance(request.data, list):
# Creating a single object
return super().create(request, *args, **kwargs)
return_data = []
for data in request.data:
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return_data.append(serializer.data)
headers = self.get_success_headers(serializer.data)
return Response(return_data, status=status.HTTP_201_CREATED, headers=headers)
class BulkUpdateModelMixin: class BulkUpdateModelMixin:
""" """
Support bulk modification of objects using the list endpoint for a model. Accepts a PATCH action with a list of one Support bulk modification of objects using the list endpoint for a model. Accepts a PATCH action with a list of one

View File

@ -197,11 +197,15 @@ class CustomFieldsMixin(models.Model):
data = {} data = {}
for field in CustomField.objects.get_for_model(self): for field in CustomField.objects.get_for_model(self):
# Skip fields that are hidden if 'omit_hidden' is set
if omit_hidden and field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN:
continue
value = self.custom_field_data.get(field.name) value = self.custom_field_data.get(field.name)
# Skip fields that are hidden if 'omit_hidden' is set
if omit_hidden:
if field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN:
continue
if field.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET and not value:
continue
data[field] = field.deserialize(value) data[field] = field.deserialize(value)
return data return data
@ -227,6 +231,8 @@ class CustomFieldsMixin(models.Model):
for cf in visible_custom_fields: for cf in visible_custom_fields:
value = self.custom_field_data.get(cf.name) value = self.custom_field_data.get(cf.name)
if value in (None, []) and cf.ui_visibility == CustomFieldVisibilityChoices.VISIBILITY_HIDDEN_IFUNSET:
continue
value = cf.deserialize(value) value = cf.deserialize(value)
groups[cf.group_name][cf] = value groups[cf.group_name][cf] = value

Binary file not shown.

Binary file not shown.

View File

@ -2,9 +2,10 @@ import { getElements, isTruthy } from './util';
import { initButtons } from './buttons'; import { initButtons } from './buttons';
import { initSelect } from './select'; import { initSelect } from './select';
import { initObjectSelector } from './objectSelector'; import { initObjectSelector } from './objectSelector';
import { initBootstrap } from './bs';
function initDepedencies(): void { function initDepedencies(): void {
for (const init of [initButtons, initSelect, initObjectSelector]) { for (const init of [initButtons, initSelect, initObjectSelector, initBootstrap]) {
init(); init();
} }
} }
@ -22,4 +23,8 @@ export function initHtmx(): void {
} }
} }
} }
for (const element of getElements('[hx-trigger=load]')) {
element.addEventListener('htmx:afterSettle', initDepedencies);
}
} }

View File

@ -1,10 +1,8 @@
{% load helpers %}
{% if counts %} {% if counts %}
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
{% for model, count in counts %} {% for model, count, url in counts %}
{% if count != None %} {% if count != None %}
<a href="{% url model|viewname:"list" %}" class="list-group-item list-group-item-action"> <a href="{{ url }}" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between align-items-center"> <div class="d-flex w-100 justify-content-between align-items-center">
{{ model|meta:"verbose_name_plural"|bettertitle }} {{ model|meta:"verbose_name_plural"|bettertitle }}
<h6 class="mb-1">{{ count }}</h6> <h6 class="mb-1">{{ count }}</h6>

View File

@ -15,25 +15,32 @@ from .models import *
class ObjectContactsView(generic.ObjectChildrenView): class ObjectContactsView(generic.ObjectChildrenView):
child_model = Contact child_model = ContactAssignment
table = tables.ContactTable table = tables.ContactAssignmentTable
filterset = filtersets.ContactFilterSet filterset = filtersets.ContactAssignmentFilterSet
template_name = 'tenancy/object_contacts.html' template_name = 'tenancy/object_contacts.html'
tab = ViewTab( tab = ViewTab(
label=_('Contacts'), label=_('Contacts'),
badge=lambda obj: obj.contacts.count(), badge=lambda obj: obj.contacts.count(),
permission='tenancy.view_contact', permission='tenancy.view_contactassignment',
weight=5000 weight=5000
) )
def get_children(self, request, parent): def get_children(self, request, parent):
return Contact.objects.annotate( return ContactAssignment.objects.restrict(request.user, 'view').filter(
assignment_count=count_related(ContactAssignment, 'contact') content_type=ContentType.objects.get_for_model(parent),
).restrict(request.user, 'view').filter( object_id=parent.pk
assignments__content_type=ContentType.objects.get_for_model(parent),
assignments__object_id=parent.pk
) )
def get_table(self, *args, **kwargs):
table = super().get_table(*args, **kwargs)
# Hide object columns
table.columns.hide('content_type')
table.columns.hide('object')
return table
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
return { return {
'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html', 'base_template': f'{instance._meta.app_label}/{instance._meta.model_name}.html',

View File

@ -19,7 +19,7 @@ drf-spectacular==0.26.2
drf-spectacular-sidecar==2023.5.1 drf-spectacular-sidecar==2023.5.1
dulwich==0.21.5 dulwich==0.21.5
feedparser==6.0.10 feedparser==6.0.10
graphene-django==3.0.2 graphene-django==3.0.0
gunicorn==20.1.0 gunicorn==20.1.0
Jinja2==3.1.2 Jinja2==3.1.2
Markdown==3.3.7 Markdown==3.3.7