Changes for JIRA-3333

This commit is contained in:
Naveen Mereddi 2020-04-03 11:11:32 -05:00
parent 4ab3854d66
commit 80943c7ab9
22 changed files with 1373 additions and 188 deletions

View File

@ -2,7 +2,7 @@ from rest_framework import serializers
from dcim.constants import CONNECTION_STATUS_CHOICES
from dcim.models import (
Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceType, DeviceRole, FrontPort, FrontPortTemplate,
Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceType, InventoryItemType, DeviceRole, FrontPort, FrontPortTemplate,
Interface, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerPanel, PowerPort, PowerPortTemplate, Rack,
RackGroup, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
@ -16,6 +16,7 @@ __all__ = [
'NestedDeviceRoleSerializer',
'NestedDeviceSerializer',
'NestedDeviceTypeSerializer',
'NestedInventoryItemTypeSerializer',
'NestedFrontPortSerializer',
'NestedFrontPortTemplateSerializer',
'NestedInterfaceSerializer',
@ -112,6 +113,16 @@ class NestedDeviceTypeSerializer(WritableNestedSerializer):
fields = ['id', 'url', 'manufacturer', 'model', 'slug', 'display_name', 'device_count']
class NestedInventoryItemTypeSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:inventoryitemtype-detail')
manufacturer = NestedManufacturerSerializer(read_only=True)
instance_count = serializers.IntegerField(read_only=True)
class Meta:
model = InventoryItemType
fields = ['id', 'url', 'manufacturer', 'model', 'slug', 'instance_count']
class NestedPowerPortTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerporttemplate-detail')

View File

@ -9,7 +9,7 @@ from dcim.constants import *
from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
Manufacturer, InventoryItem, InventoryItemRole, InventoryItemType, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
@ -332,10 +332,41 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
fields = ['id', 'device_type', 'name']
#
# Inventory Item Role
#
class InventoryItemRoleSerializer(ValidatedModelSerializer):
inventoryitem_count = serializers.IntegerField(read_only=True)
class Meta:
model = InventoryItemRole
fields = [
'id', 'name', 'slug', 'inventoryitem_count'
]
#
# Inventory Item Type
#
class InventoryItemTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
manufacturer = NestedManufacturerSerializer()
instance_count = serializers.IntegerField(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = InventoryItemType
fields = [
'id', 'manufacturer', 'model', 'slug', 'part_number', 'tags', 'created',
'last_updated', 'instance_count',
]
#
# Devices
#
class DeviceRoleSerializer(ValidatedModelSerializer):
device_count = serializers.IntegerField(read_only=True)
virtualmachine_count = serializers.IntegerField(read_only=True)
@ -612,14 +643,13 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
device = NestedDeviceSerializer()
# Provide a default value to satisfy UniqueTogetherValidator
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
tags = TagListSerializerField(required=False)
class Meta:
model = InventoryItem
fields = [
'id', 'device', 'parent', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
'description', 'tags',
'id', 'device', 'parent', 'name', 'part_id', 'serial', 'asset_tag', 'discovered',
'description', 'tags', 'role', 'type', 'site',
]

View File

@ -65,6 +65,12 @@ router.register('interface-connections', views.InterfaceConnectionViewSet, basen
# Cables
router.register('cables', views.CableViewSet)
# Inventory Item Roles
router.register('inventory-item-roles', views.InventoryItemRoleViewSet)
# Inventory Item types
router.register('inventory-item-types', views.InventoryItemTypeViewSet)
# Virtual chassis
router.register('virtual-chassis', views.VirtualChassisViewSet)

View File

@ -17,7 +17,7 @@ from dcim import filters
from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
Manufacturer, InventoryItem, InventoryItemRole, InventoryItemType, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
@ -269,7 +269,7 @@ class RackReservationViewSet(ModelViewSet):
class ManufacturerViewSet(ModelViewSet):
queryset = Manufacturer.objects.annotate(
devicetype_count=get_subquery(DeviceType, 'manufacturer'),
inventoryitem_count=get_subquery(InventoryItem, 'manufacturer'),
inventoryitem_count=get_subquery(InventoryItem, 'type__manufacturer'),
platform_count=get_subquery(Platform, 'manufacturer')
)
serializer_class = serializers.ManufacturerSerializer
@ -352,6 +352,32 @@ class DeviceRoleViewSet(ModelViewSet):
serializer_class = serializers.DeviceRoleSerializer
filterset_class = filters.DeviceRoleFilterSet
#
# Inventory item roles
#
class InventoryItemRoleViewSet(ModelViewSet):
queryset = InventoryItemRole.objects.annotate(
inventoryitem_count=get_subquery(InventoryItem, 'role'),
)
serializer_class = serializers.InventoryItemRoleSerializer
filterset_class = filters.InventoryItemRoleFilterSet
#
# Inventory Item types
#
class InventoryItemTypeViewSet(ModelViewSet):
queryset = InventoryItemType.objects.prefetch_related('manufacturer').prefetch_related('tags').annotate(
instance_count=Count('instances')
)
serializer_class = serializers.InventoryItemTypeSerializer
filterset_class = filters.InventoryItemTypeFilterSet
#
# Platforms
@ -576,7 +602,7 @@ class DeviceBayViewSet(ModelViewSet):
class InventoryItemViewSet(ModelViewSet):
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
queryset = InventoryItem.objects.prefetch_related('device', 'type__manufacturer').prefetch_related('tags')
serializer_class = serializers.InventoryItemSerializer
filterset_class = filters.InventoryItemFilterSet

View File

@ -15,9 +15,9 @@ from .constants import *
from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
InventoryItem, InventoryItemRole, InventoryItemType, Manufacturer, Platform, PowerFeed, PowerOutlet,
PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort,
RearPortTemplate, Region, Site, VirtualChassis,
)
@ -39,6 +39,8 @@ __all__ = (
'InterfaceFilterSet',
'InterfaceTemplateFilterSet',
'InventoryItemFilterSet',
'InventoryItemRoleFilterSet',
'InventoryItemTypeFilterSet',
'ManufacturerFilterSet',
'PlatformFilterSet',
'PowerConnectionFilterSet',
@ -479,6 +481,13 @@ class DeviceRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
fields = ['id', 'name', 'slug', 'color', 'vm_role']
class InventoryItemRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
class Meta:
model = InventoryItemRole
fields = ['id', 'name', 'slug']
class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer',
@ -989,15 +998,25 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
queryset=InventoryItem.objects.all(),
label='Parent inventory item (ID)',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItemRole.objects.all(),
label='Inventory Item Role (ID)'
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
role = django_filters.ModelMultipleChoiceFilter(
field_name='role__slug',
queryset=InventoryItemRole.objects.all(),
to_field_name="slug",
label="Inventory Item Role (slug)"
)
type_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItemType.objects.all(),
label='Inventory Item Type (ID)'
)
type = django_filters.ModelMultipleChoiceFilter(
field_name='type__slug',
queryset=InventoryItemType.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
label='Inventory Item Type (slug)'
)
serial = django_filters.CharFilter(
lookup_expr='iexact'
@ -1020,6 +1039,44 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
return queryset.filter(qs_filter)
class InventoryItemTypeFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
)
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)',
)
tag = TagFilter()
class Meta:
model = InventoryItemType
fields = [
'model', 'slug', 'part_number'
]
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(manufacturer__name__icontains=value) |
Q(model__icontains=value) |
Q(part_number__icontains=value) |
Q(slug__icontains=value)
)
class VirtualChassisFilterSet(BaseFilterSet):
q = django_filters.CharFilter(
method='search',

View File

@ -32,8 +32,9 @@ from .constants import *
from .models import (
Cable, DeviceBay, DeviceBayTemplate, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate,
Device, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate,
Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
InventoryItem, InventoryItemRole, InventoryItemType, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate,
PowerPanel, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate,
Region, Site, VirtualChassis,
)
DEVICE_BY_PK_RE = r'{\d+\}'
@ -58,7 +59,6 @@ def get_device_by_name_or_pk(name):
class DeviceComponentFilterForm(BootstrapMixin, forms.Form):
field_order = [
'q', 'region', 'site'
]
@ -923,7 +923,6 @@ class ManufacturerForm(BootstrapMixin, forms.ModelForm):
class ManufacturerCSVForm(forms.ModelForm):
class Meta:
model = Manufacturer
fields = Manufacturer.csv_headers
@ -1065,7 +1064,6 @@ class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
#
class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ConsolePortTemplate
fields = [
@ -1105,7 +1103,6 @@ class ConsolePortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ConsoleServerPortTemplate
fields = [
@ -1145,7 +1142,6 @@ class ConsoleServerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = PowerPortTemplate
fields = [
@ -1205,7 +1201,6 @@ class PowerPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = PowerOutletTemplate
fields = [
@ -1216,7 +1211,6 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Limit power_port choices to current DeviceType
@ -1280,7 +1274,6 @@ class PowerOutletTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = InterfaceTemplate
fields = [
@ -1330,7 +1323,6 @@ class InterfaceTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = FrontPortTemplate
fields = [
@ -1342,7 +1334,6 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Limit rear_port choices to current DeviceType
@ -1431,7 +1422,6 @@ class FrontPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = RearPortTemplate
fields = [
@ -1478,7 +1468,6 @@ class RearPortTemplateBulkEditForm(BootstrapMixin, BulkEditForm):
class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = DeviceBayTemplate
fields = [
@ -1537,7 +1526,6 @@ class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm):
class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = ConsolePortTemplate
fields = [
@ -1546,7 +1534,6 @@ class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = ConsoleServerPortTemplate
fields = [
@ -1555,7 +1542,6 @@ class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
class PowerPortTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = PowerPortTemplate
fields = [
@ -1619,7 +1605,6 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
class Meta:
model = DeviceBayTemplate
fields = [
@ -1814,7 +1799,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
if 'device_type' in kwargs['initial'] and 'manufacturer' not in kwargs['initial']:
device_type_id = kwargs['initial']['device_type']
manufacturer_id = DeviceType.objects.filter(pk=device_type_id).values_list('manufacturer__pk', flat=True).first()
manufacturer_id = DeviceType.objects.filter(pk=device_type_id).values_list('manufacturer__pk',
flat=True).first()
kwargs['initial']['manufacturer'] = manufacturer_id
if 'cluster' in kwargs['initial'] and 'cluster_group' not in kwargs['initial']:
@ -3671,7 +3657,6 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, forms.ModelForm):
class CableForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Cable
fields = [
@ -3685,7 +3670,6 @@ class CableForm(BootstrapMixin, forms.ModelForm):
class CableCSVForm(forms.ModelForm):
# Termination A
side_a_device = FlexibleModelChoiceField(
queryset=Device.objects.all(),
@ -3853,7 +3837,6 @@ class CableBulkEditForm(BootstrapMixin, BulkEditForm):
]
def clean(self):
# Validate length/unit
length = self.cleaned_data.get('length')
length_unit = self.cleaned_data.get('length_unit')
@ -3970,7 +3953,6 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
)
def __init__(self, device_bay, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['installed_device'].queryset = Device.objects.filter(
@ -4187,6 +4169,30 @@ class InventoryItemCSVForm(forms.ModelForm):
'invalid_choice': 'Invalid manufacturer.',
}
)
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
to_field_name='name',
required=False,
error_messages={
"invalid_choice": 'Invalid site.',
}
)
role = forms.ModelChoiceField(
queryset=InventoryItemRole.objects.all(),
to_field_name='name',
required=False,
error_messages={
'invalid_choice': 'Invalid item role.',
}
)
type = forms.ModelChoiceField(
queryset=InventoryItemType.objects.all(),
to_field_name='model',
required=False,
error_messages={
'invalid_choice': 'Invalie item type.',
}
)
class Meta:
model = InventoryItem
@ -4202,10 +4208,6 @@ class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):
queryset=Device.objects.all(),
required=False
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False
)
part_id = forms.CharField(
max_length=50,
required=False,
@ -4215,10 +4217,25 @@ class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):
max_length=100,
required=False
)
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
widget=APISelect(
api_url="/api/dcim/sites"
)
)
role = DynamicModelChoiceField(
queryset=InventoryItemRole.objects.all(),
required=False
)
type = DynamicModelChoiceField(
queryset=InventoryItemType.objects.all(),
required=False
)
class Meta:
nullable_fields = [
'manufacturer', 'part_id', 'description',
'part_id', 'description', 'site', 'role', 'type',
]
@ -4263,6 +4280,16 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form):
value_field="slug",
)
)
role = DynamicModelMultipleChoiceField(
queryset=InventoryItemRole.objects.all(),
to_field_name='slug',
required=False,
widget=APISelect(
api_url="/api/dcim/inventory-item-roles/",
value_field="slug",
)
)
discovered = forms.NullBooleanField(
required=False,
widget=StaticSelect2(
@ -4271,11 +4298,109 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form):
)
tag = TagFilterField(model)
#
# Inventory Item types
#
class InventoryItemTypeForm(BootstrapMixin, CustomFieldModelForm):
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
widget=APISelect(
api_url="/api/dcim/manufacturers/",
)
)
slug = SlugField(
slug_source='model'
)
tags = TagField(
required=False
)
class Meta:
model = InventoryItemType
fields = [
'manufacturer', 'model', 'slug', 'part_number', 'tags',
]
class InventoryItemTypeImportForm(BootstrapMixin, forms.ModelForm):
manufacturer = forms.ModelChoiceField(
queryset=Manufacturer.objects.all(),
to_field_name='name'
)
class Meta:
model = InventoryItemType
fields = [
'manufacturer', 'model', 'slug', 'part_number',
]
class InventoryItemTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(
queryset=InventoryItemType.objects.all(),
widget=forms.MultipleHiddenInput()
)
manufacturer = DynamicModelChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
widget=APISelect(
api_url="/api/dcim/manufacturers"
)
)
class Meta:
nullable_fields = []
class InventoryItemTypeFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = InventoryItemType
q = forms.CharField(
required=False,
label='Search'
)
manufacturer = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
to_field_name='slug',
required=False,
widget=APISelectMultiple(
api_url="/api/dcim/manufacturers/",
value_field="slug",
)
)
tag = TagFilterField(model)
#
# Inventory Item Role
#
class InventoryItemRoleCSVForm(forms.ModelForm):
slug = SlugField()
class Meta:
model = InventoryItemRole
fields = InventoryItemRole.csv_headers
help_texts = {
'name': 'Name of inventory item role',
}
class InventoryItemRoleForm(BootstrapMixin, forms.ModelForm):
slug = SlugField()
class Meta:
model = InventoryItemRole
fields = [
'name', 'slug',
]
#
# Virtual chassis
#
class DeviceSelectionForm(forms.Form):
pk = forms.ModelMultipleChoiceField(
queryset=Device.objects.all(),

View File

@ -0,0 +1,66 @@
# Generated by Django 2.2.10 on 2020-03-27 01:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0099_powerfeed_negative_voltage'),
]
operations = [
migrations.CreateModel(
name='InventoryItemRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('name', models.CharField(max_length=50, unique=True)),
('slug', models.SlugField(unique=True)),
],
options={
'ordering': ['name'],
},
),
migrations.RemoveField(
model_name='inventoryitem',
name='manufacturer',
),
migrations.AddField(
model_name='inventoryitem',
name='site',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.Site'),
),
migrations.AlterField(
model_name='inventoryitem',
name='device',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.Device'),
),
migrations.CreateModel(
name='InventoryItemType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('model', models.CharField(max_length=50)),
('part_number', models.CharField(blank=True, max_length=50)),
('slug', models.SlugField(unique=True)),
('manufacturer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='item_types', to='dcim.Manufacturer')),
],
options={
'ordering': ['model'],
},
),
migrations.AddField(
model_name='inventoryitem',
name='role',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.InventoryItemRole'),
),
migrations.AddField(
model_name='inventoryitem',
name='type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='items', to='dcim.InventoryItemType'),
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 2.2.10 on 2020-03-31 19:43
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('extras', '0038_webhook_template_support'),
('dcim', '0100_auto_20200327_0158'),
]
operations = [
migrations.AlterModelOptions(
name='inventoryitemtype',
options={'ordering': ['manufacturer', 'model']},
),
migrations.AddField(
model_name='inventoryitemtype',
name='tags',
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
),
migrations.AlterField(
model_name='inventoryitem',
name='type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.InventoryItemType'),
),
migrations.AlterField(
model_name='inventoryitemtype',
name='manufacturer',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventory_item_types', to='dcim.Manufacturer'),
),
migrations.AlterField(
model_name='inventoryitemtype',
name='slug',
field=models.SlugField(),
),
migrations.AlterUniqueTogether(
name='inventoryitemtype',
unique_together={('manufacturer', 'slug'), ('manufacturer', 'model')},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.11 on 2020-04-01 18:50
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0101_auto_20200331_1943'),
]
operations = [
migrations.AlterField(
model_name='inventoryitem',
name='role',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventoryitems', to='dcim.InventoryItemRole'),
),
]

View File

@ -52,6 +52,8 @@ __all__ = (
'Interface',
'InterfaceTemplate',
'InventoryItem',
'InventoryItemRole',
'InventoryItemType',
'Manufacturer',
'Platform',
'PowerFeed',
@ -2179,3 +2181,91 @@ class Cable(ChangeLoggedModel):
b_endpoint = b_path[-1][2]
return a_endpoint, b_endpoint, path_status
#
# Inventory Item Role
#
class InventoryItemRole(ChangeLoggedModel):
"""
Inventory Item Role
"""
name = models.CharField(
max_length=50,
unique=True
)
slug = models.SlugField(
unique=True
)
csv_headers = ['name', 'slug']
class Meta:
ordering = ['name']
def __str__(self):
return self.name
def to_csv(self):
return (
self.name,
self.slug,
)
class InventoryItemType(ChangeLoggedModel):
manufacturer = models.ForeignKey(
to='dcim.Manufacturer',
on_delete=models.PROTECT,
related_name='inventory_item_types'
)
model = models.CharField(
max_length=50
)
slug = models.SlugField()
part_number = models.CharField(
max_length=50,
blank=True,
help_text='Discrete part number (optional)'
)
tags = TaggableManager(through=TaggedItem)
clone_fields = [
'manufacturer'
]
class Meta:
ordering = ['manufacturer', 'model']
unique_together = [
['manufacturer', 'model'],
['manufacturer', 'slug'],
]
def __str__(self):
return self.model
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def get_absolute_url(self):
return reverse('dcim:inventoryitemtype', args=[self.pk])
def to_yaml(self):
data = OrderedDict((
('manufacturer', self.manufacturer.name),
('model', self.model),
('slug', self.slug),
('part_number', self.part_number),
))
return yaml.dump(dict(data), sort_keys=False)
def save(self, *args, **kwargs):
ret = super().save(*args, **kwargs)
return ret
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)

View File

@ -1027,8 +1027,28 @@ class InventoryItem(ComponentModel):
"""
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.CASCADE,
related_name='inventory_items'
on_delete=models.SET_NULL,
null=True
)
site = models.ForeignKey(
to='dcim.Site',
on_delete=models.PROTECT,
null=True,
blank=True
)
role = models.ForeignKey(
to='dcim.InventoryItemRole',
on_delete=models.PROTECT,
related_name='inventoryitems',
null=True,
blank=True
)
type = models.ForeignKey(
to='dcim.InventoryItemType',
on_delete=models.PROTECT,
related_name='instances',
null=True,
blank=True
)
parent = models.ForeignKey(
to='self',
@ -1046,13 +1066,6 @@ class InventoryItem(ComponentModel):
max_length=100,
blank=True
)
manufacturer = models.ForeignKey(
to='dcim.Manufacturer',
on_delete=models.PROTECT,
related_name='inventory_items',
blank=True,
null=True
)
part_id = models.CharField(
max_length=50,
verbose_name='Part ID',
@ -1079,7 +1092,7 @@ class InventoryItem(ComponentModel):
tags = TaggableManager(through=TaggedItem)
csv_headers = [
'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
'device', 'name', 'part_id', 'serial', 'asset_tag', 'discovered', 'description',
]
class Meta:
@ -1090,13 +1103,15 @@ class InventoryItem(ComponentModel):
return self.name
def get_absolute_url(self):
return reverse('dcim:device_inventory', kwargs={'pk': self.device.pk})
return reverse('dcim:inventoryitem_list')
def to_csv(self):
return (
self.device.name or '{{{}}}'.format(self.device.pk),
self.name,
self.manufacturer.name if self.manufacturer else None,
self.site,
self.role,
self.type,
self.part_id,
self.serial,
self.asset_tag,

View File

@ -6,7 +6,7 @@ from utilities.tables import BaseTable, BooleanColumn, ColorColumn, ToggleColumn
from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
InventoryItem, InventoryItemRole, InventoryItemType, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
@ -112,6 +112,20 @@ DEVICEROLE_ACTIONS = """
{% endif %}
"""
INVENTORYITEMROLE_ACTIONS = """
<a href="{% url 'dcim:inventoryitemrole_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Change log">
<i class="fa fa-history"></i>
</a>
{% if perms.dcim.change_inventoryitemrole %}
<a href="{% url 'dcim:inventoryitemrole_edit' slug=record.slug %}?return_url={{ request.path }}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
{% endif %}
"""
INVENTORYITEMROLE_INVENTORYITEM_COUNT = """
<a href="{% url 'dcim:inventoryitem_list' %}?role={{ record.slug }}">{{ value }}</a>
"""
DEVICEROLE_DEVICE_COUNT = """
<a href="{% url 'dcim:device_list' %}?role={{ record.slug }}">{{ value }}</a>
"""
@ -160,6 +174,10 @@ DEVICETYPE_INSTANCES_TEMPLATE = """
<a href="{% url 'dcim:device_list' %}?manufacturer_id={{ record.manufacturer_id }}&device_type_id={{ record.pk }}">{{ record.instance_count }}</a>
"""
INVENTORYITEMTYPE_INSTANCES_TEMPLATE = """
<a href="{% url 'dcim:inventoryitem_list' %}?manufacturer_id={{ record.manufacturer_id }}&inventory_item_type_id={{ record.pk }}">{{ record.instance_count }}</a>
"""
UTILIZATION_GRAPH = """
{% load helpers %}
{% utilization_graph value %}
@ -1031,16 +1049,65 @@ class InventoryItemTable(BaseTable):
pk = ToggleColumn()
device = tables.LinkColumn('dcim:device_inventory', args=[Accessor('device.pk')])
manufacturer = tables.Column(accessor=Accessor('manufacturer.name'), verbose_name='Manufacturer')
role = tables.Column(accessor=Accessor('role.name'), verbose_name='Role')
class Meta(BaseTable.Meta):
model = InventoryItem
fields = ('pk', 'device', 'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description')
#
# InventoryItemRoles
#
class InventoryItemRoleTable(BaseTable):
pk = ToggleColumn()
slug = tables.Column(verbose_name='Slug')
inventoryitem_count = tables.TemplateColumn(
template_code=INVENTORYITEMROLE_INVENTORYITEM_COUNT,
accessor=Accessor('inventoryitems.count'),
orderable=False,
verbose_name='Inventory Items'
)
actions = tables.TemplateColumn(
template_code=INVENTORYITEMROLE_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
)
class Meta(BaseTable.Meta):
model = InventoryItemRole
fields = ('pk', 'name', 'slug', 'inventoryitem_count')
#
# Inventory Item types
#
class InventoryItemTypeTable(BaseTable):
pk = ToggleColumn()
model = tables.LinkColumn(
viewname='dcim:inventoryitemtype',
args=[Accessor('pk')],
verbose_name='Inventory Item Type'
)
instance_count = tables.TemplateColumn(
template_code=INVENTORYITEMTYPE_INSTANCES_TEMPLATE,
verbose_name='Instances'
)
class Meta(BaseTable.Meta):
model = InventoryItemType
fields = (
'pk', 'model', 'manufacturer', 'part_number', 'slug',
'instance_count',
)
#
# Virtual chassis
#
class VirtualChassisTable(BaseTable):
pk = ToggleColumn()
master = tables.LinkColumn()

View File

@ -6,13 +6,13 @@ from rest_framework import status
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from dcim.api import serializers
from dcim.choices import *
from dcim.constants import *
from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, Interface, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet, PowerOutletTemplate, PowerPanel,
Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
InventoryItem, InventoryItemRole, InventoryItemType, Platform, PowerFeed, PowerPort, PowerPortTemplate, PowerOutlet,
PowerOutletTemplate, PowerPanel, Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site, VirtualChassis,
)
from dcim.constants import *
from ipam.models import IPAddress, VLAN
from extras.models import Graph
from utilities.testing import APITestCase, choices_to_dict
@ -105,7 +105,6 @@ class AppTest(APITestCase):
class RegionTest(APITestCase):
def setUp(self):
super().setUp()
self.region1 = Region.objects.create(name='Test Region 1', slug='test-region-1')
@ -113,7 +112,6 @@ class RegionTest(APITestCase):
self.region3 = Region.objects.create(name='Test Region 3', slug='test-region-3')
def test_get_region(self):
url = reverse('dcim-api:region-detail', kwargs={'pk': self.region1.pk})
response = self.client.get(url, **self.header)
@ -3249,7 +3247,7 @@ class InventoryItemTest(APITestCase):
super().setUp()
site = Site.objects.create(name='Test Site 1', slug='test-site-1')
self.site = Site.objects.create(name='Test Site 1', slug='test-site-1')
self.manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
devicetype = DeviceType.objects.create(
manufacturer=self.manufacturer, model='Test Device Type 1', slug='test-device-type-1'
@ -3258,8 +3256,16 @@ class InventoryItemTest(APITestCase):
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
)
self.device = Device.objects.create(
device_type=devicetype, device_role=devicerole, name='Test Device 1', site=site
device_type=devicetype, device_role=devicerole, name='Test Device 1', site=self.site
)
self.inventoryitemrole = InventoryItemRole.objects.create(
name='Inventory Item Role 1', slug='inventory-item-role-1'
)
self.inventoryitemtype = InventoryItemType.objects.create(
model='Inventory Item 1', manufacturer=self.manufacturer, slug='inventory-item-type-1'
)
self.inventoryitem1 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 1')
self.inventoryitem2 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 2')
self.inventoryitem3 = InventoryItem.objects.create(device=self.device, name='Test Inventory Item 3')
@ -3282,9 +3288,11 @@ class InventoryItemTest(APITestCase):
data = {
'device': self.device.pk,
'site': self.site.pk,
'type': self.inventoryitemtype.pk,
'role': self.inventoryitemrole.pk,
'parent': self.inventoryitem1.pk,
'name': 'Test Inventory Item 4',
'manufacturer': self.manufacturer.pk,
}
url = reverse('dcim-api:inventoryitem-list')
@ -3296,28 +3304,36 @@ class InventoryItemTest(APITestCase):
self.assertEqual(inventoryitem4.device_id, data['device'])
self.assertEqual(inventoryitem4.parent_id, data['parent'])
self.assertEqual(inventoryitem4.name, data['name'])
self.assertEqual(inventoryitem4.manufacturer_id, data['manufacturer'])
self.assertEqual(inventoryitem4.site_id, data['site'])
self.assertEqual(inventoryitem4.type_id, data['type'])
self.assertEqual(inventoryitem4.role_id, data['role'])
def test_create_inventoryitem_bulk(self):
data = [
{
'device': self.device.pk,
'site': self.site.pk,
'type': self.inventoryitemtype.pk,
'role': self.inventoryitemrole.pk,
'parent': self.inventoryitem1.pk,
'name': 'Test Inventory Item 4',
'manufacturer': self.manufacturer.pk,
},
{
'device': self.device.pk,
'site': self.site.pk,
'type': self.inventoryitemtype.pk,
'role': self.inventoryitemrole.pk,
'parent': self.inventoryitem1.pk,
'name': 'Test Inventory Item 5',
'manufacturer': self.manufacturer.pk,
},
{
'device': self.device.pk,
'site': self.site.pk,
'type': self.inventoryitemtype.pk,
'role': self.inventoryitemrole.pk,
'parent': self.inventoryitem1.pk,
'name': 'Test Inventory Item 6',
'manufacturer': self.manufacturer.pk,
},
]
@ -3334,9 +3350,11 @@ class InventoryItemTest(APITestCase):
data = {
'device': self.device.pk,
'site': self.site.pk,
'type': self.inventoryitemtype.pk,
'role': self.inventoryitemrole.pk,
'parent': self.inventoryitem1.pk,
'name': 'Test Inventory Item X',
'manufacturer': self.manufacturer.pk,
}
url = reverse('dcim-api:inventoryitem-detail', kwargs={'pk': self.inventoryitem1.pk})
@ -3348,7 +3366,6 @@ class InventoryItemTest(APITestCase):
self.assertEqual(inventoryitem1.device_id, data['device'])
self.assertEqual(inventoryitem1.parent_id, data['parent'])
self.assertEqual(inventoryitem1.name, data['name'])
self.assertEqual(inventoryitem1.manufacturer_id, data['manufacturer'])
def test_delete_inventoryitem(self):
@ -4329,3 +4346,234 @@ class PowerFeedTest(APITestCase):
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(PowerFeed.objects.count(), 5)
class InventoryItemRoleTest(APITestCase):
def setUp(self):
super().setUp()
self.inventoryitemrole1 = InventoryItemRole.objects.create(
name='Test Inventory Item Role 1', slug='test-inventory-item-role-1'
)
self.inventoryitem1 = InventoryItem.objects.create(name='Test Inventory Item 1', role=self.inventoryitemrole1)
self.inventoryitemrole2 = InventoryItemRole.objects.create(
name='Test Inventory Item Role 2', slug='test-inventory-item-role-2'
)
self.inventoryitemrole3 = InventoryItemRole.objects.create(
name='Test Inventory Item Role 3', slug='test-inventory-item-role-3'
)
def test_get_inventoryitemrole(self):
url = reverse('dcim-api:inventoryitemrole-detail', kwargs={'pk': self.inventoryitemrole1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['name'], self.inventoryitemrole1.name)
def test_list_inventoryitemroles(self):
url = reverse('dcim-api:inventoryitemrole-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
def test_list_inventoryitemroles_brief(self):
url = reverse('dcim-api:inventoryitemrole-list')
response = self.client.get('{}?brief=1'.format(url), **self.header)
self.assertEqual(
sorted(response.data['results'][0]),
['id', 'inventoryitem_count', 'name', 'slug', ]
)
def test_create_inventoryitemrole(self):
data = {
'name': 'Test Inventory Item Role 4',
'slug': 'test-inventoryitem-role-4',
}
url = reverse('dcim-api:inventoryitemrole-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(InventoryItemRole.objects.count(), 4)
inventoryitemrole4 = InventoryItemRole.objects.get(pk=response.data['id'])
self.assertEqual(inventoryitemrole4.name, data['name'])
self.assertEqual(inventoryitemrole4.slug, data['slug'])
def test_create_inventoryitemrole_bulk(self):
data = [
{
'name': 'Test Inventory Item Role 4',
'slug': 'test-inventoryitem-role-4',
},
{
'name': 'Test Inventory Item Role 5',
'slug': 'test-inventoryitem-role-5',
},
{
'name': 'Test Inventory Item Role 6',
'slug': 'test-inventoryitem-role-6',
},
]
url = reverse('dcim-api:inventoryitemrole-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(InventoryItemRole.objects.count(), 6)
self.assertEqual(response.data[0]['name'], data[0]['name'])
self.assertEqual(response.data[1]['name'], data[1]['name'])
self.assertEqual(response.data[2]['name'], data[2]['name'])
def test_update_inventoryitemrole(self):
data = {
'name': 'Test Inventory Item Role X',
'slug': 'test-inventoryitem-role-x',
}
url = reverse('dcim-api:inventoryitemrole-detail', kwargs={'pk': self.inventoryitemrole1.pk})
response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(InventoryItemRole.objects.count(), 3)
inventoryitemrole1 = InventoryItemRole.objects.get(pk=response.data['id'])
self.assertEqual(inventoryitemrole1.name, data['name'])
self.assertEqual(inventoryitemrole1.slug, data['slug'])
def test_delete_inventoryitemrole(self):
url = reverse('dcim-api:inventoryitemrole-detail', kwargs={'pk': self.inventoryitemrole2.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(InventoryItemRole.objects.count(), 2)
def test_delete_inventoryitemrole_with_items(self):
url = reverse('dcim-api:inventoryitemrole-detail', kwargs={'pk': self.inventoryitemrole1.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_409_CONFLICT)
self.assertEqual(InventoryItemRole.objects.count(), 3)
class InventoryItemTypeTest(APITestCase):
def setUp(self):
super().setUp()
self.manufacturer1 = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
self.manufacturer2 = Manufacturer.objects.create(name='Test Manufacturer 2', slug='test-manufacturer-2')
self.inventoryitemtype1 = InventoryItemType.objects.create(
manufacturer=self.manufacturer1, model='Test Inventory Item Type 1', slug='test-inventory-item-type-1'
)
self.inventoryitemtype2 = InventoryItemType.objects.create(
manufacturer=self.manufacturer1, model='Test Inventory Item Type 2', slug='test-inventory-item-type-2'
)
self.inventoryitemtype3 = InventoryItemType.objects.create(
manufacturer=self.manufacturer1, model='Test Inventory Item Type 3', slug='test-inventory-item-type-3'
)
def test_get_inventoryitemtype(self):
url = reverse('dcim-api:inventoryitemtype-detail', kwargs={'pk': self.inventoryitemtype1.pk})
response = self.client.get(url, **self.header)
self.assertEqual(response.data['model'], self.inventoryitemtype1.model)
def test_list_inventoryitemtypes(self):
url = reverse('dcim-api:inventoryitemtype-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3)
def test_list_inventoryitemtypes_brief(self):
url = reverse('dcim-api:inventoryitemtype-list')
response = self.client.get('{}?brief=1'.format(url), **self.header)
self.assertEqual(
sorted(response.data['results'][0]),
['id', 'instance_count', 'manufacturer', 'model', 'slug', 'url']
)
def test_create_inventoryitemtype(self):
data = {
'manufacturer': self.manufacturer1.pk,
'model': 'Test Inventory Item Type 4',
'slug': 'test-inventory-item-type-4',
}
url = reverse('dcim-api:inventoryitemtype-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(InventoryItemType.objects.count(), 4)
inventoryitemtype4 = InventoryItemType.objects.get(pk=response.data['id'])
self.assertEqual(inventoryitemtype4.manufacturer_id, data['manufacturer'])
self.assertEqual(inventoryitemtype4.model, data['model'])
self.assertEqual(inventoryitemtype4.slug, data['slug'])
def test_create_inventoryitemtype_bulk(self):
data = [
{
'manufacturer': self.manufacturer1.pk,
'model': 'Test Inventory Item Type 4',
'slug': 'test-inventory-item-type-4',
},
{
'manufacturer': self.manufacturer1.pk,
'model': 'Test Inventory Item Type 5',
'slug': 'test-inventory-item-type-5',
},
{
'manufacturer': self.manufacturer1.pk,
'model': 'Test Inventory Item Type 6',
'slug': 'test-inventory-item-type-6',
},
]
url = reverse('dcim-api:inventoryitemtype-list')
response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(InventoryItemType.objects.count(), 6)
self.assertEqual(response.data[0]['model'], data[0]['model'])
self.assertEqual(response.data[1]['model'], data[1]['model'])
self.assertEqual(response.data[2]['model'], data[2]['model'])
def test_update_inventoryitemtype(self):
data = {
'manufacturer': self.manufacturer2.pk,
'model': 'Test Inventory Item Type X',
'slug': 'test-inventory-item-type-x',
}
url = reverse('dcim-api:inventoryitemtype-detail', kwargs={'pk': self.inventoryitemtype1.pk})
response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(InventoryItemType.objects.count(), 3)
inventoryitemtype1 = InventoryItemType.objects.get(pk=response.data['id'])
self.assertEqual(inventoryitemtype1.manufacturer_id, data['manufacturer'])
self.assertEqual(inventoryitemtype1.model, data['model'])
self.assertEqual(inventoryitemtype1.slug, data['slug'])
def test_delete_inventoryitemtype(self):
url = reverse('dcim-api:inventoryitemtype-detail', kwargs={'pk': self.inventoryitemtype1.pk})
response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(InventoryItemType.objects.count(), 2)

View File

@ -6,9 +6,9 @@ from dcim.filters import *
from dcim.models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort, PowerPortTemplate, PowerOutlet,
PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
InventoryItem, InventoryItemRole, InventoryItemType, Manufacturer, Platform, PowerFeed, PowerPanel, PowerPort,
PowerPortTemplate, PowerOutlet, PowerOutletTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort,
RearPortTemplate, Region, Site, VirtualChassis,
)
from ipam.models import IPAddress
from tenancy.models import Tenant, TenantGroup
@ -67,7 +67,6 @@ class SiteTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -91,9 +90,15 @@ class SiteTestCase(TestCase):
Tenant.objects.bulk_create(tenants)
sites = (
Site(name='Site 1', slug='site-1', region=regions[0], tenant=tenants[0], status=SiteStatusChoices.STATUS_ACTIVE, facility='Facility 1', asn=65001, latitude=10, longitude=10, contact_name='Contact 1', contact_phone='123-555-0001', contact_email='contact1@example.com'),
Site(name='Site 2', slug='site-2', region=regions[1], tenant=tenants[1], status=SiteStatusChoices.STATUS_PLANNED, facility='Facility 2', asn=65002, latitude=20, longitude=20, contact_name='Contact 2', contact_phone='123-555-0002', contact_email='contact2@example.com'),
Site(name='Site 3', slug='site-3', region=regions[2], tenant=tenants[2], status=SiteStatusChoices.STATUS_RETIRED, facility='Facility 3', asn=65003, latitude=30, longitude=30, contact_name='Contact 3', contact_phone='123-555-0003', contact_email='contact3@example.com'),
Site(name='Site 1', slug='site-1', region=regions[0], tenant=tenants[0],
status=SiteStatusChoices.STATUS_ACTIVE, facility='Facility 1', asn=65001, latitude=10, longitude=10,
contact_name='Contact 1', contact_phone='123-555-0001', contact_email='contact1@example.com'),
Site(name='Site 2', slug='site-2', region=regions[1], tenant=tenants[1],
status=SiteStatusChoices.STATUS_PLANNED, facility='Facility 2', asn=65002, latitude=20, longitude=20,
contact_name='Contact 2', contact_phone='123-555-0002', contact_email='contact2@example.com'),
Site(name='Site 3', slug='site-3', region=regions[2], tenant=tenants[2],
status=SiteStatusChoices.STATUS_RETIRED, facility='Facility 3', asn=65003, latitude=30, longitude=30,
contact_name='Contact 3', contact_phone='123-555-0003', contact_email='contact3@example.com'),
)
Site.objects.bulk_create(sites)
@ -175,7 +180,6 @@ class RackGroupTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -232,7 +236,6 @@ class RackRoleTestCase(TestCase):
@classmethod
def setUpTestData(cls):
rack_roles = (
RackRole(name='Rack Role 1', slug='rack-role-1', color='ff0000'),
RackRole(name='Rack Role 2', slug='rack-role-2', color='00ff00'),
@ -264,7 +267,6 @@ class RackTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -309,9 +311,18 @@ class RackTestCase(TestCase):
Tenant.objects.bulk_create(tenants)
racks = (
Rack(name='Rack 1', facility_id='rack-1', site=sites[0], group=rack_groups[0], tenant=tenants[0], status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001', type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False, outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
Rack(name='Rack 2', facility_id='rack-2', site=sites[1], group=rack_groups[1], tenant=tenants[1], status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002', type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_19IN, u_height=43, desc_units=False, outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
Rack(name='Rack 3', facility_id='rack-3', site=sites[2], group=rack_groups[2], tenant=tenants[2], status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003', type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True, outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH),
Rack(name='Rack 1', facility_id='rack-1', site=sites[0], group=rack_groups[0], tenant=tenants[0],
status=RackStatusChoices.STATUS_ACTIVE, role=rack_roles[0], serial='ABC', asset_tag='1001',
type=RackTypeChoices.TYPE_2POST, width=RackWidthChoices.WIDTH_19IN, u_height=42, desc_units=False,
outer_width=100, outer_depth=100, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
Rack(name='Rack 2', facility_id='rack-2', site=sites[1], group=rack_groups[1], tenant=tenants[1],
status=RackStatusChoices.STATUS_PLANNED, role=rack_roles[1], serial='DEF', asset_tag='1002',
type=RackTypeChoices.TYPE_4POST, width=RackWidthChoices.WIDTH_19IN, u_height=43, desc_units=False,
outer_width=200, outer_depth=200, outer_unit=RackDimensionUnitChoices.UNIT_MILLIMETER),
Rack(name='Rack 3', facility_id='rack-3', site=sites[2], group=rack_groups[2], tenant=tenants[2],
status=RackStatusChoices.STATUS_RESERVED, role=rack_roles[2], serial='GHI', asset_tag='1003',
type=RackTypeChoices.TYPE_CABINET, width=RackWidthChoices.WIDTH_23IN, u_height=44, desc_units=True,
outer_width=300, outer_depth=300, outer_unit=RackDimensionUnitChoices.UNIT_INCH),
)
Rack.objects.bulk_create(racks)
@ -429,7 +440,6 @@ class RackReservationTestCase(TestCase):
@classmethod
def setUpTestData(cls):
sites = (
Site(name='Site 1', slug='site-1'),
Site(name='Site 2', slug='site-2'),
@ -527,7 +537,6 @@ class ManufacturerTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturers = (
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
@ -555,7 +564,6 @@ class DeviceTypeTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturers = (
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
@ -564,9 +572,12 @@ class DeviceTypeTestCase(TestCase):
Manufacturer.objects.bulk_create(manufacturers)
device_types = (
DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True),
DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT),
DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD),
DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1',
u_height=1, is_full_depth=True),
DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2',
u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT),
DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3',
u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD),
)
DeviceType.objects.bulk_create(device_types)
@ -597,8 +608,10 @@ class DeviceTypeTestCase(TestCase):
)
RearPortTemplate.objects.bulk_create(rear_ports)
FrontPortTemplate.objects.bulk_create((
FrontPortTemplate(device_type=device_types[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0]),
FrontPortTemplate(device_type=device_types[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[1]),
FrontPortTemplate(device_type=device_types[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C,
rear_port=rear_ports[0]),
FrontPortTemplate(device_type=device_types[1], name='Front Port 2', type=PortTypeChoices.TYPE_8P8C,
rear_port=rear_ports[1]),
))
DeviceBayTemplate.objects.bulk_create((
DeviceBayTemplate(device_type=device_types[0], name='Device Bay 1'),
@ -692,7 +705,6 @@ class ConsolePortTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -729,7 +741,6 @@ class ConsoleServerPortTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -766,7 +777,6 @@ class PowerPortTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -811,7 +821,6 @@ class PowerOutletTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -822,9 +831,12 @@ class PowerOutletTemplateTestCase(TestCase):
DeviceType.objects.bulk_create(device_types)
PowerOutletTemplate.objects.bulk_create((
PowerOutletTemplate(device_type=device_types[0], name='Power Outlet 1', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A),
PowerOutletTemplate(device_type=device_types[1], name='Power Outlet 2', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B),
PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C),
PowerOutletTemplate(device_type=device_types[0], name='Power Outlet 1',
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A),
PowerOutletTemplate(device_type=device_types[1], name='Power Outlet 2',
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B),
PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3',
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C),
))
def test_id(self):
@ -853,7 +865,6 @@ class InterfaceTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -864,9 +875,12 @@ class InterfaceTemplateTestCase(TestCase):
DeviceType.objects.bulk_create(device_types)
InterfaceTemplate.objects.bulk_create((
InterfaceTemplate(device_type=device_types[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED, mgmt_only=True),
InterfaceTemplate(device_type=device_types[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_GBIC, mgmt_only=False),
InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP, mgmt_only=False),
InterfaceTemplate(device_type=device_types[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED,
mgmt_only=True),
InterfaceTemplate(device_type=device_types[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_GBIC,
mgmt_only=False),
InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP,
mgmt_only=False),
))
def test_id(self):
@ -901,7 +915,6 @@ class FrontPortTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -919,9 +932,12 @@ class FrontPortTemplateTestCase(TestCase):
RearPortTemplate.objects.bulk_create(rear_ports)
FrontPortTemplate.objects.bulk_create((
FrontPortTemplate(device_type=device_types[0], name='Front Port 1', rear_port=rear_ports[0], type=PortTypeChoices.TYPE_8P8C),
FrontPortTemplate(device_type=device_types[1], name='Front Port 2', rear_port=rear_ports[1], type=PortTypeChoices.TYPE_110_PUNCH),
FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2], type=PortTypeChoices.TYPE_BNC),
FrontPortTemplate(device_type=device_types[0], name='Front Port 1', rear_port=rear_ports[0],
type=PortTypeChoices.TYPE_8P8C),
FrontPortTemplate(device_type=device_types[1], name='Front Port 2', rear_port=rear_ports[1],
type=PortTypeChoices.TYPE_110_PUNCH),
FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2],
type=PortTypeChoices.TYPE_BNC),
))
def test_id(self):
@ -950,7 +966,6 @@ class RearPortTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -961,9 +976,12 @@ class RearPortTemplateTestCase(TestCase):
DeviceType.objects.bulk_create(device_types)
RearPortTemplate.objects.bulk_create((
RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C, positions=1),
RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PortTypeChoices.TYPE_110_PUNCH, positions=2),
RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC, positions=3),
RearPortTemplate(device_type=device_types[0], name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C,
positions=1),
RearPortTemplate(device_type=device_types[1], name='Rear Port 2', type=PortTypeChoices.TYPE_110_PUNCH,
positions=2),
RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC,
positions=3),
))
def test_id(self):
@ -996,7 +1014,6 @@ class DeviceBayTemplateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_types = (
@ -1033,7 +1050,6 @@ class DeviceRoleTestCase(TestCase):
@classmethod
def setUpTestData(cls):
device_roles = (
DeviceRole(name='Device Role 1', slug='device-role-1', color='ff0000', vm_role=True),
DeviceRole(name='Device Role 2', slug='device-role-2', color='00ff00', vm_role=True),
@ -1071,7 +1087,6 @@ class PlatformTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturers = (
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
@ -1117,7 +1132,6 @@ class DeviceTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturers = (
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
@ -1198,9 +1212,16 @@ class DeviceTestCase(TestCase):
Tenant.objects.bulk_create(tenants)
devices = (
Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], tenant=tenants[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1, face=DeviceFaceChoices.FACE_FRONT, status=DeviceStatusChoices.STATUS_ACTIVE, cluster=clusters[0], local_context_data={"foo": 123}),
Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], tenant=tenants[1], serial='DEF', asset_tag='1002', site=sites[1], rack=racks[1], position=2, face=DeviceFaceChoices.FACE_FRONT, status=DeviceStatusChoices.STATUS_STAGED, cluster=clusters[1]),
Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], tenant=tenants[2], serial='GHI', asset_tag='1003', site=sites[2], rack=racks[2], position=3, face=DeviceFaceChoices.FACE_REAR, status=DeviceStatusChoices.STATUS_FAILED, cluster=clusters[2]),
Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0],
tenant=tenants[0], serial='ABC', asset_tag='1001', site=sites[0], rack=racks[0], position=1,
face=DeviceFaceChoices.FACE_FRONT, status=DeviceStatusChoices.STATUS_ACTIVE, cluster=clusters[0],
local_context_data={"foo": 123}),
Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1],
tenant=tenants[1], serial='DEF', asset_tag='1002', site=sites[1], rack=racks[1], position=2,
face=DeviceFaceChoices.FACE_FRONT, status=DeviceStatusChoices.STATUS_STAGED, cluster=clusters[1]),
Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2],
tenant=tenants[2], serial='GHI', asset_tag='1003', site=sites[2], rack=racks[2], position=3,
face=DeviceFaceChoices.FACE_REAR, status=DeviceStatusChoices.STATUS_FAILED, cluster=clusters[2]),
)
Device.objects.bulk_create(devices)
@ -1452,7 +1473,6 @@ class ConsolePortTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -1548,7 +1568,6 @@ class ConsoleServerPortTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -1644,7 +1663,6 @@ class PowerPortTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -1678,8 +1696,10 @@ class PowerPortTestCase(TestCase):
power_ports = (
PowerPort(device=devices[0], name='Power Port 1', maximum_draw=100, allocated_draw=50, description='First'),
PowerPort(device=devices[1], name='Power Port 2', maximum_draw=200, allocated_draw=100, description='Second'),
PowerPort(device=devices[2], name='Power Port 3', maximum_draw=300, allocated_draw=150, description='Third'),
PowerPort(device=devices[1], name='Power Port 2', maximum_draw=200, allocated_draw=100,
description='Second'),
PowerPort(device=devices[2], name='Power Port 3', maximum_draw=300, allocated_draw=150,
description='Third'),
)
PowerPort.objects.bulk_create(power_ports)
@ -1748,7 +1768,6 @@ class PowerOutletTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -1781,9 +1800,12 @@ class PowerOutletTestCase(TestCase):
PowerPort.objects.bulk_create(power_ports)
power_outlets = (
PowerOutlet(device=devices[0], name='Power Outlet 1', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A, description='First'),
PowerOutlet(device=devices[1], name='Power Outlet 2', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B, description='Second'),
PowerOutlet(device=devices[2], name='Power Outlet 3', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C, description='Third'),
PowerOutlet(device=devices[0], name='Power Outlet 1', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A,
description='First'),
PowerOutlet(device=devices[1], name='Power Outlet 2', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B,
description='Second'),
PowerOutlet(device=devices[2], name='Power Outlet 3', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C,
description='Third'),
)
PowerOutlet.objects.bulk_create(power_outlets)
@ -1849,7 +1871,6 @@ class InterfaceTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -1876,12 +1897,21 @@ class InterfaceTestCase(TestCase):
Device.objects.bulk_create(devices)
interfaces = (
Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True, mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01', description='First'),
Interface(device=devices[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True, mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02', description='Second'),
Interface(device=devices[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False, mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL, mac_address='00-00-00-00-00-03', description='Third'),
Interface(device=devices[3], name='Interface 4', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True),
Interface(device=devices[3], name='Interface 5', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True, mgmt_only=True),
Interface(device=devices[3], name='Interface 6', type=InterfaceTypeChoices.TYPE_OTHER, enabled=False, mgmt_only=False),
Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_SFP, enabled=True,
mgmt_only=True, mtu=100, mode=InterfaceModeChoices.MODE_ACCESS, mac_address='00-00-00-00-00-01',
description='First'),
Interface(device=devices[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_GBIC, enabled=True,
mgmt_only=True, mtu=200, mode=InterfaceModeChoices.MODE_TAGGED, mac_address='00-00-00-00-00-02',
description='Second'),
Interface(device=devices[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED, enabled=False,
mgmt_only=False, mtu=300, mode=InterfaceModeChoices.MODE_TAGGED_ALL,
mac_address='00-00-00-00-00-03', description='Third'),
Interface(device=devices[3], name='Interface 4', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True,
mgmt_only=True),
Interface(device=devices[3], name='Interface 5', type=InterfaceTypeChoices.TYPE_OTHER, enabled=True,
mgmt_only=True),
Interface(device=devices[3], name='Interface 6', type=InterfaceTypeChoices.TYPE_OTHER, enabled=False,
mgmt_only=False),
)
Interface.objects.bulk_create(interfaces)
@ -1976,7 +2006,6 @@ class FrontPortTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -2013,12 +2042,18 @@ class FrontPortTestCase(TestCase):
RearPort.objects.bulk_create(rear_ports)
front_ports = (
FrontPort(device=devices[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0], rear_port_position=1, description='First'),
FrontPort(device=devices[1], name='Front Port 2', type=PortTypeChoices.TYPE_110_PUNCH, rear_port=rear_ports[1], rear_port_position=2, description='Second'),
FrontPort(device=devices[2], name='Front Port 3', type=PortTypeChoices.TYPE_BNC, rear_port=rear_ports[2], rear_port_position=3, description='Third'),
FrontPort(device=devices[3], name='Front Port 4', type=PortTypeChoices.TYPE_FC, rear_port=rear_ports[3], rear_port_position=1),
FrontPort(device=devices[3], name='Front Port 5', type=PortTypeChoices.TYPE_FC, rear_port=rear_ports[4], rear_port_position=1),
FrontPort(device=devices[3], name='Front Port 6', type=PortTypeChoices.TYPE_FC, rear_port=rear_ports[5], rear_port_position=1),
FrontPort(device=devices[0], name='Front Port 1', type=PortTypeChoices.TYPE_8P8C, rear_port=rear_ports[0],
rear_port_position=1, description='First'),
FrontPort(device=devices[1], name='Front Port 2', type=PortTypeChoices.TYPE_110_PUNCH,
rear_port=rear_ports[1], rear_port_position=2, description='Second'),
FrontPort(device=devices[2], name='Front Port 3', type=PortTypeChoices.TYPE_BNC, rear_port=rear_ports[2],
rear_port_position=3, description='Third'),
FrontPort(device=devices[3], name='Front Port 4', type=PortTypeChoices.TYPE_FC, rear_port=rear_ports[3],
rear_port_position=1),
FrontPort(device=devices[3], name='Front Port 5', type=PortTypeChoices.TYPE_FC, rear_port=rear_ports[4],
rear_port_position=1),
FrontPort(device=devices[3], name='Front Port 6', type=PortTypeChoices.TYPE_FC, rear_port=rear_ports[5],
rear_port_position=1),
)
FrontPort.objects.bulk_create(front_ports)
@ -2079,7 +2114,6 @@ class RearPortTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -2106,9 +2140,12 @@ class RearPortTestCase(TestCase):
Device.objects.bulk_create(devices)
rear_ports = (
RearPort(device=devices[0], name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C, positions=1, description='First'),
RearPort(device=devices[1], name='Rear Port 2', type=PortTypeChoices.TYPE_110_PUNCH, positions=2, description='Second'),
RearPort(device=devices[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC, positions=3, description='Third'),
RearPort(device=devices[0], name='Rear Port 1', type=PortTypeChoices.TYPE_8P8C, positions=1,
description='First'),
RearPort(device=devices[1], name='Rear Port 2', type=PortTypeChoices.TYPE_110_PUNCH, positions=2,
description='Second'),
RearPort(device=devices[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC, positions=3,
description='Third'),
RearPort(device=devices[3], name='Rear Port 4', type=PortTypeChoices.TYPE_FC, positions=4),
RearPort(device=devices[3], name='Rear Port 5', type=PortTypeChoices.TYPE_FC, positions=5),
RearPort(device=devices[3], name='Rear Port 6', type=PortTypeChoices.TYPE_FC, positions=6),
@ -2176,7 +2213,6 @@ class DeviceBayTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -2249,7 +2285,6 @@ class InventoryItemTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturers = (
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
@ -2257,6 +2292,29 @@ class InventoryItemTestCase(TestCase):
)
Manufacturer.objects.bulk_create(manufacturers)
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(roles)
types = (
InventoryItemType(
model='Inventory Item Type 1', manufacturer=manufacturers[0], part_number='101',
slug='inventory-item-type=1'
),
InventoryItemType(
model='Inventory Item Type 2', manufacturer=manufacturers[1], part_number='102',
slug='inventory-item-type=2'
),
InventoryItemType(
model='Inventory Item Type 3', manufacturer=manufacturers[2], part_number='103',
slug='inventory-item-type=3'
)
)
InventoryItemType.objects.bulk_create(types)
device_type = DeviceType.objects.create(manufacturer=manufacturers[0], model='Model 1', slug='model-1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
@ -2283,9 +2341,18 @@ class InventoryItemTestCase(TestCase):
Device.objects.bulk_create(devices)
inventory_items = (
InventoryItem(device=devices[0], manufacturer=manufacturers[0], name='Inventory Item 1', part_id='1001', serial='ABC', asset_tag='1001', discovered=True, description='First'),
InventoryItem(device=devices[1], manufacturer=manufacturers[1], name='Inventory Item 2', part_id='1002', serial='DEF', asset_tag='1002', discovered=True, description='Second'),
InventoryItem(device=devices[2], manufacturer=manufacturers[2], name='Inventory Item 3', part_id='1003', serial='GHI', asset_tag='1003', discovered=False, description='Third'),
InventoryItem(
device=devices[0], site=sites[0], type=types[0], role=roles[0], name='Inventory Item 1', part_id='1001',
serial='ABC', asset_tag='1001', discovered=True, description='First'
),
InventoryItem(
device=devices[1], site=sites[1], type=types[1], role=roles[1], name='Inventory Item 2', part_id='1002',
serial='DEF', asset_tag='1002', discovered=True, description='Second'
),
InventoryItem(
device=devices[2], site=sites[2], type=types[2], role=roles[2], name='Inventory Item 3', part_id='1003',
serial='GHI', asset_tag='1003', discovered=False, description='Third'
),
)
InventoryItem.objects.bulk_create(inventory_items)
@ -2347,11 +2414,18 @@ class InventoryItemTestCase(TestCase):
params = {'parent_id': [parent_items[0].pk, parent_items[1].pk]}
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]}
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 = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
params = {'role': [roles[0].slug, roles[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_type(self):
types = InventoryItemType.objects.all()[:2]
params = {'type_id': [types[0].pk, types[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'type': [types[0].slug, types[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_serial(self):
@ -2367,7 +2441,6 @@ class VirtualChassisTestCase(TestCase):
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Model 1', slug='model-1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
@ -2438,7 +2511,6 @@ class CableTestCase(TestCase):
@classmethod
def setUpTestData(cls):
sites = (
Site(name='Site 1', slug='site-1'),
Site(name='Site 2', slug='site-2'),
@ -2464,12 +2536,18 @@ class CableTestCase(TestCase):
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
devices = (
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=1, tenant=tenants[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0], position=2, tenant=tenants[0]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=1, tenant=tenants[1]),
Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1], position=2),
Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=1),
Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2], position=2),
Device(name='Device 1', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0],
position=1, tenant=tenants[0]),
Device(name='Device 2', device_type=device_type, device_role=device_role, site=sites[0], rack=racks[0],
position=2, tenant=tenants[0]),
Device(name='Device 3', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1],
position=1, tenant=tenants[1]),
Device(name='Device 4', device_type=device_type, device_role=device_role, site=sites[1], rack=racks[1],
position=2),
Device(name='Device 5', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2],
position=1),
Device(name='Device 6', device_type=device_type, device_role=device_role, site=sites[2], rack=racks[2],
position=2),
)
Device.objects.bulk_create(devices)
@ -2490,12 +2568,24 @@ class CableTestCase(TestCase):
Interface.objects.bulk_create(interfaces)
# Cables
Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2', type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_CONNECTED, color='f44336', length=30, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4', type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40, length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
Cable(termination_a=interfaces[1], termination_b=interfaces[2], label='Cable 1',
type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=10,
length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[3], termination_b=interfaces[4], label='Cable 2',
type=CableTypeChoices.TYPE_CAT3, status=CableStatusChoices.STATUS_CONNECTED, color='aa1409', length=20,
length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[5], termination_b=interfaces[6], label='Cable 3',
type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_CONNECTED, color='f44336', length=30,
length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[7], termination_b=interfaces[8], label='Cable 4',
type=CableTypeChoices.TYPE_CAT5E, status=CableStatusChoices.STATUS_PLANNED, color='f44336', length=40,
length_unit=CableLengthUnitChoices.UNIT_FOOT).save()
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5',
type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10,
length_unit=CableLengthUnitChoices.UNIT_METER).save()
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6',
type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20,
length_unit=CableLengthUnitChoices.UNIT_METER).save()
def test_id(self):
id_list = self.queryset.values_list('id', flat=True)[:2]
@ -2563,7 +2653,6 @@ class PowerPanelTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -2623,7 +2712,6 @@ class PowerFeedTestCase(TestCase):
@classmethod
def setUpTestData(cls):
regions = (
Region(name='Region 1', slug='region-1'),
Region(name='Region 2', slug='region-2'),
@ -2654,9 +2742,18 @@ class PowerFeedTestCase(TestCase):
PowerPanel.objects.bulk_create(power_panels)
power_feeds = (
PowerFeed(power_panel=power_panels[0], rack=racks[0], name='Power Feed 1', status=PowerFeedStatusChoices.STATUS_ACTIVE, type=PowerFeedTypeChoices.TYPE_PRIMARY, supply=PowerFeedSupplyChoices.SUPPLY_AC, phase=PowerFeedPhaseChoices.PHASE_3PHASE, voltage=100, amperage=100, max_utilization=10),
PowerFeed(power_panel=power_panels[1], rack=racks[1], name='Power Feed 2', status=PowerFeedStatusChoices.STATUS_FAILED, type=PowerFeedTypeChoices.TYPE_PRIMARY, supply=PowerFeedSupplyChoices.SUPPLY_AC, phase=PowerFeedPhaseChoices.PHASE_3PHASE, voltage=200, amperage=200, max_utilization=20),
PowerFeed(power_panel=power_panels[2], rack=racks[2], name='Power Feed 3', status=PowerFeedStatusChoices.STATUS_OFFLINE, type=PowerFeedTypeChoices.TYPE_REDUNDANT, supply=PowerFeedSupplyChoices.SUPPLY_DC, phase=PowerFeedPhaseChoices.PHASE_SINGLE, voltage=300, amperage=300, max_utilization=30),
PowerFeed(power_panel=power_panels[0], rack=racks[0], name='Power Feed 1',
status=PowerFeedStatusChoices.STATUS_ACTIVE, type=PowerFeedTypeChoices.TYPE_PRIMARY,
supply=PowerFeedSupplyChoices.SUPPLY_AC, phase=PowerFeedPhaseChoices.PHASE_3PHASE, voltage=100,
amperage=100, max_utilization=10),
PowerFeed(power_panel=power_panels[1], rack=racks[1], name='Power Feed 2',
status=PowerFeedStatusChoices.STATUS_FAILED, type=PowerFeedTypeChoices.TYPE_PRIMARY,
supply=PowerFeedSupplyChoices.SUPPLY_AC, phase=PowerFeedPhaseChoices.PHASE_3PHASE, voltage=200,
amperage=200, max_utilization=20),
PowerFeed(power_panel=power_panels[2], rack=racks[2], name='Power Feed 3',
status=PowerFeedStatusChoices.STATUS_OFFLINE, type=PowerFeedTypeChoices.TYPE_REDUNDANT,
supply=PowerFeedSupplyChoices.SUPPLY_DC, phase=PowerFeedPhaseChoices.PHASE_SINGLE, voltage=300,
amperage=300, max_utilization=30),
)
PowerFeed.objects.bulk_create(power_feeds)
@ -2718,4 +2815,34 @@ class PowerFeedTestCase(TestCase):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class InventoryItemRoleTestCase(TestCase):
queryset = InventoryItemRole.objects.all()
filterset = InventoryItemRoleFilterSet
@classmethod
def setUpTestData(cls):
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)
def test_id(self):
id_list = self.queryset.values_list('id', flat=True)[:2]
params = {'id': [str(id) for id in id_list]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_name(self):
params = {'name': ['Inventory Item Role 1', 'Inventory Item Role 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_slug(self):
params = {'slug': ['inventory-item-role-1', 'inventory-item-role-2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_slug1(self):
params = {'slug': ['inventory-item-role-1', 'test-role']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
# TODO: Connection filters

View File

@ -1384,7 +1384,6 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
cls.form_data = {
'device': device.pk,
'manufacturer': manufacturer.pk,
'name': 'Inventory Item X',
'parent': None,
'discovered': False,
@ -1398,7 +1397,6 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
cls.bulk_create_data = {
'device': device.pk,
'name_pattern': 'Inventory Item [4-6]',
'manufacturer': manufacturer.pk,
'parent': None,
'discovered': False,
'part_id': '123456',
@ -1409,7 +1407,6 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
cls.bulk_edit_data = {
'device': device.pk,
'manufacturer': manufacturer.pk,
'part_id': '123456',
'description': 'New description',
}

View File

@ -6,7 +6,7 @@ from . import views
from .models import (
Cable, ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, FrontPort, Interface, Manufacturer, Platform,
PowerFeed, PowerPanel, PowerPort, PowerOutlet, Rack, RackGroup, RackReservation, RackRole, RearPort, Region, Site,
VirtualChassis,
VirtualChassis, InventoryItemRole, InventoryItemType,
)
app_name = 'dcim'
@ -303,6 +303,30 @@ urlpatterns = [
path('inventory-items/<int:pk>/edit/', views.InventoryItemEditView.as_view(), name='inventoryitem_edit'),
path('inventory-items/<int:pk>/delete/', views.InventoryItemDeleteView.as_view(), name='inventoryitem_delete'),
# Inventory item roles
path('inventory-item-roles/', views.InventoryItemRoleListView.as_view(), name='inventoryitemrole_list'),
path('inventory-item-roles/import/', views.InventoryItemRoleBulkImportView.as_view(),
name='inventoryitemrole_import'),
path('inventory-item-roles/add/', views.InventoryItemRoleCreateView.as_view(), name='inventoryitemrole_add'),
path('inventory-item-roles/delete/', views.InventoryItemRoleBulkDeleteView.as_view(),
name='inventoryitemrole_bulk_delete'),
path('inventory-item-roles/<slug:slug>/edit/', views.InventoryItemRoleEditView.as_view(),
name='inventoryitemrole_edit'),
path('inventory-item-roles/<slug:slug>/changelog/', ObjectChangeLogView.as_view(),
name='inventoryitemrole_changelog',
kwargs={'model': InventoryItemRole}),
# Inventory Item types
path('inventory-item-types/', views.InventoryItemTypeListView.as_view(), name='inventoryitemtype_list'),
path('inventory-item-types/add/', views.InventoryItemTypeCreateView.as_view(), name='inventoryitemtype_add'),
path('inventory-item-types/import/', views.InventoryItemTypeImportView.as_view(), name='inventoryitemtype_import'),
path('inventory-item-types/edit/', views.InventoryItemTypeBulkEditView.as_view(), name='inventoryitemtype_bulk_edit'),
path('inventory-item-types/delete/', views.InventoryItemTypeBulkDeleteView.as_view(), name='inventoryitemtype_bulk_delete'),
path('inventory-item-types/<int:pk>/', views.InventoryItemTypeView.as_view(), name='inventoryitemtype'),
path('inventory-item-types/<int:pk>/edit/', views.InventoryItemTypeEditView.as_view(), name='inventoryitemtype_edit'),
path('inventory-item-types/<int:pk>/delete/', views.InventoryItemTypeDeleteView.as_view(), name='inventoryitemtype_delete'),
path('inventory-item-types/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='inventoryitemtype_changelog', kwargs={'model': InventoryItemType}),
# Cables
path('cables/', views.CableListView.as_view(), name='cable_list'),
path('cables/import/', views.CableBulkImportView.as_view(), name='cable_import'),

View File

@ -35,7 +35,7 @@ from .constants import NONCONNECTABLE_IFACE_TYPES
from .models import (
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
InventoryItem, InventoryItemRole, InventoryItemType, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
VirtualChassis,
)
@ -552,7 +552,7 @@ class ManufacturerListView(PermissionRequiredMixin, ObjectListView):
permission_required = 'dcim.view_manufacturer'
queryset = Manufacturer.objects.annotate(
devicetype_count=Count('device_types', distinct=True),
inventoryitem_count=Count('inventory_items', distinct=True),
inventoryitem_count=Count('inventory_item_types__instances', distinct=True),
platform_count=Count('platforms', distinct=True),
)
table = tables.ManufacturerTable
@ -1007,11 +1007,11 @@ class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
queryset = DeviceBayTemplate.objects.all()
table = tables.DeviceBayTemplateTable
#
# Device roles
#
class DeviceRoleListView(PermissionRequiredMixin, ObjectListView):
permission_required = 'dcim.view_devicerole'
queryset = DeviceRole.objects.all()
@ -1180,7 +1180,7 @@ class DeviceInventoryView(PermissionRequiredMixin, View):
inventory_items = InventoryItem.objects.filter(
device=device, parent=None
).prefetch_related(
'manufacturer', 'child_items'
'type__manufacturer', 'child_items'
)
return render(request, 'dcim/device_inventory.html', {
@ -2264,14 +2264,50 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
return '\n'.join(csv_data)
#
# Inventory item roles
#
class InventoryItemRoleListView(PermissionRequiredMixin, ObjectListView):
permission_required = 'dcim.view_inventoryitemrole'
queryset = InventoryItemRole.objects.all()
table = tables.InventoryItemRoleTable
class InventoryItemRoleCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.add_inventoryitemrole'
model = InventoryItemRole
model_form = forms.InventoryItemRoleForm
default_return_url = 'dcim:inventoryitemrole_list'
class InventoryItemRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
permission_required = 'dcim.add_inventoryitemrole'
model_form = forms.InventoryItemRoleCSVForm
table = tables.InventoryItemRoleTable
default_return_url = 'dcim:inventoryitemrole_list'
class InventoryItemRoleEditView(InventoryItemRoleCreateView):
permission_required = 'dcim.change_inventoryitemrole'
class InventoryItemRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_inventoryitemrole'
queryset = InventoryItemRole.objects.all()
filterset = filters.InventoryItemRoleFilterSet
table = tables.InventoryItemRoleTable
default_return_url = 'dcim:inventoryitemrole_list'
#
# Inventory items
#
class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
permission_required = 'dcim.view_inventoryitem'
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
queryset = InventoryItem.objects.prefetch_related('device', 'role', 'type__manufacturer')
filterset = filters.InventoryItemFilterSet
filterset_form = forms.InventoryItemFilterForm
table = tables.InventoryItemTable
@ -2306,7 +2342,7 @@ class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView):
class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_inventoryitem'
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
queryset = InventoryItem.objects.prefetch_related('device', 'type__manufacturer')
filterset = filters.InventoryItemFilterSet
table = tables.InventoryItemTable
form = forms.InventoryItemBulkEditForm
@ -2315,11 +2351,78 @@ class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_inventoryitem'
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
queryset = InventoryItem.objects.prefetch_related('device', 'type__manufacturer')
table = tables.InventoryItemTable
template_name = 'dcim/inventoryitem_bulk_delete.html'
default_return_url = 'dcim:inventoryitem_list'
#
# Inventory Item types
#
class InventoryItemTypeListView(PermissionRequiredMixin, ObjectListView):
permission_required = 'dcim.view_inventoryitemtype'
queryset = InventoryItemType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
filterset = filters.InventoryItemTypeFilterSet
filterset_form = forms.InventoryItemTypeFilterForm
table = tables.InventoryItemTypeTable
class InventoryItemTypeView(PermissionRequiredMixin, View):
permission_required = 'dcim.view_inventoryitemtype'
def get(self, request, pk):
inventoryitemtype = get_object_or_404(InventoryItemType, pk=pk)
return render(request, 'dcim/inventoryitemtype.html', {
'inventoryitemtype': inventoryitemtype,
})
class InventoryItemTypeCreateView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.add_inventoryitemtype'
model = InventoryItemType
model_form = forms.InventoryItemTypeForm
template_name = 'dcim/inventoryitemtype_edit.html'
default_return_url = 'dcim:inventoryitemtype_list'
class InventoryItemTypeEditView(InventoryItemTypeCreateView):
permission_required = 'dcim.change_inventoryitemtype'
class InventoryItemTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'dcim.delete_inventoryitemtype'
model = InventoryItemType
default_return_url = 'dcim:inventoryitemtype_list'
class InventoryItemTypeImportView(PermissionRequiredMixin, ObjectImportView):
permission_required = [
'dcim.add_inventoryitemtype',
]
model = InventoryItemType
model_form = forms.InventoryItemTypeImportForm
default_return_url = 'dcim:inventoryitemtype_import'
class InventoryItemTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
permission_required = 'dcim.change_inventoryitemtype'
queryset = InventoryItemType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
filterset = filters.InventoryItemTypeFilterSet
table = tables.InventoryItemTypeTable
form = forms.InventoryItemTypeBulkEditForm
default_return_url = 'dcim:inventoryitemtype_list'
class InventoryItemTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
permission_required = 'dcim.delete_inventoryitemtype'
queryset = InventoryItemType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
filterset = filters.InventoryItemTypeFilterSet
table = tables.InventoryItemTypeTable
default_return_url = 'dcim:inventoryitemtype_list'
#
# Virtual chassis

View File

@ -1,7 +1,7 @@
<tr>
<td style="padding-left: {{ indent|add:5 }}px">{{ item }}</td>
<td>{% if not item.discovered %}<i class="fa fa-asterisk" title="Manually created"></i>{% endif %}</td>
<td>{{ item.manufacturer|default:"" }}</td>
<td>{{ item.type.manufacturer|default:"" }}</td>
<td>{{ item.part_id }}</td>
<td>{{ item.serial }}</td>
<td>{{ item.asset_tag|default:"" }}</td>

View File

@ -0,0 +1,89 @@
{% extends '_base.html' %}
{% load buttons %}
{% load custom_links %}
{% load helpers %}
{% block title %}{{ inventoryitemtype.manufacturer }} {{ inventoryitemtype.model }}{% endblock %}
{% block header %}
<div class="row noprint">
<div class="col-md-12">
<ol class="breadcrumb">
<li><a href="{% url 'dcim:inventoryitemtype_list' %}">Inventory Item Types</a></li>
<li><a href="{% url 'dcim:inventoryitemtype_list' %}?manufacturer={{ inventoryitemtype.manufacturer.slug }}">{{ inventoryitemtype.manufacturer }}</a></li>
<li>{{ inventoryitemtype.model }}</li>
</ol>
</div>
</div>
<div class="pull-right noprint">
{% if perms.dcim.add_inventoryitemtype %}
{% clone_button inventoryitemtype %}
{% endif %}
{% if perms.dcim.change_inventoryitemtype %}
{% edit_button inventoryitemtype use_pk=True %}
{% endif %}
{% if perms.dcim.delete_inventoryitemtype %}
{% delete_button inventoryitemtype use_pk=True %}
{% endif %}
</div>
<h1>{{ inventoryitemtype.manufacturer }} {{ inventoryitemtype.model }}</h1>
{% include 'inc/created_updated.html' with obj=inventoryitemtype %}
<div class="pull-right noprint">
{% custom_links inventoryitemtype %}
</div>
<ul class="nav nav-tabs">
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
<a href="{{ inventoryitemtype.get_absolute_url }}">Inventory Item Type</a>
</li>
{% if perms.extras.view_objectchange %}
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
<a href="{% url 'dcim:inventoryitemtype_changelog' pk=inventoryitemtype.pk %}">Change Log</a>
</li>
{% endif %}
</ul>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Chassis</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Manufacturer</td>
<td><a href="{% url 'dcim:inventoryitemtype_list' %}?manufacturer={{ inventoryitemtype.manufacturer.slug }}">{{ inventoryitemtype.manufacturer }}</a></td>
</tr>
<tr>
<td>Model Name</td>
<td>
{{ inventoryitemtype.model }}<br/>
<small class="text-muted">{{ inventoryitemtype.slug }}</small>
</td>
</tr>
<tr>
<td>Part Number</td>
<td>{{ inventoryitemtype.part_number|placeholder }}</td>
</tr>
</table>
</div>
</div>
<div class="col-md-6">
{% include 'inc/custom_fields_panel.html' with obj=inventoryitemtype %}
{% include 'extras/inc/tags_panel.html' with tags=inventoryitemtype.tags.all url='dcim:inventoryitemtype_list' %}
<div class="panel panel-default">
<div class="panel-heading">
<strong>Comments</strong>
</div>
<div class="panel-body rendered-markdown">
{% if inventoryitemtype.comments %}
{{ inventoryitemtype.comments|gfm }}
{% else %}
<span class="text-muted">None</span>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends 'utilities/obj_edit.html' %}
{% load form_helpers %}
{% block form %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Inventory Item Type</strong></div>
<div class="panel-body">
{% render_field form.manufacturer %}
{% render_field form.model %}
{% render_field form.slug %}
{% render_field form.part_number %}
</div>
</div>
{% if form.custom_fields %}
<div class="panel panel-default">
<div class="panel-heading"><strong>Custom Fields</strong></div>
<div class="panel-body">
{% render_custom_fields form %}
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -169,6 +169,25 @@
{% endif %}
<a href="{% url 'dcim:inventoryitem_list' %}">Inventory Items</a>
</li>
<li{% if not perms.dcim.view_inventoryitemrole %} class="disabled"{% endif %}>
{% if perms.dcim.add_inventoryitemrole %}
<div class="buttons pull-right">
<a href="{% url 'dcim:inventoryitemrole_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
<a href="{% url 'dcim:inventoryitemrole_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
</div>
{% endif %}
<a href="{% url 'dcim:inventoryitemrole_list' %}">Inventory Item Roles</a>
</li>
</li>
<li{% if not perms.dcim.view_inventoryitemtype %} class="disabled"{% endif %}>
{% if perms.dcim.add_inventoryitemtype %}
<div class="buttons pull-right">
<a href="{% url 'dcim:inventoryitemtype_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
<a href="{% url 'dcim:inventoryitemtype_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
</div>
{% endif %}
<a href="{% url 'dcim:inventoryitemtype_list' %}">Inventory Item Types</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Connections</li>
<li{% if not perms.dcim.view_cable %} class="disabled"{% endif %}>

View File

@ -27,7 +27,7 @@ fi
# - E501: line greater than 80 characters in length
pycodestyle \
--ignore=W504,E501 \
netbox/
netbox
RC=$?
if [[ $RC != 0 ]]; then
echo -e "\n$(info) one or more PEP 8 errors detected, failing build."