mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 02:48:38 -06:00
Add ModuleTypeProfile & related fields
This commit is contained in:
parent
65ad972a1c
commit
678e6f1bd5
@ -82,6 +82,10 @@ gunicorn
|
|||||||
# https://jinja.palletsprojects.com/changes/
|
# https://jinja.palletsprojects.com/changes/
|
||||||
Jinja2
|
Jinja2
|
||||||
|
|
||||||
|
# JSON schema validation
|
||||||
|
# https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst
|
||||||
|
jsonschema
|
||||||
|
|
||||||
# Simple markup language for rendering HTML
|
# Simple markup language for rendering HTML
|
||||||
# https://python-markdown.github.io/changelog/
|
# https://python-markdown.github.io/changelog/
|
||||||
Markdown
|
Markdown
|
||||||
|
@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.models import DeviceType, ModuleType
|
from dcim.models import DeviceType, ModuleType, ModuleTypeProfile
|
||||||
from netbox.api.fields import ChoiceField, RelatedObjectCountField
|
from netbox.api.fields import ChoiceField, RelatedObjectCountField
|
||||||
from netbox.api.serializers import NetBoxModelSerializer
|
from netbox.api.serializers import NetBoxModelSerializer
|
||||||
from netbox.choices import *
|
from netbox.choices import *
|
||||||
@ -13,6 +13,7 @@ from .platforms import PlatformSerializer
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'DeviceTypeSerializer',
|
'DeviceTypeSerializer',
|
||||||
|
'ModuleTypeProfileSerializer',
|
||||||
'ModuleTypeSerializer',
|
'ModuleTypeSerializer',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,7 +63,23 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
|
|||||||
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description', 'device_count')
|
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description', 'device_count')
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileSerializer(NetBoxModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ModuleTypeProfile
|
||||||
|
fields = [
|
||||||
|
'id', 'url', 'display_url', 'display', 'name', 'description', 'schema', 'comments', 'tags', 'custom_fields',
|
||||||
|
'created', 'last_updated',
|
||||||
|
]
|
||||||
|
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeSerializer(NetBoxModelSerializer):
|
class ModuleTypeSerializer(NetBoxModelSerializer):
|
||||||
|
profile = ModuleTypeProfileSerializer(
|
||||||
|
nested=True,
|
||||||
|
required=False,
|
||||||
|
allow_null=True
|
||||||
|
)
|
||||||
manufacturer = ManufacturerSerializer(
|
manufacturer = ManufacturerSerializer(
|
||||||
nested=True
|
nested=True
|
||||||
)
|
)
|
||||||
@ -82,8 +99,8 @@ class ModuleTypeSerializer(NetBoxModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'part_number', 'airflow',
|
'id', 'url', 'display_url', 'display', 'profile', 'manufacturer', 'model', 'part_number', 'airflow',
|
||||||
'weight', 'weight_unit', 'description', 'comments', 'tags', 'custom_fields',
|
'weight', 'weight_unit', 'description', 'attributes', 'comments', 'tags', 'custom_fields', 'created',
|
||||||
'created', 'last_updated',
|
'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'description')
|
brief_fields = ('id', 'url', 'display', 'profile', 'manufacturer', 'model', 'description')
|
||||||
|
@ -21,6 +21,7 @@ router.register('rack-reservations', views.RackReservationViewSet)
|
|||||||
router.register('manufacturers', views.ManufacturerViewSet)
|
router.register('manufacturers', views.ManufacturerViewSet)
|
||||||
router.register('device-types', views.DeviceTypeViewSet)
|
router.register('device-types', views.DeviceTypeViewSet)
|
||||||
router.register('module-types', views.ModuleTypeViewSet)
|
router.register('module-types', views.ModuleTypeViewSet)
|
||||||
|
router.register('module-type-profiles', views.ModuleTypeProfileViewSet)
|
||||||
|
|
||||||
# Device type components
|
# Device type components
|
||||||
router.register('console-port-templates', views.ConsolePortTemplateViewSet)
|
router.register('console-port-templates', views.ConsolePortTemplateViewSet)
|
||||||
|
@ -269,6 +269,12 @@ class DeviceTypeViewSet(NetBoxModelViewSet):
|
|||||||
filterset_class = filtersets.DeviceTypeFilterSet
|
filterset_class = filtersets.DeviceTypeFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileViewSet(NetBoxModelViewSet):
|
||||||
|
queryset = ModuleTypeProfile.objects.all()
|
||||||
|
serializer_class = serializers.ModuleTypeProfileSerializer
|
||||||
|
filterset_class = filtersets.ModuleTypeProfileFilterSet
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeViewSet(NetBoxModelViewSet):
|
class ModuleTypeViewSet(NetBoxModelViewSet):
|
||||||
queryset = ModuleType.objects.all()
|
queryset = ModuleType.objects.all()
|
||||||
serializer_class = serializers.ModuleTypeSerializer
|
serializer_class = serializers.ModuleTypeSerializer
|
||||||
|
@ -59,6 +59,7 @@ __all__ = (
|
|||||||
'ModuleBayTemplateFilterSet',
|
'ModuleBayTemplateFilterSet',
|
||||||
'ModuleFilterSet',
|
'ModuleFilterSet',
|
||||||
'ModuleTypeFilterSet',
|
'ModuleTypeFilterSet',
|
||||||
|
'ModuleTypeProfileFilterSet',
|
||||||
'PathEndpointFilterSet',
|
'PathEndpointFilterSet',
|
||||||
'PlatformFilterSet',
|
'PlatformFilterSet',
|
||||||
'PowerConnectionFilterSet',
|
'PowerConnectionFilterSet',
|
||||||
@ -674,7 +675,33 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
|||||||
return queryset.exclude(inventoryitemtemplates__isnull=value)
|
return queryset.exclude(inventoryitemtemplates__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileFilterSet(NetBoxModelFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ModuleTypeProfile
|
||||||
|
fields = ('id', 'name', 'description')
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(name__icontains=value) |
|
||||||
|
Q(description__icontains=value) |
|
||||||
|
Q(comments__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeFilterSet(NetBoxModelFilterSet):
|
class ModuleTypeFilterSet(NetBoxModelFilterSet):
|
||||||
|
profile_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=ModuleTypeProfile.objects.all(),
|
||||||
|
label=_('Profile (ID)'),
|
||||||
|
)
|
||||||
|
profile = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='profile__name',
|
||||||
|
queryset=ModuleTypeProfile.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('Profile (name)'),
|
||||||
|
)
|
||||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
label=_('Manufacturer (ID)'),
|
label=_('Manufacturer (ID)'),
|
||||||
|
@ -46,6 +46,7 @@ __all__ = (
|
|||||||
'ModuleBayBulkEditForm',
|
'ModuleBayBulkEditForm',
|
||||||
'ModuleBayTemplateBulkEditForm',
|
'ModuleBayTemplateBulkEditForm',
|
||||||
'ModuleTypeBulkEditForm',
|
'ModuleTypeBulkEditForm',
|
||||||
|
'ModuleTypeProfileBulkEditForm',
|
||||||
'PlatformBulkEditForm',
|
'PlatformBulkEditForm',
|
||||||
'PowerFeedBulkEditForm',
|
'PowerFeedBulkEditForm',
|
||||||
'PowerOutletBulkEditForm',
|
'PowerOutletBulkEditForm',
|
||||||
@ -574,7 +575,31 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
schema = forms.JSONField(
|
||||||
|
label=_('Schema'),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
description = forms.CharField(
|
||||||
|
label=_('Description'),
|
||||||
|
max_length=200,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
|
model = ModuleTypeProfile
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('name', 'description', 'schema', name=_('Profile')),
|
||||||
|
)
|
||||||
|
nullable_fields = ('description', 'comments')
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
|
profile = DynamicModelChoiceField(
|
||||||
|
label=_('Profile'),
|
||||||
|
queryset=ModuleTypeProfile.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
label=_('Manufacturer'),
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
@ -609,7 +634,7 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('manufacturer', 'part_number', 'description', name=_('Module Type')),
|
FieldSet('profile', 'manufacturer', 'part_number', 'description', name=_('Module Type')),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'airflow',
|
'airflow',
|
||||||
InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
|
InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
|
||||||
|
@ -39,6 +39,7 @@ __all__ = (
|
|||||||
'ModuleImportForm',
|
'ModuleImportForm',
|
||||||
'ModuleBayImportForm',
|
'ModuleBayImportForm',
|
||||||
'ModuleTypeImportForm',
|
'ModuleTypeImportForm',
|
||||||
|
'ModuleTypeProfileImportForm',
|
||||||
'PlatformImportForm',
|
'PlatformImportForm',
|
||||||
'PowerFeedImportForm',
|
'PowerFeedImportForm',
|
||||||
'PowerOutletImportForm',
|
'PowerOutletImportForm',
|
||||||
@ -427,7 +428,22 @@ class DeviceTypeImportForm(NetBoxModelImportForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileImportForm(NetBoxModelImportForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ModuleTypeProfile
|
||||||
|
fields = [
|
||||||
|
'name', 'description', 'schema', 'comments', 'tags',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeImportForm(NetBoxModelImportForm):
|
class ModuleTypeImportForm(NetBoxModelImportForm):
|
||||||
|
profile = forms.ModelChoiceField(
|
||||||
|
label=_('Profile'),
|
||||||
|
queryset=ModuleTypeProfile.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
required=False
|
||||||
|
)
|
||||||
manufacturer = forms.ModelChoiceField(
|
manufacturer = forms.ModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
label=_('Manufacturer'),
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
|
@ -39,6 +39,7 @@ __all__ = (
|
|||||||
'ModuleFilterForm',
|
'ModuleFilterForm',
|
||||||
'ModuleBayFilterForm',
|
'ModuleBayFilterForm',
|
||||||
'ModuleTypeFilterForm',
|
'ModuleTypeFilterForm',
|
||||||
|
'ModuleTypeProfileFilterForm',
|
||||||
'PlatformFilterForm',
|
'PlatformFilterForm',
|
||||||
'PowerConnectionFilterForm',
|
'PowerConnectionFilterForm',
|
||||||
'PowerFeedFilterForm',
|
'PowerFeedFilterForm',
|
||||||
@ -602,11 +603,19 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileFilterForm(NetBoxModelFilterSetForm):
|
||||||
|
model = ModuleTypeProfile
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
|
)
|
||||||
|
selector_fields = ('filter_id', 'q')
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('q', 'filter_id', 'tag'),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
FieldSet('manufacturer_id', 'part_number', 'airflow', name=_('Hardware')),
|
FieldSet('profile_id', 'manufacturer_id', 'part_number', 'airflow', name=_('Hardware')),
|
||||||
FieldSet(
|
FieldSet(
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||||
'pass_through_ports', name=_('Components')
|
'pass_through_ports', name=_('Components')
|
||||||
@ -614,6 +623,11 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
||||||
|
profile_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=ModuleTypeProfile.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Profile')
|
||||||
|
)
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -48,6 +48,7 @@ __all__ = (
|
|||||||
'ModuleBayForm',
|
'ModuleBayForm',
|
||||||
'ModuleBayTemplateForm',
|
'ModuleBayTemplateForm',
|
||||||
'ModuleTypeForm',
|
'ModuleTypeForm',
|
||||||
|
'ModuleTypeProfileForm',
|
||||||
'PlatformForm',
|
'PlatformForm',
|
||||||
'PopulateDeviceBayForm',
|
'PopulateDeviceBayForm',
|
||||||
'PowerFeedForm',
|
'PowerFeedForm',
|
||||||
@ -404,7 +405,26 @@ class DeviceTypeForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileForm(NetBoxModelForm):
|
||||||
|
comments = CommentField()
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('name', 'description', 'schema', 'tags', name=_('Profile')),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ModuleTypeProfile
|
||||||
|
fields = [
|
||||||
|
'name', 'description', 'schema', 'comments', 'tags',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeForm(NetBoxModelForm):
|
class ModuleTypeForm(NetBoxModelForm):
|
||||||
|
profile = DynamicModelChoiceField(
|
||||||
|
label=_('Profile'),
|
||||||
|
queryset=ModuleTypeProfile.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
manufacturer = DynamicModelChoiceField(
|
manufacturer = DynamicModelChoiceField(
|
||||||
label=_('Manufacturer'),
|
label=_('Manufacturer'),
|
||||||
queryset=Manufacturer.objects.all()
|
queryset=Manufacturer.objects.all()
|
||||||
@ -412,15 +432,16 @@ class ModuleTypeForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
FieldSet('manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
|
FieldSet('profile', 'manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
|
||||||
FieldSet('airflow', 'weight', 'weight_unit', name=_('Chassis'))
|
FieldSet('attributes', name=_('Profile Attributes')),
|
||||||
|
FieldSet('airflow', 'weight', 'weight_unit', name=_('Hardware')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fields = [
|
fields = [
|
||||||
'manufacturer', 'model', 'part_number', 'airflow', 'weight', 'weight_unit', 'description',
|
'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
|
||||||
'comments', 'tags',
|
'attributes', 'comments', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ __all__ = (
|
|||||||
'ModuleBayFilter',
|
'ModuleBayFilter',
|
||||||
'ModuleBayTemplateFilter',
|
'ModuleBayTemplateFilter',
|
||||||
'ModuleTypeFilter',
|
'ModuleTypeFilter',
|
||||||
|
'ModuleTypeProfileFilter',
|
||||||
'PlatformFilter',
|
'PlatformFilter',
|
||||||
'PowerFeedFilter',
|
'PowerFeedFilter',
|
||||||
'PowerOutletFilter',
|
'PowerOutletFilter',
|
||||||
@ -559,6 +560,11 @@ class ModuleBayTemplateFilter(ModularComponentTemplateFilterMixin):
|
|||||||
position: FilterLookup[str] | None = strawberry_django.filter_field()
|
position: FilterLookup[str] | None = strawberry_django.filter_field()
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.filter(models.ModuleTypeProfile, lookups=True)
|
||||||
|
class ModuleTypeProfileFilter(PrimaryModelFilterMixin):
|
||||||
|
name: FilterLookup[str] | None = strawberry_django.filter_field()
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.filter(models.ModuleType, lookups=True)
|
@strawberry_django.filter(models.ModuleType, lookups=True)
|
||||||
class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, WeightFilterMixin):
|
class ModuleTypeFilter(ImageAttachmentFilterMixin, PrimaryModelFilterMixin, WeightFilterMixin):
|
||||||
manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
manufacturer: Annotated['ManufacturerFilter', strawberry.lazy('dcim.graphql.filters')] | None = (
|
||||||
|
@ -77,6 +77,9 @@ class DCIMQuery:
|
|||||||
module_bay_template: ModuleBayTemplateType = strawberry_django.field()
|
module_bay_template: ModuleBayTemplateType = strawberry_django.field()
|
||||||
module_bay_template_list: List[ModuleBayTemplateType] = strawberry_django.field()
|
module_bay_template_list: List[ModuleBayTemplateType] = strawberry_django.field()
|
||||||
|
|
||||||
|
module_type_profile: ModuleTypeProfileType = strawberry_django.field()
|
||||||
|
module_type_profile_list: List[ModuleTypeProfileType] = strawberry_django.field()
|
||||||
|
|
||||||
module_type: ModuleTypeType = strawberry_django.field()
|
module_type: ModuleTypeType = strawberry_django.field()
|
||||||
module_type_list: List[ModuleTypeType] = strawberry_django.field()
|
module_type_list: List[ModuleTypeType] = strawberry_django.field()
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ __all__ = (
|
|||||||
'ModuleType',
|
'ModuleType',
|
||||||
'ModuleBayType',
|
'ModuleBayType',
|
||||||
'ModuleBayTemplateType',
|
'ModuleBayTemplateType',
|
||||||
|
'ModuleTypeProfileType',
|
||||||
'ModuleTypeType',
|
'ModuleTypeType',
|
||||||
'PlatformType',
|
'PlatformType',
|
||||||
'PowerFeedType',
|
'PowerFeedType',
|
||||||
@ -593,6 +594,16 @@ class ModuleBayTemplateType(ModularComponentTemplateType):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@strawberry_django.type(
|
||||||
|
models.ModuleTypeProfile,
|
||||||
|
fields='__all__',
|
||||||
|
filters=ModuleTypeProfileFilter,
|
||||||
|
pagination=True
|
||||||
|
)
|
||||||
|
class ModuleTypeProfileType(NetBoxObjectType):
|
||||||
|
moduletypes: List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]
|
||||||
|
|
||||||
|
|
||||||
@strawberry_django.type(
|
@strawberry_django.type(
|
||||||
models.ModuleType,
|
models.ModuleType,
|
||||||
fields='__all__',
|
fields='__all__',
|
||||||
@ -600,6 +611,7 @@ class ModuleBayTemplateType(ModularComponentTemplateType):
|
|||||||
pagination=True
|
pagination=True
|
||||||
)
|
)
|
||||||
class ModuleTypeType(NetBoxObjectType):
|
class ModuleTypeType(NetBoxObjectType):
|
||||||
|
profile: Annotated["ModuleTypeProfileType", strawberry.lazy('dcim.graphql.types')] | None
|
||||||
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
|
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
|
||||||
|
|
||||||
frontporttemplates: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
|
frontporttemplates: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
|
||||||
|
57
netbox/dcim/migrations/0203_moduletypeprofile.py
Normal file
57
netbox/dcim/migrations/0203_moduletypeprofile.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import utilities.json
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0202_location_comments_region_comments_sitegroup_comments'),
|
||||||
|
('extras', '0125_exporttemplate_file_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ModuleTypeProfile',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||||
|
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
(
|
||||||
|
'custom_field_data',
|
||||||
|
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
|
||||||
|
),
|
||||||
|
('description', models.CharField(blank=True, max_length=200)),
|
||||||
|
('comments', models.TextField(blank=True)),
|
||||||
|
('name', models.CharField(max_length=100, unique=True)),
|
||||||
|
('schema', models.JSONField()),
|
||||||
|
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'module type profile',
|
||||||
|
'verbose_name_plural': 'module type profiles',
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='moduletype',
|
||||||
|
name='attributes',
|
||||||
|
field=models.JSONField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='moduletype',
|
||||||
|
name='profile',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name='module_types',
|
||||||
|
to='dcim.moduletypeprofile',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='moduletype',
|
||||||
|
options={'ordering': ('profile', 'manufacturer', 'model')},
|
||||||
|
),
|
||||||
|
]
|
@ -15,9 +15,34 @@ from .device_components import *
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'Module',
|
'Module',
|
||||||
'ModuleType',
|
'ModuleType',
|
||||||
|
'ModuleTypeProfile',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfile(PrimaryModel):
|
||||||
|
"""
|
||||||
|
A profile which defines the attributes which can be set on one or more ModuleTypes.
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
verbose_name=_('name'),
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
schema = models.JSONField(
|
||||||
|
verbose_name=_('schema')
|
||||||
|
)
|
||||||
|
|
||||||
|
clone_fields = ('schema',)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('name',)
|
||||||
|
verbose_name = _('module type profile')
|
||||||
|
verbose_name_plural = _('module type profiles')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
||||||
"""
|
"""
|
||||||
A ModuleType represents a hardware element that can be installed within a device and which houses additional
|
A ModuleType represents a hardware element that can be installed within a device and which houses additional
|
||||||
@ -25,6 +50,13 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||||||
DeviceType, each ModuleType can have console, power, interface, and pass-through port templates assigned to it. It
|
DeviceType, each ModuleType can have console, power, interface, and pass-through port templates assigned to it. It
|
||||||
cannot, however house device bays or module bays.
|
cannot, however house device bays or module bays.
|
||||||
"""
|
"""
|
||||||
|
profile = models.ForeignKey(
|
||||||
|
to='dcim.ModuleTypeProfile',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='module_types',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
manufacturer = models.ForeignKey(
|
manufacturer = models.ForeignKey(
|
||||||
to='dcim.Manufacturer',
|
to='dcim.Manufacturer',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@ -47,14 +79,19 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
attributes = models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name=_('attributes')
|
||||||
|
)
|
||||||
|
|
||||||
clone_fields = ('manufacturer', 'weight', 'weight_unit', 'airflow')
|
clone_fields = ('profile', 'manufacturer', 'weight', 'weight_unit', 'airflow')
|
||||||
prerequisite_models = (
|
prerequisite_models = (
|
||||||
'dcim.Manufacturer',
|
'dcim.Manufacturer',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('manufacturer', 'model')
|
ordering = ('profile', 'manufacturer', 'model')
|
||||||
constraints = (
|
constraints = (
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(
|
||||||
fields=('manufacturer', 'model'),
|
fields=('manufacturer', 'model'),
|
||||||
@ -73,6 +110,7 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||||||
|
|
||||||
def to_yaml(self):
|
def to_yaml(self):
|
||||||
data = {
|
data = {
|
||||||
|
'profile': self.profile.name if self.profile else None,
|
||||||
'manufacturer': self.manufacturer.name,
|
'manufacturer': self.manufacturer.name,
|
||||||
'model': self.model,
|
'model': self.model,
|
||||||
'part_number': self.part_number,
|
'part_number': self.part_number,
|
||||||
|
@ -183,6 +183,17 @@ class ModuleBayIndex(SearchIndex):
|
|||||||
display_attrs = ('device', 'label', 'position', 'description')
|
display_attrs = ('device', 'label', 'position', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
@register_search
|
||||||
|
class ModuleTypeProfileIndex(SearchIndex):
|
||||||
|
model = models.ModuleTypeProfile
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('description', 500),
|
||||||
|
('comments', 5000),
|
||||||
|
)
|
||||||
|
display_attrs = ('name', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
class ModuleTypeIndex(SearchIndex):
|
class ModuleTypeIndex(SearchIndex):
|
||||||
model = models.ModuleType
|
model = models.ModuleType
|
||||||
|
@ -7,19 +7,42 @@ from .template_code import WEIGHT
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ModuleTable',
|
'ModuleTable',
|
||||||
|
'ModuleTypeProfileTable',
|
||||||
'ModuleTypeTable',
|
'ModuleTypeTable',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleTypeProfileTable(NetBoxTable):
|
||||||
|
comments = columns.MarkdownColumn(
|
||||||
|
verbose_name=_('Comments'),
|
||||||
|
)
|
||||||
|
tags = columns.TagColumn(
|
||||||
|
url_name='dcim:moduletype_list'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(NetBoxTable.Meta):
|
||||||
|
model = ModuleType
|
||||||
|
fields = (
|
||||||
|
'pk', 'id', 'name', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'name', 'description',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModuleTypeTable(NetBoxTable):
|
class ModuleTypeTable(NetBoxTable):
|
||||||
model = tables.Column(
|
profile = tables.Column(
|
||||||
linkify=True,
|
verbose_name=_('Profile'),
|
||||||
verbose_name=_('Module Type')
|
linkify=True
|
||||||
)
|
)
|
||||||
manufacturer = tables.Column(
|
manufacturer = tables.Column(
|
||||||
verbose_name=_('Manufacturer'),
|
verbose_name=_('Manufacturer'),
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
model = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Module Type')
|
||||||
|
)
|
||||||
instance_count = columns.LinkedCountColumn(
|
instance_count = columns.LinkedCountColumn(
|
||||||
viewname='dcim:module_list',
|
viewname='dcim:module_list',
|
||||||
url_params={'module_type_id': 'pk'},
|
url_params={'module_type_id': 'pk'},
|
||||||
@ -40,11 +63,11 @@ class ModuleTypeTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'model', 'manufacturer', 'part_number', 'airflow', 'weight', 'description', 'comments', 'tags',
|
'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'weight', 'description',
|
||||||
'created', 'last_updated',
|
'comments', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'model', 'manufacturer', 'part_number',
|
'pk', 'model', 'profile', 'manufacturer', 'part_number',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -591,7 +591,7 @@ class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
|
class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'url']
|
brief_fields = ['description', 'display', 'id', 'manufacturer', 'model', 'profile', 'url']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'part_number': 'ABC123',
|
'part_number': 'ABC123',
|
||||||
}
|
}
|
||||||
|
@ -1486,6 +1486,7 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ModuleTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ModuleType.objects.all()
|
queryset = ModuleType.objects.all()
|
||||||
filterset = ModuleTypeFilterSet
|
filterset = ModuleTypeFilterSet
|
||||||
|
ignore_fields = ['attributes']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -37,6 +37,9 @@ urlpatterns = [
|
|||||||
path('device-types/', include(get_model_urls('dcim', 'devicetype', detail=False))),
|
path('device-types/', include(get_model_urls('dcim', 'devicetype', detail=False))),
|
||||||
path('device-types/<int:pk>/', include(get_model_urls('dcim', 'devicetype'))),
|
path('device-types/<int:pk>/', include(get_model_urls('dcim', 'devicetype'))),
|
||||||
|
|
||||||
|
path('module-type-profiles/', include(get_model_urls('dcim', 'moduletypeprofile', detail=False))),
|
||||||
|
path('module-type-profiles/<int:pk>/', include(get_model_urls('dcim', 'moduletypeprofile'))),
|
||||||
|
|
||||||
path('module-types/', include(get_model_urls('dcim', 'moduletype', detail=False))),
|
path('module-types/', include(get_model_urls('dcim', 'moduletype', detail=False))),
|
||||||
path('module-types/<int:pk>/', include(get_model_urls('dcim', 'moduletype'))),
|
path('module-types/<int:pk>/', include(get_model_urls('dcim', 'moduletype'))),
|
||||||
|
|
||||||
|
@ -1278,6 +1278,62 @@ class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
|||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Module type profiles
|
||||||
|
#
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile, 'list', path='', detail=False)
|
||||||
|
class ModuleTypeProfileListView(generic.ObjectListView):
|
||||||
|
queryset = ModuleTypeProfile.objects.annotate(
|
||||||
|
instance_count=count_related(ModuleType, 'profile')
|
||||||
|
)
|
||||||
|
filterset = filtersets.ModuleTypeProfileFilterSet
|
||||||
|
filterset_form = forms.ModuleTypeProfileFilterForm
|
||||||
|
table = tables.ModuleTypeProfileTable
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile)
|
||||||
|
class ModuleTypeProfileView(GetRelatedModelsMixin, generic.ObjectView):
|
||||||
|
queryset = ModuleTypeProfile.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile, 'add', detail=False)
|
||||||
|
@register_model_view(ModuleTypeProfile, 'edit')
|
||||||
|
class ModuleTypeProfileEditView(generic.ObjectEditView):
|
||||||
|
queryset = ModuleTypeProfile.objects.all()
|
||||||
|
form = forms.ModuleTypeProfileForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile, 'delete')
|
||||||
|
class ModuleTypeProfileDeleteView(generic.ObjectDeleteView):
|
||||||
|
queryset = ModuleTypeProfile.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile, 'bulk_import', detail=False)
|
||||||
|
class ModuleTypeProfileBulkImportView(generic.BulkImportView):
|
||||||
|
queryset = ModuleTypeProfile.objects.all()
|
||||||
|
model_form = forms.ModuleTypeProfileImportForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile, 'bulk_edit', path='edit', detail=False)
|
||||||
|
class ModuleTypeProfileBulkEditView(generic.BulkEditView):
|
||||||
|
queryset = ModuleTypeProfile.objects.annotate(
|
||||||
|
instance_count=count_related(Module, 'module_type')
|
||||||
|
)
|
||||||
|
filterset = filtersets.ModuleTypeProfileFilterSet
|
||||||
|
table = tables.ModuleTypeProfileTable
|
||||||
|
form = forms.ModuleTypeProfileBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
|
@register_model_view(ModuleTypeProfile, 'bulk_delete', path='delete', detail=False)
|
||||||
|
class ModuleTypeProfileBulkDeleteView(generic.BulkDeleteView):
|
||||||
|
queryset = ModuleTypeProfile.objects.annotate(
|
||||||
|
instance_count=count_related(Module, 'module_type')
|
||||||
|
)
|
||||||
|
filterset = filtersets.ModuleTypeProfileFilterSet
|
||||||
|
table = tables.ModuleTypeProfileTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Module types
|
# Module types
|
||||||
#
|
#
|
||||||
|
@ -1161,6 +1161,7 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
'module',
|
'module',
|
||||||
'modulebay',
|
'modulebay',
|
||||||
'moduletype',
|
'moduletype',
|
||||||
|
'moduletypeprofile',
|
||||||
'platform',
|
'platform',
|
||||||
'powerfeed',
|
'powerfeed',
|
||||||
'poweroutlet',
|
'poweroutlet',
|
||||||
|
@ -85,6 +85,7 @@ DEVICES_MENU = Menu(
|
|||||||
items=(
|
items=(
|
||||||
get_model_item('dcim', 'devicetype', _('Device Types')),
|
get_model_item('dcim', 'devicetype', _('Device Types')),
|
||||||
get_model_item('dcim', 'moduletype', _('Module Types')),
|
get_model_item('dcim', 'moduletype', _('Module Types')),
|
||||||
|
get_model_item('dcim', 'moduletypeprofile', _('Module Type Profiles')),
|
||||||
get_model_item('dcim', 'manufacturer', _('Manufacturers')),
|
get_model_item('dcim', 'manufacturer', _('Manufacturers')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -23,6 +23,10 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Module Type" %}</h2>
|
<h2 class="card-header">{% trans "Module Type" %}</h2>
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Profile" %}</th>
|
||||||
|
<td>{{ object.profile|linkify }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{% trans "Manufacturer" %}</th>
|
<th scope="row">{% trans "Manufacturer" %}</th>
|
||||||
<td>{{ object.manufacturer|linkify }}</td>
|
<td>{{ object.manufacturer|linkify }}</td>
|
||||||
|
44
netbox/templates/dcim/moduletypeprofile.html
Normal file
44
netbox/templates/dcim/moduletypeprofile.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'generic/object.html' %}
|
||||||
|
{% load buttons %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load plugins %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{{ object.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Module Type Profile" %}</h2>
|
||||||
|
<table class="table table-hover attr-table">
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Name" %}</th>
|
||||||
|
<td>{{ object.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Description" %}</th>
|
||||||
|
<td>{{ object.description|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
{% include 'inc/panels/comments.html' %}
|
||||||
|
{% plugin_left_page object %}
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<h2 class="card-header">{% trans "Schema" %}</h2>
|
||||||
|
<pre>{{ object.schema|json }}</pre>
|
||||||
|
</div>
|
||||||
|
{% include 'inc/panels/related_objects.html' %}
|
||||||
|
{% include 'inc/panels/custom_fields.html' %}
|
||||||
|
{% plugin_right_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
{% plugin_full_width_page object %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -20,6 +20,7 @@ drf-spectacular-sidecar==2025.2.1
|
|||||||
feedparser==6.0.11
|
feedparser==6.0.11
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
Jinja2==3.1.5
|
Jinja2==3.1.5
|
||||||
|
jsonschema==4.23.0
|
||||||
Markdown==3.7
|
Markdown==3.7
|
||||||
mkdocs-material==9.6.7
|
mkdocs-material==9.6.7
|
||||||
mkdocstrings[python]==0.28.2
|
mkdocstrings[python]==0.28.2
|
||||||
|
Loading…
Reference in New Issue
Block a user