This commit is contained in:
parent
0d1e9d88a8
commit
87b17ff26d
|
|
@ -18,6 +18,10 @@ The [module bay](./modulebay.md) into which the module is installed.
|
|||
|
||||
The [module type](./moduletype.md) which represents the physical make & model of hardware. By default, module components will be instantiated automatically from the module type when creating a new module.
|
||||
|
||||
### Profile
|
||||
|
||||
The [module type profile](./moduletypeprofile.md) associated with the selected module type. Module list views and the REST API can be filtered by this related field.
|
||||
|
||||
### Status
|
||||
|
||||
The module's operational status.
|
||||
|
|
|
|||
|
|
@ -1537,6 +1537,19 @@ class VirtualDeviceContextFilterSet(PrimaryModelFilterSet, TenancyFilterSet, Pri
|
|||
|
||||
@register_filterset
|
||||
class ModuleFilterSet(PrimaryModelFilterSet):
|
||||
profile_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='module_type__profile',
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
distinct=False,
|
||||
label=_('Profile (ID)'),
|
||||
)
|
||||
profile = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='module_type__profile__name',
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
distinct=False,
|
||||
to_field_name='name',
|
||||
label=_('Profile (name)'),
|
||||
)
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='module_type__manufacturer',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
|
|
|
|||
|
|
@ -687,6 +687,7 @@ class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
|
|||
profile_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
label=_('Profile')
|
||||
)
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
|
|
@ -1040,9 +1041,13 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, PrimaryM
|
|||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
|
||||
FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')),
|
||||
FieldSet(
|
||||
'profile_id', 'manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag',
|
||||
name=_('Hardware')
|
||||
),
|
||||
FieldSet('owner_group_id', 'owner_id', name=_('Ownership')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'manufacturer_id', 'profile_id')
|
||||
device_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
|
|
@ -1095,10 +1100,17 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, PrimaryM
|
|||
required=False,
|
||||
label=_('Manufacturer')
|
||||
)
|
||||
profile_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
required=False,
|
||||
null_option='None',
|
||||
label=_('Profile')
|
||||
)
|
||||
module_type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ModuleType.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'profile_id': '$profile_id',
|
||||
'manufacturer_id': '$manufacturer_id'
|
||||
},
|
||||
label=_('Type')
|
||||
|
|
|
|||
|
|
@ -93,6 +93,11 @@ class ModuleTable(PrimaryModelTable):
|
|||
accessor=tables.A('module_type__manufacturer'),
|
||||
linkify=True
|
||||
)
|
||||
profile = tables.Column(
|
||||
verbose_name=_('Profile'),
|
||||
accessor=tables.A('module_type__profile'),
|
||||
linkify=True,
|
||||
)
|
||||
module_type = tables.Column(
|
||||
verbose_name=_('Module Type'),
|
||||
linkify=True
|
||||
|
|
@ -107,7 +112,8 @@ class ModuleTable(PrimaryModelTable):
|
|||
class Meta(PrimaryModelTable.Meta):
|
||||
model = Module
|
||||
fields = (
|
||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag',
|
||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'profile', 'module_type', 'status',
|
||||
'serial', 'asset_tag',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import override_settings, tag
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
|
@ -1640,16 +1641,23 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
|
|||
bulk_update_data = {
|
||||
'serial': '1234ABCD',
|
||||
}
|
||||
user_permissions = ('dcim.view_modulebay', 'dcim.view_moduletype', 'dcim.view_device')
|
||||
user_permissions = (
|
||||
'dcim.view_modulebay', 'dcim.view_moduletype', 'dcim.view_moduletypeprofile', 'dcim.view_device'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
|
||||
profiles = (
|
||||
ModuleTypeProfile(name='Test CPU'),
|
||||
ModuleTypeProfile(name='Test Hard disk'),
|
||||
)
|
||||
ModuleTypeProfile.objects.bulk_create(profiles)
|
||||
device = create_test_device('Test Device 1')
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 1'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 2'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 1', profile=profiles[0]),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 2', profile=profiles[1]),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 3'),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
|
|
@ -1699,6 +1707,36 @@ class ModuleTest(APIViewTestCases.APIViewTestCase):
|
|||
},
|
||||
]
|
||||
|
||||
def test_list_objects_by_profile_id(self):
|
||||
profiles = ModuleTypeProfile.objects.filter(name__startswith='Test').order_by('name')
|
||||
self.add_permissions('dcim.view_module')
|
||||
response = self.client.get(self._get_list_url(), {'profile_id': [profiles[0].pk]}, **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data['results']), 1)
|
||||
|
||||
response = self.client.get(self._get_list_url(), {'profile_id': [profiles[1].pk]}, **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data['results']), 1)
|
||||
|
||||
response = self.client.get(
|
||||
self._get_list_url(),
|
||||
{'profile_id': [settings.FILTERS_NULL_CHOICE_VALUE]},
|
||||
**self.header,
|
||||
)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data['results']), 1)
|
||||
|
||||
def test_list_objects_by_profile(self):
|
||||
profiles = ModuleTypeProfile.objects.filter(name__startswith='Test').order_by('name')
|
||||
self.add_permissions('dcim.view_module')
|
||||
response = self.client.get(self._get_list_url(), {'profile': [profiles[0].name]}, **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data['results']), 1)
|
||||
|
||||
response = self.client.get(self._get_list_url(), {'profile': [profiles[1].name]}, **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data['results']), 1)
|
||||
|
||||
|
||||
class ConsolePortTest(Mixins.ComponentTraceMixin, APIViewTestCases.APIViewTestCase):
|
||||
model = ConsolePort
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
|
|
@ -3085,6 +3086,11 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||
Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
module_type_profiles = (
|
||||
ModuleTypeProfile(name='Test CPU'),
|
||||
ModuleTypeProfile(name='Test Hard disk'),
|
||||
)
|
||||
ModuleTypeProfile.objects.bulk_create(module_type_profiles)
|
||||
|
||||
device_types = (
|
||||
DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'),
|
||||
|
|
@ -3148,8 +3154,8 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||
Device.objects.bulk_create(devices)
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturers[0], model='Module Type 1'),
|
||||
ModuleType(manufacturer=manufacturers[1], model='Module Type 2'),
|
||||
ModuleType(manufacturer=manufacturers[0], model='Module Type 1', profile=module_type_profiles[0]),
|
||||
ModuleType(manufacturer=manufacturers[1], model='Module Type 2', profile=module_type_profiles[1]),
|
||||
ModuleType(manufacturer=manufacturers[2], model='Module Type 3'),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
|
|
@ -3265,6 +3271,19 @@ class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||
params = {'module_type': [module_types[0].model, module_types[1].model]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
|
||||
def test_profile(self):
|
||||
profiles = ModuleTypeProfile.objects.filter(name__startswith='Test').order_by('name')
|
||||
params = {'profile_id': [profiles[0].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
params = {'profile': [profiles[0].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
params = {'profile_id': [profiles[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
params = {'profile': [profiles[1].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
params = {'profile_id': [settings.FILTERS_NULL_CHOICE_VALUE]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
def test_description(self):
|
||||
params = {'description': ['foobar1', 'foobar2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ class ModuleTypeTableTest(TableTestCases.StandardTableTestCase):
|
|||
class ModuleTableTest(TableTestCases.StandardTableTestCase):
|
||||
table = ModuleTable
|
||||
|
||||
def test_profile_column_available(self):
|
||||
self.assertIn('profile', self.table.base_columns)
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
|
|
|
|||
|
|
@ -2446,13 +2446,14 @@ class ModuleTestCase(
|
|||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
|
||||
module_type_profile = ModuleTypeProfile.objects.create(name='Module Type Profile 1')
|
||||
devices = (
|
||||
create_test_device('Device 1'),
|
||||
create_test_device('Device 2'),
|
||||
)
|
||||
|
||||
module_types = (
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 1'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 1', profile=module_type_profile),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 2'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 3'),
|
||||
ModuleType(manufacturer=manufacturer, model='Module Type 4'),
|
||||
|
|
@ -2511,6 +2512,12 @@ class ModuleTestCase(
|
|||
f"{modules[2].pk},offline,Serial 1",
|
||||
)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_module_detail_includes_module_type_profile(self):
|
||||
response = self.client.get(self._get_queryset().first().get_absolute_url())
|
||||
|
||||
self.assertContains(response, 'Module Type Profile 1')
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_module_component_replication(self):
|
||||
self.add_permissions('dcim.add_module')
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@
|
|||
<th scope="row">{% trans "Model" %}</th>
|
||||
<td>{{ object.module_type|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Profile" %}</th>
|
||||
<td>{{ object.module_type.profile|linkify|placeholder }}</td>
|
||||
</tr>
|
||||
{% for k, v in object.module_type.attributes.items %}
|
||||
<tr>
|
||||
<th scope="row">{{ k }}</th>
|
||||
|
|
|
|||
Loading…
Reference in New Issue