mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
Add documentation & tests
This commit is contained in:
parent
94b3aae0a2
commit
7edc67ed8a
@ -43,3 +43,11 @@ The numeric weight of the module, including a unit designation (e.g. 3 kilograms
|
||||
### Airflow
|
||||
|
||||
The direction in which air circulates through the device chassis for cooling.
|
||||
|
||||
### Profile
|
||||
|
||||
The assigned [profile](./moduletypeprofile.md) for the type of module. Profiles can be used to classify module types by function (e.g. power supply, hard disk, etc.), and they support the addition of user-configurable attributes on module types. The assignment of a module type to a profile is optional.
|
||||
|
||||
### Attributes
|
||||
|
||||
Depending on the module type's assigned [profile](./moduletypeprofile.md) (if any), one or more user-defined attributes may be available to configure.
|
||||
|
40
docs/models/dcim/moduletypeprofile.md
Normal file
40
docs/models/dcim/moduletypeprofile.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Module Type Profiles
|
||||
|
||||
!!! info "This model was introduced in NetBox v4.3."
|
||||
|
||||
Each [module type](./moduletype.md) may optionally be assigned a profile according to its classification. A profile can extend module types with user-configured attributes. For example, you might want to specify the input current and voltage of a power supply, or the clock speed and number of cores for a processor.
|
||||
|
||||
Module type attributes are managed via the configuration of a [JSON schema](https://json-schema.org/) on the profile. For example, the following schema introduces three module type attributes, two of which are designated as required attributes.
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"title": "Disk type",
|
||||
"enum": ["HD", "SSD", "NVME"],
|
||||
"default": "HD"
|
||||
},
|
||||
"capacity": {
|
||||
"type": "integer",
|
||||
"title": "Capacity (GB)",
|
||||
"description": "Gross disk size"
|
||||
},
|
||||
"speed": {
|
||||
"type": "integer",
|
||||
"title": "Speed (RPM)"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type", "capacity"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The assignment of module types to a profile is optional. The designation of a schema for a profile is also optional: A profile can be used simply as a mechanism for classifying module types if the addition of custom attributes is not needed.
|
||||
|
||||
## Fields
|
||||
|
||||
### Schema
|
||||
|
||||
This field holds the [JSON schema](https://json-schema.org/) for the profile. The configured JSON schema must be valid (or the field must be null).
|
@ -601,7 +601,7 @@ class ModuleBayTemplateType(ModularComponentTemplateType):
|
||||
pagination=True
|
||||
)
|
||||
class ModuleTypeProfileType(NetBoxObjectType):
|
||||
moduletypes: List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]
|
||||
module_types: List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
|
@ -629,6 +629,70 @@ class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
|
||||
]
|
||||
|
||||
|
||||
class ModuleTypeProfileTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ModuleTypeProfile
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||
SCHEMAS = [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
create_data = [
|
||||
{
|
||||
'name': 'Module Type Profile 4',
|
||||
'schema': SCHEMAS[0],
|
||||
},
|
||||
{
|
||||
'name': 'Module Type Profile 5',
|
||||
'schema': SCHEMAS[1],
|
||||
},
|
||||
{
|
||||
'name': 'Module Type Profile 6',
|
||||
'schema': SCHEMAS[2],
|
||||
},
|
||||
]
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
'comments': 'New comments',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
module_type_profiles = (
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 1',
|
||||
schema=cls.SCHEMAS[0]
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 2',
|
||||
schema=cls.SCHEMAS[1]
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 3',
|
||||
schema=cls.SCHEMAS[2]
|
||||
),
|
||||
)
|
||||
ModuleTypeProfile.objects.bulk_create(module_type_profiles)
|
||||
|
||||
|
||||
class ConsolePortTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ConsolePortTemplate
|
||||
brief_fields = ['description', 'display', 'id', 'name', 'url']
|
||||
|
@ -1488,6 +1488,15 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
filterset = ModuleTypeFilterSet
|
||||
ignore_fields = ['attribute_data']
|
||||
|
||||
PROFILE_SCHEMA = {
|
||||
"properties": {
|
||||
"string": {"type": "string"},
|
||||
"integer": {"type": "integer"},
|
||||
"number": {"type": "number"},
|
||||
"boolean": {"type": "boolean"},
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
@ -1497,6 +1506,21 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
module_type_profiles = (
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 1',
|
||||
schema=cls.PROFILE_SCHEMA
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 2',
|
||||
schema=cls.PROFILE_SCHEMA
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 3',
|
||||
schema=cls.PROFILE_SCHEMA
|
||||
),
|
||||
)
|
||||
ModuleTypeProfile.objects.bulk_create(module_type_profiles)
|
||||
|
||||
module_types = (
|
||||
ModuleType(
|
||||
@ -1506,7 +1530,14 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
weight=10,
|
||||
weight_unit=WeightUnitChoices.UNIT_POUND,
|
||||
description='foobar1',
|
||||
airflow=ModuleAirflowChoices.FRONT_TO_REAR
|
||||
airflow=ModuleAirflowChoices.FRONT_TO_REAR,
|
||||
profile=module_type_profiles[0],
|
||||
attribute_data={
|
||||
'string': 'string1',
|
||||
'integer': 1,
|
||||
'number': 1.0,
|
||||
'boolean': True,
|
||||
}
|
||||
),
|
||||
ModuleType(
|
||||
manufacturer=manufacturers[1],
|
||||
@ -1515,7 +1546,14 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
weight=20,
|
||||
weight_unit=WeightUnitChoices.UNIT_POUND,
|
||||
description='foobar2',
|
||||
airflow=ModuleAirflowChoices.REAR_TO_FRONT
|
||||
airflow=ModuleAirflowChoices.REAR_TO_FRONT,
|
||||
profile=module_type_profiles[1],
|
||||
attribute_data={
|
||||
'string': 'string2',
|
||||
'integer': 2,
|
||||
'number': 2.0,
|
||||
'boolean_': False,
|
||||
}
|
||||
),
|
||||
ModuleType(
|
||||
manufacturer=manufacturers[2],
|
||||
@ -1523,7 +1561,14 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
part_number='Part Number 3',
|
||||
weight=30,
|
||||
weight_unit=WeightUnitChoices.UNIT_KILOGRAM,
|
||||
description='foobar3'
|
||||
description='foobar3',
|
||||
profile=module_type_profiles[2],
|
||||
attribute_data={
|
||||
'string': 'string3',
|
||||
'integer': 3,
|
||||
'number': 3.0,
|
||||
'boolean': None,
|
||||
}
|
||||
),
|
||||
)
|
||||
ModuleType.objects.bulk_create(module_types)
|
||||
@ -1642,6 +1687,82 @@ class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
params = {'airflow': RackAirflowChoices.FRONT_TO_REAR}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_profile(self):
|
||||
profiles = ModuleTypeProfile.objects.all()[:2]
|
||||
params = {'profile_id': [profiles[0].pk, profiles[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'profile': [profiles[0].name, profiles[1].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_profile_attributes(self):
|
||||
params = {'attr_string': 'string1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
params = {'attr_integer': '1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
params = {'attr_number': '2.0'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
params = {'attr_boolean': 'true'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
|
||||
class ModuleTypeProfileTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = ModuleTypeProfile.objects.all()
|
||||
filterset = ModuleTypeProfileFilterSet
|
||||
ignore_fields = ['schema']
|
||||
|
||||
SCHEMAS = [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
module_type_profiles = (
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 1',
|
||||
description='foobar1',
|
||||
schema=cls.SCHEMAS[0]
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 2',
|
||||
description='foobar2 2',
|
||||
schema=cls.SCHEMAS[1]
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 3',
|
||||
description='foobar3',
|
||||
schema=cls.SCHEMAS[2]
|
||||
),
|
||||
)
|
||||
ModuleTypeProfile.objects.bulk_create(module_type_profiles)
|
||||
|
||||
def test_q(self):
|
||||
params = {'q': 'foobar1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Module Type Profile 1', 'Module Type Profile 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ConsolePortTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests, ChangeLoggedFilterSetTests):
|
||||
queryset = ConsolePortTemplate.objects.all()
|
||||
|
@ -1,3 +1,4 @@
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
@ -1305,6 +1306,79 @@ front-ports:
|
||||
self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8')
|
||||
|
||||
|
||||
class ModuleTypeProfileTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
model = ModuleTypeProfile
|
||||
|
||||
SCHEMAS = [
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"foo": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
module_type_profiles = (
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 1',
|
||||
schema=cls.SCHEMAS[0]
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 2',
|
||||
schema=cls.SCHEMAS[1]
|
||||
),
|
||||
ModuleTypeProfile(
|
||||
name='Module Type Profile 3',
|
||||
schema=cls.SCHEMAS[2]
|
||||
),
|
||||
)
|
||||
ModuleTypeProfile.objects.bulk_create(module_type_profiles)
|
||||
|
||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Module Type Profile X',
|
||||
'description': 'A new profile',
|
||||
'schema': json.dumps(cls.SCHEMAS[0]),
|
||||
'tags': [t.pk for t in tags],
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"name,schema",
|
||||
f"Module Type Profile 4,{json.dumps(cls.SCHEMAS[0])}",
|
||||
f"Module Type Profile 5,{json.dumps(cls.SCHEMAS[1])}",
|
||||
f"Module Type Profile 6,{json.dumps(cls.SCHEMAS[2])}",
|
||||
)
|
||||
|
||||
cls.csv_update_data = (
|
||||
"id,description",
|
||||
f"{module_type_profiles[0].pk},New description",
|
||||
f"{module_type_profiles[1].pk},New description",
|
||||
f"{module_type_profiles[2].pk},New description",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# DeviceType components
|
||||
#
|
||||
|
Loading…
Reference in New Issue
Block a user