Skip to content

Commit

Permalink
Closes #1744: Allow associating a platform with a specific manufacturer
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Dec 19, 2017
1 parent 02e01b7 commit 9984238
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 15 deletions.
12 changes: 10 additions & 2 deletions netbox/dcim/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,11 +426,12 @@ class Meta:
# Platforms
#

class PlatformSerializer(ValidatedModelSerializer):
class PlatformSerializer(serializers.ModelSerializer):
manufacturer = NestedManufacturerSerializer()

class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'napalm_driver', 'rpc_client']
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']


class NestedPlatformSerializer(serializers.ModelSerializer):
Expand All @@ -441,6 +442,13 @@ class Meta:
fields = ['id', 'url', 'name', 'slug']


class WritablePlatformSerializer(ValidatedModelSerializer):

class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']


#
# Devices
#
Expand Down
1 change: 1 addition & 0 deletions netbox/dcim/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ class DeviceRoleViewSet(ModelViewSet):
class PlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
serializer_class = serializers.PlatformSerializer
write_serializer_class = serializers.WritablePlatformSerializer
filter_class = filters.PlatformFilter


Expand Down
11 changes: 11 additions & 0 deletions netbox/dcim/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,17 @@ class Meta:


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:
model = Platform
Expand Down
10 changes: 8 additions & 2 deletions netbox/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,17 +677,18 @@ class PlatformForm(BootstrapMixin, forms.ModelForm):

class Meta:
model = Platform
fields = ['name', 'slug', 'napalm_driver', 'rpc_client']
fields = ['name', 'slug', 'manufacturer', 'napalm_driver', 'rpc_client']


class PlatformCSVForm(forms.ModelForm):
slug = SlugField()

class Meta:
model = Platform
fields = ['name', 'slug', 'napalm_driver']
fields = ['name', 'slug', 'manufacturer', 'napalm_driver']
help_texts = {
'name': 'Platform name',
'manufacturer': 'Manufacturer name',
}


Expand Down Expand Up @@ -797,6 +798,11 @@ def __init__(self, *args, **kwargs):
# can be flipped from one face to another.
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:

# An object that doesn't exist yet can't have any IPs assigned to it
Expand Down
26 changes: 26 additions & 0 deletions netbox/dcim/migrations/0053_platform_manufacturer.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
35 changes: 29 additions & 6 deletions netbox/dcim/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,16 +768,31 @@ def get_absolute_url(self):
@python_2_unicode_compatible
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
specifying an remote procedure call (RPC) client.
specifying a NAPALM driver.
"""
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(unique=True)
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')
manufacturer = models.ForeignKey(
to='Manufacturer',
related_name='platforms',
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:
ordering = ['name']
Expand Down Expand Up @@ -946,6 +961,14 @@ def clean(self):
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)
if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
raise ValidationError({
Expand Down
12 changes: 8 additions & 4 deletions netbox/dcim/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,14 @@ class ManufacturerTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name')
devicetype_count = tables.Column(verbose_name='Device Types')
platform_count = tables.Column(verbose_name='Platforms')
slug = tables.Column(verbose_name='Slug')
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right'}},
verbose_name='')

class Meta(BaseTable.Meta):
model = Manufacturer
fields = ('pk', 'name', 'devicetype_count', 'slug', 'actions')
fields = ('pk', 'name', 'devicetype_count', 'platform_count', 'slug', 'actions')


#
Expand Down Expand Up @@ -389,12 +390,15 @@ class PlatformTable(BaseTable):
name = tables.LinkColumn(verbose_name='Name')
device_count = tables.Column(verbose_name='Devices')
slug = tables.Column(verbose_name='Slug')
actions = tables.TemplateColumn(template_code=PLATFORM_ACTIONS, attrs={'td': {'class': 'text-right'}},
verbose_name='')
actions = tables.TemplateColumn(
template_code=PLATFORM_ACTIONS,
attrs={'td': {'class': 'text-right'}},
verbose_name=''
)

class Meta(BaseTable.Meta):
model = Platform
fields = ('pk', 'name', 'device_count', 'slug', 'napalm_driver', 'actions')
fields = ('pk', 'name', 'manufacturer', 'device_count', 'slug', 'napalm_driver', 'actions')


#
Expand Down
5 changes: 4 additions & 1 deletion netbox/dcim/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,10 @@ class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
#

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
template_name = 'dcim/manufacturer_list.html'

Expand Down

0 comments on commit 9984238

Please sign in to comment.