mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
Initial work on #8118
This commit is contained in:
parent
3bb485d0b8
commit
4c15f4a84f
@ -21,6 +21,7 @@ __all__ = [
|
||||
'NestedInterfaceTemplateSerializer',
|
||||
'NestedInventoryItemSerializer',
|
||||
'NestedInventoryItemRoleSerializer',
|
||||
'NestedInventoryItemTemplateSerializer',
|
||||
'NestedManufacturerSerializer',
|
||||
'NestedModuleBaySerializer',
|
||||
'NestedModuleBayTemplateSerializer',
|
||||
@ -231,6 +232,15 @@ class NestedDeviceBayTemplateSerializer(WritableNestedSerializer):
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedInventoryItemTemplateSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail')
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.InventoryItemTemplate
|
||||
fields = ['id', 'url', 'display', 'name', '_depth']
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
#
|
||||
|
@ -447,6 +447,40 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'url', 'display', 'device_type', 'name', 'label', 'description', 'created', 'last_updated']
|
||||
|
||||
|
||||
class InventoryItemTemplateSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtemplate-detail')
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
parent = serializers.PrimaryKeyRelatedField(
|
||||
queryset=InventoryItemTemplate.objects.all(),
|
||||
allow_null=True,
|
||||
default=None
|
||||
)
|
||||
role = NestedInventoryItemRoleSerializer(required=False, allow_null=True)
|
||||
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
|
||||
component_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS),
|
||||
required=False,
|
||||
allow_null=True
|
||||
)
|
||||
component = serializers.SerializerMethodField(read_only=True)
|
||||
_depth = serializers.IntegerField(source='level', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItemTemplate
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id',
|
||||
'description', 'component_type', 'component_id', 'component', 'created', 'last_updated', '_depth',
|
||||
]
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
||||
def get_component(self, obj):
|
||||
if obj.component is None:
|
||||
return None
|
||||
serializer = get_serializer_for_model(obj.component, prefix='Nested')
|
||||
context = {'request': self.context['request']}
|
||||
return serializer(obj.component, context=context).data
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
#
|
||||
|
@ -31,6 +31,7 @@ router.register('front-port-templates', views.FrontPortTemplateViewSet)
|
||||
router.register('rear-port-templates', views.RearPortTemplateViewSet)
|
||||
router.register('module-bay-templates', views.ModuleBayTemplateViewSet)
|
||||
router.register('device-bay-templates', views.DeviceBayTemplateViewSet)
|
||||
router.register('inventory-item-templates', views.InventoryItemTemplateViewSet)
|
||||
|
||||
# Device/modules
|
||||
router.register('device-roles', views.DeviceRoleViewSet)
|
||||
|
@ -350,6 +350,12 @@ class DeviceBayTemplateViewSet(ModelViewSet):
|
||||
filterset_class = filtersets.DeviceBayTemplateFilterSet
|
||||
|
||||
|
||||
class InventoryItemTemplateViewSet(ModelViewSet):
|
||||
queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role')
|
||||
serializer_class = serializers.InventoryItemTemplateSerializer
|
||||
filterset_class = filtersets.InventoryItemTemplateFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Device roles
|
||||
#
|
||||
|
@ -62,6 +62,18 @@ POWERFEED_MAX_UTILIZATION_DEFAULT = 80 # Percentage
|
||||
# Device components
|
||||
#
|
||||
|
||||
MODULAR_COMPONENT_TEMPLATE_MODELS = Q(
|
||||
app_label='dcim',
|
||||
model__in=(
|
||||
'consoleporttemplate',
|
||||
'consoleserverporttemplate',
|
||||
'frontporttemplate',
|
||||
'interfacetemplate',
|
||||
'poweroutlettemplate',
|
||||
'powerporttemplate',
|
||||
'rearporttemplate',
|
||||
))
|
||||
|
||||
MODULAR_COMPONENT_MODELS = Q(
|
||||
app_label='dcim',
|
||||
model__in=(
|
||||
|
@ -40,6 +40,7 @@ __all__ = (
|
||||
'InterfaceTemplateFilterSet',
|
||||
'InventoryItemFilterSet',
|
||||
'InventoryItemRoleFilterSet',
|
||||
'InventoryItemTemplateFilterSet',
|
||||
'LocationFilterSet',
|
||||
'ManufacturerFilterSet',
|
||||
'ModuleBayFilterSet',
|
||||
@ -687,6 +688,49 @@ class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponent
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=InventoryItemTemplate.objects.all(),
|
||||
label='Parent inventory item (ID)',
|
||||
)
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
label='Manufacturer (ID)',
|
||||
)
|
||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='manufacturer__slug',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Manufacturer (slug)',
|
||||
)
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
label='Role (ID)',
|
||||
)
|
||||
role = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='role__slug',
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Role (slug)',
|
||||
)
|
||||
component_type = ContentTypeFilter()
|
||||
component_id = MultiValueNumberFilter()
|
||||
|
||||
class Meta:
|
||||
model = InventoryItemTemplate
|
||||
fields = ['id', 'name', 'label', 'part_id']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
qs_filter = (
|
||||
Q(name__icontains=value) |
|
||||
Q(part_id__icontains=value) |
|
||||
Q(description__icontains=value)
|
||||
)
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class DeviceRoleFilterSet(OrganizationalModelFilterSet):
|
||||
tag = TagFilter()
|
||||
|
||||
|
@ -31,6 +31,7 @@ __all__ = (
|
||||
'InterfaceTemplateBulkEditForm',
|
||||
'InventoryItemBulkEditForm',
|
||||
'InventoryItemRoleBulkEditForm',
|
||||
'InventoryItemTemplateBulkEditForm',
|
||||
'LocationBulkEditForm',
|
||||
'ManufacturerBulkEditForm',
|
||||
'ModuleBulkEditForm',
|
||||
@ -907,6 +908,31 @@ class DeviceBayTemplateBulkEditForm(BulkEditForm):
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
||||
class InventoryItemTemplateBulkEditForm(BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=InventoryItemTemplate.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
label = forms.CharField(
|
||||
max_length=64,
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
required=False
|
||||
)
|
||||
role = DynamicModelChoiceField(
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
required=False
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
nullable_fields = ['label', 'role', 'manufacturer', 'part_id', 'description']
|
||||
|
||||
|
||||
#
|
||||
# Device components
|
||||
#
|
||||
|
@ -38,6 +38,7 @@ __all__ = (
|
||||
'InterfaceTemplateForm',
|
||||
'InventoryItemForm',
|
||||
'InventoryItemRoleForm',
|
||||
'InventoryItemTemplateForm',
|
||||
'LocationForm',
|
||||
'ManufacturerForm',
|
||||
'ModuleForm',
|
||||
@ -1073,6 +1074,48 @@ class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
queryset=InventoryItem.objects.all(),
|
||||
required=False,
|
||||
query_params={
|
||||
'device_id': '$device'
|
||||
}
|
||||
)
|
||||
role = DynamicModelChoiceField(
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
required=False
|
||||
)
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
)
|
||||
component_type = ContentTypeChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=MODULAR_COMPONENT_MODELS,
|
||||
required=False,
|
||||
widget=forms.HiddenInput
|
||||
)
|
||||
component_id = forms.IntegerField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItemTemplate
|
||||
fields = [
|
||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||
'component_type', 'component_id',
|
||||
]
|
||||
fieldsets = (
|
||||
('Inventory Item', ('device_type', 'parent', 'name', 'label', 'role', 'description')),
|
||||
('Hardware', ('manufacturer', 'part_id')),
|
||||
)
|
||||
widgets = {
|
||||
'device_type': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Device components
|
||||
#
|
||||
|
@ -11,6 +11,7 @@ __all__ = (
|
||||
'DeviceTypeImportForm',
|
||||
'FrontPortTemplateImportForm',
|
||||
'InterfaceTemplateImportForm',
|
||||
'InventoryItemTemplateImportForm',
|
||||
'ModuleBayTemplateImportForm',
|
||||
'ModuleTypeImportForm',
|
||||
'PowerOutletTemplateImportForm',
|
||||
@ -49,24 +50,7 @@ class ModuleTypeImportForm(BootstrapMixin, forms.ModelForm):
|
||||
#
|
||||
|
||||
class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
def clean_device_type(self):
|
||||
# Limit fields referencing other components to the parent DeviceType
|
||||
if data := self.cleaned_data['device_type']:
|
||||
for field_name, field in self.fields.items():
|
||||
if isinstance(field, forms.ModelChoiceField) and field_name not in ['device_type', 'module_type']:
|
||||
field.queryset = field.queryset.filter(device_type=data)
|
||||
|
||||
return data
|
||||
|
||||
def clean_module_type(self):
|
||||
# Limit fields referencing other components to the parent ModuleType
|
||||
if data := self.cleaned_data['module_type']:
|
||||
for field_name, field in self.fields.items():
|
||||
if isinstance(field, forms.ModelChoiceField) and field_name not in ['device_type', 'module_type']:
|
||||
field.queryset = field.queryset.filter(module_type=data)
|
||||
|
||||
return data
|
||||
pass
|
||||
|
||||
|
||||
class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
|
||||
@ -109,6 +93,20 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
]
|
||||
|
||||
def clean_device_type(self):
|
||||
if device_type := self.cleaned_data['device_type']:
|
||||
power_port = self.fields['power_port']
|
||||
power_port.queryset = power_port.queryset.filter(device_type=device_type)
|
||||
|
||||
return device_type
|
||||
|
||||
def clean_module_type(self):
|
||||
if module_type := self.cleaned_data['module_type']:
|
||||
power_port = self.fields['power_port']
|
||||
power_port.queryset = power_port.queryset.filter(module_type=module_type)
|
||||
|
||||
return module_type
|
||||
|
||||
|
||||
class InterfaceTemplateImportForm(ComponentTemplateImportForm):
|
||||
type = forms.ChoiceField(
|
||||
@ -131,6 +129,20 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
|
||||
to_field_name='name'
|
||||
)
|
||||
|
||||
def clean_device_type(self):
|
||||
if device_type := self.cleaned_data['device_type']:
|
||||
rear_port = self.fields['rear_port']
|
||||
rear_port.queryset = rear_port.queryset.filter(device_type=device_type)
|
||||
|
||||
return device_type
|
||||
|
||||
def clean_module_type(self):
|
||||
if module_type := self.cleaned_data['module_type']:
|
||||
rear_port = self.fields['rear_port']
|
||||
rear_port.queryset = rear_port.queryset.filter(module_type=module_type)
|
||||
|
||||
return module_type
|
||||
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
fields = [
|
||||
@ -166,3 +178,40 @@ class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
|
||||
fields = [
|
||||
'device_type', 'name', 'label', 'description',
|
||||
]
|
||||
|
||||
|
||||
class InventoryItemTemplateImportForm(ComponentTemplateImportForm):
|
||||
parent = forms.ModelChoiceField(
|
||||
queryset=InventoryItemTemplate.objects.all(),
|
||||
required=False
|
||||
)
|
||||
role = forms.ModelChoiceField(
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
to_field_name='name',
|
||||
required=False
|
||||
)
|
||||
manufacturer = forms.ModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='name',
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InventoryItemTemplate
|
||||
fields = [
|
||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||
]
|
||||
|
||||
def clean_device_type(self):
|
||||
if device_type := self.cleaned_data['device_type']:
|
||||
parent = self.fields['parent']
|
||||
parent.queryset = parent.queryset.filter(device_type=device_type)
|
||||
|
||||
return device_type
|
||||
|
||||
def clean_module_type(self):
|
||||
if module_type := self.cleaned_data['module_type']:
|
||||
parent = self.fields['parent']
|
||||
parent.queryset = parent.queryset.filter(module_type=module_type)
|
||||
|
||||
return module_type
|
||||
|
@ -53,6 +53,9 @@ class DCIMQuery(graphene.ObjectType):
|
||||
inventory_item_role = ObjectField(InventoryItemRoleType)
|
||||
inventory_item_role_list = ObjectListField(InventoryItemRoleType)
|
||||
|
||||
inventory_item_template = ObjectField(InventoryItemTemplateType)
|
||||
inventory_item_template_list = ObjectListField(InventoryItemTemplateType)
|
||||
|
||||
location = ObjectField(LocationType)
|
||||
location_list = ObjectListField(LocationType)
|
||||
|
||||
|
@ -26,6 +26,7 @@ __all__ = (
|
||||
'InterfaceTemplateType',
|
||||
'InventoryItemType',
|
||||
'InventoryItemRoleType',
|
||||
'InventoryItemTemplateType',
|
||||
'LocationType',
|
||||
'ManufacturerType',
|
||||
'ModuleType',
|
||||
@ -172,6 +173,14 @@ class DeviceBayTemplateType(ComponentTemplateObjectType):
|
||||
filterset_class = filtersets.DeviceBayTemplateFilterSet
|
||||
|
||||
|
||||
class InventoryItemTemplateType(ComponentTemplateObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.InventoryItemTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.InventoryItemTemplateFilterSet
|
||||
|
||||
|
||||
class DeviceRoleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
|
43
netbox/dcim/migrations/0148_inventoryitem_templates.py
Normal file
43
netbox/dcim/migrations/0148_inventoryitem_templates.py
Normal file
@ -0,0 +1,43 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
import utilities.fields
|
||||
import utilities.ordering
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('dcim', '0147_inventoryitem_component'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InventoryItemTemplate',
|
||||
fields=[
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
|
||||
('label', models.CharField(blank=True, max_length=64)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('component_id', models.PositiveBigIntegerField(blank=True, null=True)),
|
||||
('part_id', models.CharField(blank=True, max_length=50)),
|
||||
('lft', models.PositiveIntegerField(editable=False)),
|
||||
('rght', models.PositiveIntegerField(editable=False)),
|
||||
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
|
||||
('level', models.PositiveIntegerField(editable=False)),
|
||||
('component_type', models.ForeignKey(blank=True, limit_choices_to=models.Q(('app_label', 'dcim'), ('model__in', ('consoleporttemplate', 'consoleserverporttemplate', 'frontporttemplate', 'interfacetemplate', 'poweroutlettemplate', 'powerporttemplate', 'rearporttemplate'))), null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
|
||||
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventoryitemtemplates', to='dcim.devicetype')),
|
||||
('manufacturer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_item_templates', to='dcim.manufacturer')),
|
||||
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.inventoryitemtemplate')),
|
||||
('role', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_item_templates', to='dcim.inventoryitemrole')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('device_type__id', 'parent__id', '_name'),
|
||||
'unique_together': {('device_type', 'parent', 'name')},
|
||||
},
|
||||
),
|
||||
]
|
@ -1,15 +1,20 @@
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from extras.utils import extras_features
|
||||
from netbox.models import ChangeLoggedModel
|
||||
from utilities.fields import ColorField, NaturalOrderingField
|
||||
from utilities.mptt import TreeManager
|
||||
from utilities.ordering import naturalize_interface
|
||||
from .device_components import (
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, ModuleBay, PowerOutlet, PowerPort, RearPort,
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, InventoryItem, ModuleBay, PowerOutlet, PowerPort,
|
||||
RearPort,
|
||||
)
|
||||
|
||||
|
||||
@ -19,6 +24,7 @@ __all__ = (
|
||||
'DeviceBayTemplate',
|
||||
'FrontPortTemplate',
|
||||
'InterfaceTemplate',
|
||||
'InventoryItemTemplate',
|
||||
'ModuleBayTemplate',
|
||||
'PowerOutletTemplate',
|
||||
'PowerPortTemplate',
|
||||
@ -140,6 +146,8 @@ class ConsolePortTemplate(ModularComponentTemplateModel):
|
||||
blank=True
|
||||
)
|
||||
|
||||
component_model = ConsolePort
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
@ -148,7 +156,7 @@ class ConsolePortTemplate(ModularComponentTemplateModel):
|
||||
)
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
return ConsolePort(
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
@ -167,6 +175,8 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
||||
blank=True
|
||||
)
|
||||
|
||||
component_model = ConsoleServerPort
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
@ -175,7 +185,7 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
||||
)
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
return ConsoleServerPort(
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
@ -206,6 +216,8 @@ class PowerPortTemplate(ModularComponentTemplateModel):
|
||||
help_text="Allocated power draw (watts)"
|
||||
)
|
||||
|
||||
component_model = PowerPort
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
@ -214,7 +226,7 @@ class PowerPortTemplate(ModularComponentTemplateModel):
|
||||
)
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
return PowerPort(
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
@ -257,6 +269,8 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
||||
help_text="Phase (for three-phase feeds)"
|
||||
)
|
||||
|
||||
component_model = PowerOutlet
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
@ -283,7 +297,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
||||
power_port = PowerPort.objects.get(name=self.power_port.name, **kwargs)
|
||||
else:
|
||||
power_port = None
|
||||
return PowerOutlet(
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
@ -314,6 +328,8 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
||||
verbose_name='Management only'
|
||||
)
|
||||
|
||||
component_model = Interface
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
@ -322,7 +338,7 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
||||
)
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
return Interface(
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
@ -356,6 +372,8 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
||||
]
|
||||
)
|
||||
|
||||
component_model = FrontPort
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
@ -391,7 +409,7 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
||||
rear_port = RearPort.objects.get(name=self.rear_port.name, **kwargs)
|
||||
else:
|
||||
rear_port = None
|
||||
return FrontPort(
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
@ -422,6 +440,8 @@ class RearPortTemplate(ModularComponentTemplateModel):
|
||||
]
|
||||
)
|
||||
|
||||
component_model = RearPort
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', 'module_type', '_name')
|
||||
unique_together = (
|
||||
@ -430,7 +450,7 @@ class RearPortTemplate(ModularComponentTemplateModel):
|
||||
)
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
return RearPort(
|
||||
return self.component_model(
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.label,
|
||||
type=self.type,
|
||||
@ -451,12 +471,14 @@ class ModuleBayTemplate(ComponentTemplateModel):
|
||||
help_text='Identifier to reference when renaming installed components'
|
||||
)
|
||||
|
||||
component_model = ModuleBay
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
|
||||
def instantiate(self, device):
|
||||
return ModuleBay(
|
||||
return self.component_model(
|
||||
device=device,
|
||||
name=self.name,
|
||||
label=self.label,
|
||||
@ -469,12 +491,14 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a DeviceBay to be created for a new parent Device.
|
||||
"""
|
||||
component_model = DeviceBay
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type', '_name')
|
||||
unique_together = ('device_type', 'name')
|
||||
|
||||
def instantiate(self, device):
|
||||
return DeviceBay(
|
||||
return self.component_model(
|
||||
device=device,
|
||||
name=self.name,
|
||||
label=self.label
|
||||
@ -485,3 +509,79 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
||||
raise ValidationError(
|
||||
f"Subdevice role of device type ({self.device_type}) must be set to \"parent\" to allow device bays."
|
||||
)
|
||||
|
||||
|
||||
@extras_features('webhooks')
|
||||
class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
|
||||
"""
|
||||
A template for an InventoryItem to be created for a new parent Device.
|
||||
"""
|
||||
parent = TreeForeignKey(
|
||||
to='self',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='child_items',
|
||||
blank=True,
|
||||
null=True,
|
||||
db_index=True
|
||||
)
|
||||
component_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
limit_choices_to=MODULAR_COMPONENT_TEMPLATE_MODELS,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
component_id = models.PositiveBigIntegerField(
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
component = GenericForeignKey(
|
||||
ct_field='component_type',
|
||||
fk_field='component_id'
|
||||
)
|
||||
role = models.ForeignKey(
|
||||
to='dcim.InventoryItemRole',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='inventory_item_templates',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
manufacturer = models.ForeignKey(
|
||||
to='dcim.Manufacturer',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='inventory_item_templates',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
part_id = models.CharField(
|
||||
max_length=50,
|
||||
verbose_name='Part ID',
|
||||
blank=True,
|
||||
help_text='Manufacturer-assigned part identifier'
|
||||
)
|
||||
|
||||
objects = TreeManager()
|
||||
component_model = InventoryItem
|
||||
|
||||
class Meta:
|
||||
ordering = ('device_type__id', 'parent__id', '_name')
|
||||
unique_together = ('device_type', 'parent', 'name')
|
||||
|
||||
def instantiate(self, **kwargs):
|
||||
parent = InventoryItemTemplate.objects.get(name=self.parent.name, **kwargs) if self.parent else None
|
||||
if self.component:
|
||||
model = self.component.component_model
|
||||
component = model.objects.get(name=self.component.name, **kwargs)
|
||||
else:
|
||||
component = None
|
||||
return self.component_model(
|
||||
parent=parent,
|
||||
name=self.name,
|
||||
label=self.label,
|
||||
component=component,
|
||||
role=self.role,
|
||||
manufacturer=self.manufacturer,
|
||||
part_id=self.part_id,
|
||||
**kwargs
|
||||
)
|
||||
|
@ -933,6 +933,9 @@ class Device(PrimaryModel, ConfigContextModel):
|
||||
DeviceBay.objects.bulk_create(
|
||||
[x.instantiate(device=self) for x in self.device_type.devicebaytemplates.all()]
|
||||
)
|
||||
# Avoid bulk_create to handle MPTT
|
||||
for x in self.device_type.inventoryitemtemplates.all():
|
||||
x.instantiate(device=self).save()
|
||||
|
||||
# Update Site and Rack assignment for any child Devices
|
||||
devices = Device.objects.filter(parent_bay__device=self)
|
||||
|
@ -1,8 +1,9 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from dcim.models import (
|
||||
ConsolePortTemplate, ConsoleServerPortTemplate, DeviceBayTemplate, DeviceType, FrontPortTemplate, InterfaceTemplate,
|
||||
Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
|
||||
InventoryItemTemplate, Manufacturer, ModuleBayTemplate, PowerOutletTemplate, PowerPortTemplate, RearPortTemplate,
|
||||
)
|
||||
from utilities.tables import (
|
||||
BaseTable, BooleanColumn, ButtonsColumn, ColorColumn, LinkedCountColumn, MarkdownColumn, TagColumn, ToggleColumn,
|
||||
@ -15,6 +16,7 @@ __all__ = (
|
||||
'DeviceTypeTable',
|
||||
'FrontPortTemplateTable',
|
||||
'InterfaceTemplateTable',
|
||||
'InventoryItemTemplateTable',
|
||||
'ManufacturerTable',
|
||||
'ModuleBayTemplateTable',
|
||||
'PowerOutletTemplateTable',
|
||||
@ -223,3 +225,25 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
|
||||
model = DeviceBayTemplate
|
||||
fields = ('pk', 'name', 'label', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
|
||||
class InventoryItemTemplateTable(ComponentTemplateTable):
|
||||
actions = ButtonsColumn(
|
||||
model=InventoryItemTemplate,
|
||||
buttons=('edit', 'delete')
|
||||
)
|
||||
role = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
manufacturer = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
component = tables.Column(
|
||||
accessor=Accessor('component'),
|
||||
orderable=False
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = InventoryItemTemplate
|
||||
fields = ('pk', 'name', 'label', 'role', 'manufacturer', 'part_id', 'component', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
@ -897,6 +897,57 @@ class DeviceBayTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
]
|
||||
|
||||
|
||||
class InventoryItemTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = InventoryItemTemplate
|
||||
brief_fields = ['_depth', 'display', 'id', 'name', 'url']
|
||||
bulk_update_data = {
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
|
||||
devicetype = DeviceType.objects.create(
|
||||
manufacturer=manufacturer,
|
||||
model='Device Type 1',
|
||||
slug='device-type-1'
|
||||
)
|
||||
role = InventoryItemRole.objects.create(name='Inventory Item Role 1', slug='inventory-item-role-1')
|
||||
|
||||
inventory_item_templates = (
|
||||
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 1', manufacturer=manufacturer, role=role),
|
||||
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 2', manufacturer=manufacturer, role=role),
|
||||
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 3', manufacturer=manufacturer, role=role),
|
||||
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 4', manufacturer=manufacturer, role=role),
|
||||
)
|
||||
for item in inventory_item_templates:
|
||||
item.save()
|
||||
|
||||
cls.create_data = [
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Inventory Item Template 5',
|
||||
'manufacturer': manufacturer.pk,
|
||||
'role': role.pk,
|
||||
'parent': inventory_item_templates[3].pk,
|
||||
},
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Inventory Item Template 6',
|
||||
'manufacturer': manufacturer.pk,
|
||||
'role': role.pk,
|
||||
'parent': inventory_item_templates[3].pk,
|
||||
},
|
||||
{
|
||||
'device_type': devicetype.pk,
|
||||
'name': 'Inventory Item Template 7',
|
||||
'manufacturer': manufacturer.pk,
|
||||
'role': role.pk,
|
||||
'parent': inventory_item_templates[3].pk,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class DeviceRoleTest(APIViewTestCases.APIViewTestCase):
|
||||
model = DeviceRole
|
||||
brief_fields = ['device_count', 'display', 'id', 'name', 'slug', 'url', 'virtualmachine_count']
|
||||
|
@ -1214,6 +1214,86 @@ class DeviceBayTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class InventoryItemTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
filterset = InventoryItemTemplateFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturers = (
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||
Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
|
||||
device_types = (
|
||||
DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1'),
|
||||
DeviceType(manufacturer=manufacturers[0], model='Model 2', slug='model-2'),
|
||||
DeviceType(manufacturer=manufacturers[0], model='Model 3', slug='model-3'),
|
||||
)
|
||||
DeviceType.objects.bulk_create(device_types)
|
||||
|
||||
inventory_item_roles = (
|
||||
InventoryItemRole(name='Inventory Item Role 1', slug='inventory-item-role-1'),
|
||||
InventoryItemRole(name='Inventory Item Role 2', slug='inventory-item-role-2'),
|
||||
InventoryItemRole(name='Inventory Item Role 3', slug='inventory-item-role-3'),
|
||||
)
|
||||
InventoryItemRole.objects.bulk_create(inventory_item_roles)
|
||||
|
||||
inventory_item_templates = (
|
||||
InventoryItemTemplate(device_type=device_types[0], name='Inventory Item 1', label='A', role=inventory_item_roles[0], manufacturer=manufacturers[0], part_id='1001'),
|
||||
InventoryItemTemplate(device_type=device_types[1], name='Inventory Item 2', label='B', role=inventory_item_roles[1], manufacturer=manufacturers[1], part_id='1002'),
|
||||
InventoryItemTemplate(device_type=device_types[2], name='Inventory Item 3', label='C', role=inventory_item_roles[2], manufacturer=manufacturers[2], part_id='1003'),
|
||||
)
|
||||
for item in inventory_item_templates:
|
||||
item.save()
|
||||
|
||||
child_inventory_item_templates = (
|
||||
InventoryItemTemplate(device_type=device_types[0], name='Inventory Item 1A', parent=inventory_item_templates[0]),
|
||||
InventoryItemTemplate(device_type=device_types[1], name='Inventory Item 2A', parent=inventory_item_templates[1]),
|
||||
InventoryItemTemplate(device_type=device_types[2], name='Inventory Item 3A', parent=inventory_item_templates[2]),
|
||||
)
|
||||
for item in child_inventory_item_templates:
|
||||
item.save()
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Inventory Item 1', 'Inventory Item 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_devicetype_id(self):
|
||||
device_types = DeviceType.objects.all()[:2]
|
||||
params = {'devicetype_id': [device_types[0].pk, device_types[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
def test_label(self):
|
||||
params = {'label': ['A', 'B']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_part_id(self):
|
||||
params = {'part_id': ['1001', '1002']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_parent_id(self):
|
||||
parent_items = InventoryItemTemplate.objects.filter(parent__isnull=True)[:2]
|
||||
params = {'parent_id': [parent_items[0].pk, parent_items[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_role(self):
|
||||
roles = InventoryItemRole.objects.all()[:2]
|
||||
params = {'role_id': [roles[0].pk, roles[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'role': [roles[0].slug, roles[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_manufacturer(self):
|
||||
manufacturers = Manufacturer.objects.all()[:2]
|
||||
params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
queryset = DeviceRole.objects.all()
|
||||
filterset = DeviceRoleFilterSet
|
||||
|
@ -580,6 +580,20 @@ class DeviceTypeTestCase(
|
||||
url = reverse('dcim:devicetype_devicebays', kwargs={'pk': devicetype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_devicetype_inventoryitems(self):
|
||||
devicetype = DeviceType.objects.first()
|
||||
inventory_items = (
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay 1'),
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay 2'),
|
||||
DeviceBayTemplate(device_type=devicetype, name='Device Bay 3'),
|
||||
)
|
||||
for inventory_item in inventory_items:
|
||||
inventory_item.save()
|
||||
|
||||
url = reverse('dcim:devicetype_inventoryitems', kwargs={'pk': devicetype.pk})
|
||||
self.assertHttpStatus(self.client.get(url), 200)
|
||||
|
||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
|
||||
def test_import_objects(self):
|
||||
"""
|
||||
@ -659,6 +673,13 @@ device-bays:
|
||||
- name: Device Bay 1
|
||||
- name: Device Bay 2
|
||||
- name: Device Bay 3
|
||||
inventory-items:
|
||||
- name: Inventory Item 1
|
||||
manufacturer: Generic
|
||||
- name: Inventory Item 2
|
||||
manufacturer: Generic
|
||||
- name: Inventory Item 3
|
||||
manufacturer: Generic
|
||||
"""
|
||||
|
||||
# Create the manufacturer
|
||||
@ -677,6 +698,7 @@ device-bays:
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
'dcim.add_devicebaytemplate',
|
||||
'dcim.add_inventoryitemtemplate',
|
||||
)
|
||||
|
||||
form_data = {
|
||||
@ -729,13 +751,17 @@ device-bays:
|
||||
self.assertEqual(fp1.rear_port_position, 1)
|
||||
|
||||
self.assertEqual(device_type.modulebaytemplates.count(), 3)
|
||||
db1 = ModuleBayTemplate.objects.first()
|
||||
self.assertEqual(db1.name, 'Module Bay 1')
|
||||
mb1 = ModuleBayTemplate.objects.first()
|
||||
self.assertEqual(mb1.name, 'Module Bay 1')
|
||||
|
||||
self.assertEqual(device_type.devicebaytemplates.count(), 3)
|
||||
db1 = DeviceBayTemplate.objects.first()
|
||||
self.assertEqual(db1.name, 'Device Bay 1')
|
||||
|
||||
self.assertEqual(device_type.inventoryitemtemplates.count(), 3)
|
||||
ii1 = InventoryItemTemplate.objects.first()
|
||||
self.assertEqual(ii1.name, 'Inventory Item 1')
|
||||
|
||||
def test_export_objects(self):
|
||||
url = reverse('dcim:devicetype_list')
|
||||
self.add_permissions('dcim.view_devicetype')
|
||||
@ -1393,6 +1419,48 @@ class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
||||
}
|
||||
|
||||
|
||||
class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||
model = InventoryItemTemplate
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
manufacturers = (
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||
)
|
||||
Manufacturer.objects.bulk_create(manufacturers)
|
||||
|
||||
devicetypes = (
|
||||
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.objects.bulk_create(devicetypes)
|
||||
|
||||
inventory_item_templates = (
|
||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 1', manufacturer=manufacturers[0]),
|
||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 2', manufacturer=manufacturers[0]),
|
||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 3', manufacturer=manufacturers[0]),
|
||||
)
|
||||
for item in inventory_item_templates:
|
||||
item.save()
|
||||
|
||||
cls.form_data = {
|
||||
'device_type': devicetypes[1].pk,
|
||||
'name': 'Inventory Item Template X',
|
||||
'manufacturer': manufacturers[1].pk,
|
||||
}
|
||||
|
||||
cls.bulk_create_data = {
|
||||
'device_type': devicetypes[1].pk,
|
||||
'name_pattern': 'Inventory Item Template [4-6]',
|
||||
'manufacturer': manufacturers[1].pk,
|
||||
}
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'description': 'Foo bar',
|
||||
}
|
||||
|
||||
|
||||
class DeviceRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
||||
model = DeviceRole
|
||||
|
||||
|
@ -115,6 +115,7 @@ urlpatterns = [
|
||||
path('device-types/<int:pk>/rear-ports/', views.DeviceTypeRearPortsView.as_view(), name='devicetype_rearports'),
|
||||
path('device-types/<int:pk>/module-bays/', views.DeviceTypeModuleBaysView.as_view(), name='devicetype_modulebays'),
|
||||
path('device-types/<int:pk>/device-bays/', views.DeviceTypeDeviceBaysView.as_view(), name='devicetype_devicebays'),
|
||||
path('device-types/<int:pk>/inventory-items/', views.DeviceTypeInventoryItemsView.as_view(), name='devicetype_inventoryitems'),
|
||||
path('device-types/<int:pk>/edit/', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
|
||||
path('device-types/<int:pk>/delete/', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
|
||||
path('device-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='devicetype_changelog', kwargs={'model': DeviceType}),
|
||||
@ -203,7 +204,7 @@ urlpatterns = [
|
||||
path('device-bay-templates/<int:pk>/edit/', views.DeviceBayTemplateEditView.as_view(), name='devicebaytemplate_edit'),
|
||||
path('device-bay-templates/<int:pk>/delete/', views.DeviceBayTemplateDeleteView.as_view(), name='devicebaytemplate_delete'),
|
||||
|
||||
# Device bay templates
|
||||
# Module bay templates
|
||||
path('module-bay-templates/add/', views.ModuleBayTemplateCreateView.as_view(), name='modulebaytemplate_add'),
|
||||
path('module-bay-templates/edit/', views.ModuleBayTemplateBulkEditView.as_view(), name='modulebaytemplate_bulk_edit'),
|
||||
path('module-bay-templates/rename/', views.ModuleBayTemplateBulkRenameView.as_view(), name='modulebaytemplate_bulk_rename'),
|
||||
@ -211,6 +212,14 @@ urlpatterns = [
|
||||
path('module-bay-templates/<int:pk>/edit/', views.ModuleBayTemplateEditView.as_view(), name='modulebaytemplate_edit'),
|
||||
path('module-bay-templates/<int:pk>/delete/', views.ModuleBayTemplateDeleteView.as_view(), name='modulebaytemplate_delete'),
|
||||
|
||||
# Inventory item templates
|
||||
path('inventory-item-templates/add/', views.InventoryItemTemplateCreateView.as_view(), name='inventoryitemtemplate_add'),
|
||||
path('inventory-item-templates/edit/', views.InventoryItemTemplateBulkEditView.as_view(), name='inventoryitemtemplate_bulk_edit'),
|
||||
path('inventory-item-templates/rename/', views.InventoryItemTemplateBulkRenameView.as_view(), name='inventoryitemtemplate_bulk_rename'),
|
||||
path('inventory-item-templates/delete/', views.InventoryItemTemplateBulkDeleteView.as_view(), name='inventoryitemtemplate_bulk_delete'),
|
||||
path('inventory-item-templates/<int:pk>/edit/', views.InventoryItemTemplateEditView.as_view(), name='inventoryitemtemplate_edit'),
|
||||
path('inventory-item-templates/<int:pk>/delete/', views.InventoryItemTemplateDeleteView.as_view(), name='inventoryitemtemplate_delete'),
|
||||
|
||||
# Device roles
|
||||
path('device-roles/', views.DeviceRoleListView.as_view(), name='devicerole_list'),
|
||||
path('device-roles/add/', views.DeviceRoleEditView.as_view(), name='devicerole_add'),
|
||||
|
@ -869,6 +869,13 @@ class DeviceTypeDeviceBaysView(DeviceTypeComponentsView):
|
||||
viewname = 'dcim:devicetype_devicebays'
|
||||
|
||||
|
||||
class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
||||
child_model = InventoryItemTemplate
|
||||
table = tables.InventoryItemTemplateTable
|
||||
filterset = filtersets.InventoryItemTemplateFilterSet
|
||||
viewname = 'dcim:devicetype_inventoryitems'
|
||||
|
||||
|
||||
class DeviceTypeEditView(generic.ObjectEditView):
|
||||
queryset = DeviceType.objects.all()
|
||||
model_form = forms.DeviceTypeForm
|
||||
@ -890,6 +897,7 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
||||
'dcim.add_rearporttemplate',
|
||||
'dcim.add_modulebaytemplate',
|
||||
'dcim.add_devicebaytemplate',
|
||||
'dcim.add_inventoryitemtemplate',
|
||||
]
|
||||
queryset = DeviceType.objects.all()
|
||||
model_form = forms.DeviceTypeImportForm
|
||||
@ -903,6 +911,7 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
||||
('front-ports', forms.FrontPortTemplateImportForm),
|
||||
('module-bays', forms.ModuleBayTemplateImportForm),
|
||||
('device-bays', forms.DeviceBayTemplateImportForm),
|
||||
('inventory-items', forms.InventoryItemTemplateImportForm),
|
||||
))
|
||||
|
||||
def prep_related_object_data(self, parent, data):
|
||||
@ -1362,6 +1371,51 @@ class DeviceBayTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.DeviceBayTemplateTable
|
||||
|
||||
|
||||
#
|
||||
# Inventory item templates
|
||||
#
|
||||
|
||||
class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
form = forms.DeviceTypeComponentCreateForm
|
||||
model_form = forms.InventoryItemTemplateForm
|
||||
|
||||
def alter_object(self, instance, request):
|
||||
# Set component (if any)
|
||||
component_type = request.GET.get('component_type')
|
||||
component_id = request.GET.get('component_id')
|
||||
|
||||
if component_type and component_id:
|
||||
content_type = get_object_or_404(ContentType, pk=component_type)
|
||||
instance.component = get_object_or_404(content_type.model_class(), pk=component_id)
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class InventoryItemTemplateEditView(generic.ObjectEditView):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
model_form = forms.InventoryItemTemplateForm
|
||||
|
||||
|
||||
class InventoryItemTemplateDeleteView(generic.ObjectDeleteView):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
|
||||
|
||||
class InventoryItemTemplateBulkEditView(generic.BulkEditView):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
table = tables.InventoryItemTemplateTable
|
||||
form = forms.InventoryItemTemplateBulkEditForm
|
||||
|
||||
|
||||
class InventoryItemTemplateBulkRenameView(generic.BulkRenameView):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
|
||||
|
||||
class InventoryItemTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||
queryset = InventoryItemTemplate.objects.all()
|
||||
table = tables.InventoryItemTemplateTable
|
||||
|
||||
|
||||
#
|
||||
# Device roles
|
||||
#
|
||||
|
@ -44,6 +44,9 @@
|
||||
{% if perms.dcim.add_devicebaytemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:devicebaytemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_devicebays' pk=object.pk %}">Device Bays</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_inventoryitemtemplate %}
|
||||
<li><a class="dropdown-item" href="{% url 'dcim:inventoryitemtemplate_add' %}?device_type={{ object.pk }}&return_url={% url 'dcim:devicetype_inventoryitems' pk=object.pk %}">Inventory Items</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -127,4 +130,12 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% with tab_name='inventory-item-templates' inventoryitem_count=object.inventoryitemtemplates.count %}
|
||||
{% if active_tab == tab_name or inventoryitem_count %}
|
||||
<li role="presentation" class="nav-item">
|
||||
<a class="nav-link {% if active_tab == tab_name %} active{% endif %}" href="{% url 'dcim:devicetype_inventoryitems' pk=object.pk %}">Inventory Items {% badge inventoryitem_count %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user