mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
Closes #1744: Allow associating a platform with a specific manufacturer
This commit is contained in:
parent
02e01b7386
commit
9984238f2a
@ -426,11 +426,12 @@ class NestedDeviceRoleSerializer(serializers.ModelSerializer):
|
|||||||
# Platforms
|
# Platforms
|
||||||
#
|
#
|
||||||
|
|
||||||
class PlatformSerializer(ValidatedModelSerializer):
|
class PlatformSerializer(serializers.ModelSerializer):
|
||||||
|
manufacturer = NestedManufacturerSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['id', 'name', 'slug', 'napalm_driver', 'rpc_client']
|
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
|
||||||
|
|
||||||
|
|
||||||
class NestedPlatformSerializer(serializers.ModelSerializer):
|
class NestedPlatformSerializer(serializers.ModelSerializer):
|
||||||
@ -441,6 +442,13 @@ class NestedPlatformSerializer(serializers.ModelSerializer):
|
|||||||
fields = ['id', 'url', 'name', 'slug']
|
fields = ['id', 'url', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
|
class WritablePlatformSerializer(ValidatedModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Platform
|
||||||
|
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Devices
|
# Devices
|
||||||
#
|
#
|
||||||
|
@ -225,6 +225,7 @@ class DeviceRoleViewSet(ModelViewSet):
|
|||||||
class PlatformViewSet(ModelViewSet):
|
class PlatformViewSet(ModelViewSet):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
serializer_class = serializers.PlatformSerializer
|
serializer_class = serializers.PlatformSerializer
|
||||||
|
write_serializer_class = serializers.WritablePlatformSerializer
|
||||||
filter_class = filters.PlatformFilter
|
filter_class = filters.PlatformFilter
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,6 +344,17 @@ class DeviceRoleFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class PlatformFilter(django_filters.FilterSet):
|
class PlatformFilter(django_filters.FilterSet):
|
||||||
|
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='manufacturer',
|
||||||
|
queryset=Manufacturer.objects.all(),
|
||||||
|
label='Manufacturer (ID)',
|
||||||
|
)
|
||||||
|
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
name='manufacturer__slug',
|
||||||
|
queryset=Manufacturer.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Manufacturer (slug)',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
|
@ -677,7 +677,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['name', 'slug', 'napalm_driver', 'rpc_client']
|
fields = ['name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']
|
||||||
|
|
||||||
|
|
||||||
class PlatformCSVForm(forms.ModelForm):
|
class PlatformCSVForm(forms.ModelForm):
|
||||||
@ -685,9 +685,10 @@ class PlatformCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['name', 'slug', 'napalm_driver']
|
fields = ['name', 'slug', 'manufacturer', 'napalm_driver']
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'name': 'Platform name',
|
'name': 'Platform name',
|
||||||
|
'manufacturer': 'Manufacturer name',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -797,6 +798,11 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
# can be flipped from one face to another.
|
# can be flipped from one face to another.
|
||||||
self.fields['position'].widget.attrs['api-url'] += '&exclude={}'.format(self.instance.pk)
|
self.fields['position'].widget.attrs['api-url'] += '&exclude={}'.format(self.instance.pk)
|
||||||
|
|
||||||
|
# Limit platform by manufacturer
|
||||||
|
self.fields['platform'].queryset = Platform.objects.filter(
|
||||||
|
Q(manufacturer__isnull=True) | Q(manufacturer=self.instance.device_type.manufacturer)
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# An object that doesn't exist yet can't have any IPs assigned to it
|
# An object that doesn't exist yet can't have any IPs assigned to it
|
||||||
|
26
netbox/dcim/migrations/0053_platform_manufacturer.py
Normal file
26
netbox/dcim/migrations/0053_platform_manufacturer.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2017-12-19 20:56
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0052_virtual_chassis'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platform',
|
||||||
|
name='manufacturer',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Optionally limit this platform to devices of a certain manufacturer', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='platforms', to='dcim.Manufacturer'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='platform',
|
||||||
|
name='napalm_driver',
|
||||||
|
field=models.CharField(blank=True, help_text='The name of the NAPALM driver to use when interacting with devices', max_length=50, verbose_name='NAPALM driver'),
|
||||||
|
),
|
||||||
|
]
|
@ -768,16 +768,31 @@ class DeviceRole(models.Model):
|
|||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Platform(models.Model):
|
class Platform(models.Model):
|
||||||
"""
|
"""
|
||||||
Platform refers to the software or firmware running on a Device; for example, "Cisco IOS-XR" or "Juniper Junos".
|
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
||||||
NetBox uses Platforms to determine how to interact with devices when pulling inventory data or other information by
|
NetBox uses Platforms to determine how to interact with devices when pulling inventory data or other information by
|
||||||
specifying an remote procedure call (RPC) client.
|
specifying a NAPALM driver.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
napalm_driver = models.CharField(max_length=50, blank=True, verbose_name='NAPALM driver',
|
manufacturer = models.ForeignKey(
|
||||||
help_text="The name of the NAPALM driver to use when interacting with devices.")
|
to='Manufacturer',
|
||||||
rpc_client = models.CharField(max_length=30, choices=RPC_CLIENT_CHOICES, blank=True,
|
related_name='platforms',
|
||||||
verbose_name='Legacy RPC client')
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text="Optionally limit this platform to devices of a certain manufacturer"
|
||||||
|
)
|
||||||
|
napalm_driver = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
blank=True,
|
||||||
|
verbose_name='NAPALM driver',
|
||||||
|
help_text="The name of the NAPALM driver to use when interacting with devices"
|
||||||
|
)
|
||||||
|
rpc_client = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=RPC_CLIENT_CHOICES,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Legacy RPC client"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -946,6 +961,14 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
self.primary_ip6),
|
self.primary_ip6),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Validate manufacturer/platform
|
||||||
|
if self.device_type and self.platform:
|
||||||
|
if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:
|
||||||
|
raise ValidationError({
|
||||||
|
'platform': "The assigned platform is limited to {} device types, but this device's type belongs "
|
||||||
|
"to {}.".format(self.platform.manufacturer, self.device_type.manufacturer)
|
||||||
|
})
|
||||||
|
|
||||||
# A Device can only be assigned to a Cluster in the same Site (or no Site)
|
# A Device can only be assigned to a Cluster in the same Site (or no Site)
|
||||||
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
|
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
|
@ -271,13 +271,14 @@ class ManufacturerTable(BaseTable):
|
|||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
name = tables.LinkColumn(verbose_name='Name')
|
name = tables.LinkColumn(verbose_name='Name')
|
||||||
devicetype_count = tables.Column(verbose_name='Device Types')
|
devicetype_count = tables.Column(verbose_name='Device Types')
|
||||||
|
platform_count = tables.Column(verbose_name='Platforms')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||||
verbose_name='')
|
verbose_name='')
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fields = ('pk', 'name', 'devicetype_count', 'slug', 'actions')
|
fields = ('pk', 'name', 'devicetype_count', 'platform_count', 'slug', 'actions')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -389,12 +390,15 @@ class PlatformTable(BaseTable):
|
|||||||
name = tables.LinkColumn(verbose_name='Name')
|
name = tables.LinkColumn(verbose_name='Name')
|
||||||
device_count = tables.Column(verbose_name='Devices')
|
device_count = tables.Column(verbose_name='Devices')
|
||||||
slug = tables.Column(verbose_name='Slug')
|
slug = tables.Column(verbose_name='Slug')
|
||||||
actions = tables.TemplateColumn(template_code=PLATFORM_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
actions = tables.TemplateColumn(
|
||||||
verbose_name='')
|
template_code=PLATFORM_ACTIONS,
|
||||||
|
attrs={'td': {'class': 'text-right'}},
|
||||||
|
verbose_name=''
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ('pk', 'name', 'device_count', 'slug', 'napalm_driver', 'actions')
|
fields = ('pk', 'name', 'manufacturer', 'device_count', 'slug', 'napalm_driver', 'actions')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -453,7 +453,10 @@ class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ManufacturerListView(ObjectListView):
|
class ManufacturerListView(ObjectListView):
|
||||||
queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
|
queryset = Manufacturer.objects.annotate(
|
||||||
|
devicetype_count=Count('device_types', distinct=True),
|
||||||
|
platform_count=Count('platforms', distinct=True),
|
||||||
|
)
|
||||||
table = tables.ManufacturerTable
|
table = tables.ManufacturerTable
|
||||||
template_name = 'dcim/manufacturer_list.html'
|
template_name = 'dcim/manufacturer_list.html'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user