Просмотр исходного кода

Closes #4833: Allow assigning config contexts by device type

Jeremy Stretch 4 лет назад
Родитель
Сommit
cd629fc737

+ 4 - 2
docs/models/extras/configcontext.md

@@ -3,11 +3,13 @@
 Sometimes it is desirable to associate additional data with a group of devices or virtual machines to aid in automated configuration. For example, you might want to associate a set of syslog servers for all devices within a particular region. Context data enables the association of extra user-defined data with devices and virtual machines grouped by one or more of the following assignments:
 Sometimes it is desirable to associate additional data with a group of devices or virtual machines to aid in automated configuration. For example, you might want to associate a set of syslog servers for all devices within a particular region. Context data enables the association of extra user-defined data with devices and virtual machines grouped by one or more of the following assignments:
 
 
 * Region
 * Region
+* Site group
 * Site
 * Site
+* Device type (devices only)
 * Role
 * Role
 * Platform
 * Platform
-* Cluster group
-* Cluster
+* Cluster group (VMs only)
+* Cluster (VMs only)
 * Tenant group
 * Tenant group
 * Tenant
 * Tenant
 * Tag
 * Tag

+ 1 - 0
docs/release-notes/version-2.11.md

@@ -84,6 +84,7 @@ A new Cloud model has been introduced to represent the boundary of a network tha
 
 
 ### Enhancements
 ### Enhancements
 
 
+* [#4833](https://github.com/netbox-community/netbox/issues/4833) - Allow assigning config contexts by device type
 * [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models
 * [#5370](https://github.com/netbox-community/netbox/issues/5370) - Extend custom field support to organizational models
 * [#5375](https://github.com/netbox-community/netbox/issues/5375) - Add `speed` attribute to console port models
 * [#5375](https://github.com/netbox-community/netbox/issues/5375) - Add `speed` attribute to console port models
 * [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models
 * [#5401](https://github.com/netbox-community/netbox/issues/5401) - Extend custom field support to device component models

+ 11 - 5
netbox/extras/api/serializers.py

@@ -4,10 +4,10 @@ from drf_yasg.utils import swagger_serializer_method
 from rest_framework import serializers
 from rest_framework import serializers
 
 
 from dcim.api.nested_serializers import (
 from dcim.api.nested_serializers import (
-    NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedRackSerializer,
-    NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
+    NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer,
+    NestedRackSerializer, NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
 )
 )
-from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
+from dcim.models import Device, DeviceRole, DeviceType, Platform, Rack, Region, Site, SiteGroup
 from extras.choices import *
 from extras.choices import *
 from extras.models import *
 from extras.models import *
 from extras.utils import FeatureQuery
 from extras.utils import FeatureQuery
@@ -251,6 +251,12 @@ class ConfigContextSerializer(ValidatedModelSerializer):
         required=False,
         required=False,
         many=True
         many=True
     )
     )
+    device_types = SerializedPKRelatedField(
+        queryset=DeviceType.objects.all(),
+        serializer=NestedDeviceTypeSerializer,
+        required=False,
+        many=True
+    )
     roles = SerializedPKRelatedField(
     roles = SerializedPKRelatedField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         serializer=NestedDeviceRoleSerializer,
         serializer=NestedDeviceRoleSerializer,
@@ -298,8 +304,8 @@ class ConfigContextSerializer(ValidatedModelSerializer):
         model = ConfigContext
         model = ConfigContext
         fields = [
         fields = [
             'id', 'url', 'display', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites',
             'id', 'url', 'display', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites',
-            'roles', 'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data', 'created',
-            'last_updated',
+            'device_types', 'roles', 'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
+            'data', 'created', 'last_updated',
         ]
         ]
 
 
 
 

+ 6 - 1
netbox/extras/filters.py

@@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
 from django.db.models import Q
 from django.forms import DateField, IntegerField, NullBooleanField
 from django.forms import DateField, IntegerField, NullBooleanField
 
 
-from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
+from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.filters import BaseFilterSet, ContentTypeFilter
 from utilities.filters import BaseFilterSet, ContentTypeFilter
 from virtualization.models import Cluster, ClusterGroup
 from virtualization.models import Cluster, ClusterGroup
@@ -206,6 +206,11 @@ class ConfigContextFilterSet(BaseFilterSet):
         to_field_name='slug',
         to_field_name='slug',
         label='Site (slug)',
         label='Site (slug)',
     )
     )
+    device_type_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='device_types',
+        queryset=DeviceType.objects.all(),
+        label='Device type',
+    )
     role_id = django_filters.ModelMultipleChoiceFilter(
     role_id = django_filters.ModelMultipleChoiceFilter(
         field_name='roles',
         field_name='roles',
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),

+ 13 - 4
netbox/extras/forms.py

@@ -4,11 +4,11 @@ from django.contrib.contenttypes.models import ContentType
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
 
 
-from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup
+from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
 from utilities.forms import (
 from utilities.forms import (
     add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
     add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
-    CommentField, CSVModelForm, DateTimePicker, DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect2,
+    CSVModelForm, DateTimePicker, DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect2,
     BOOLEAN_WITH_BLANK_CHOICES,
     BOOLEAN_WITH_BLANK_CHOICES,
 )
 )
 from virtualization.models import Cluster, ClusterGroup
 from virtualization.models import Cluster, ClusterGroup
@@ -218,6 +218,10 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),
         required=False
         required=False
     )
     )
+    device_types = DynamicModelMultipleChoiceField(
+        queryset=DeviceType.objects.all(),
+        required=False
+    )
     roles = DynamicModelMultipleChoiceField(
     roles = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         required=False
         required=False
@@ -253,8 +257,8 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
     class Meta:
     class Meta:
         model = ConfigContext
         model = ConfigContext
         fields = (
         fields = (
-            'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites', 'roles', 'platforms',
-            'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data',
+            'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites', 'roles', 'device_types',
+            'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data',
         )
         )
 
 
 
 
@@ -306,6 +310,11 @@ class ConfigContextFilterForm(BootstrapMixin, forms.Form):
         required=False,
         required=False,
         label=_('Sites')
         label=_('Sites')
     )
     )
+    device_type_id = DynamicModelMultipleChoiceField(
+        queryset=DeviceType.objects.all(),
+        required=False,
+        label=_('Device types')
+    )
     role_id = DynamicModelMultipleChoiceField(
     role_id = DynamicModelMultipleChoiceField(
         queryset=DeviceRole.objects.all(),
         queryset=DeviceRole.objects.all(),
         required=False,
         required=False,

+ 5 - 0
netbox/extras/migrations/0056_sitegroup.py → netbox/extras/migrations/0056_extend_configcontext.py

@@ -14,4 +14,9 @@ class Migration(migrations.Migration):
             name='site_groups',
             name='site_groups',
             field=models.ManyToManyField(blank=True, related_name='_extras_configcontext_site_groups_+', to='dcim.SiteGroup'),
             field=models.ManyToManyField(blank=True, related_name='_extras_configcontext_site_groups_+', to='dcim.SiteGroup'),
         ),
         ),
+        migrations.AddField(
+            model_name='configcontext',
+            name='device_types',
+            field=models.ManyToManyField(blank=True, related_name='_extras_configcontext_device_types_+', to='dcim.DeviceType'),
+        ),
     ]
     ]

+ 1 - 1
netbox/extras/migrations/0057_customlink_rename_fields.py

@@ -6,7 +6,7 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 class Migration(migrations.Migration):
 
 
     dependencies = [
     dependencies = [
-        ('extras', '0056_sitegroup'),
+        ('extras', '0056_extend_configcontext'),
     ]
     ]
 
 
     operations = [
     operations = [

+ 5 - 0
netbox/extras/models/configcontexts.py

@@ -56,6 +56,11 @@ class ConfigContext(ChangeLoggedModel):
         related_name='+',
         related_name='+',
         blank=True
         blank=True
     )
     )
+    device_types = models.ManyToManyField(
+        to='dcim.DeviceType',
+        related_name='+',
+        blank=True
+    )
     roles = models.ManyToManyField(
     roles = models.ManyToManyField(
         to='dcim.DeviceRole',
         to='dcim.DeviceRole',
         related_name='+',
         related_name='+',

+ 6 - 1
netbox/extras/querysets.py

@@ -19,7 +19,10 @@ class ConfigContextQuerySet(RestrictedQuerySet):
         # `device_role` for Device; `role` for VirtualMachine
         # `device_role` for Device; `role` for VirtualMachine
         role = getattr(obj, 'device_role', None) or obj.role
         role = getattr(obj, 'device_role', None) or obj.role
 
 
-        # Virtualization cluster for VirtualMachine
+        # Device type assignment is relevant only for Devices
+        device_type = getattr(obj, 'device_type', None)
+
+        # Cluster assignment is relevant only for VirtualMachines
         cluster = getattr(obj, 'cluster', None)
         cluster = getattr(obj, 'cluster', None)
         cluster_group = getattr(cluster, 'group', None)
         cluster_group = getattr(cluster, 'group', None)
 
 
@@ -36,6 +39,7 @@ class ConfigContextQuerySet(RestrictedQuerySet):
         queryset = self.filter(
         queryset = self.filter(
             Q(regions__in=regions) | Q(regions=None),
             Q(regions__in=regions) | Q(regions=None),
             Q(sites=obj.site) | Q(sites=None),
             Q(sites=obj.site) | Q(sites=None),
+            Q(device_types=device_type) | Q(device_types=None),
             Q(roles=role) | Q(roles=None),
             Q(roles=role) | Q(roles=None),
             Q(platforms=obj.platform) | Q(platforms=None),
             Q(platforms=obj.platform) | Q(platforms=None),
             Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
             Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
@@ -108,6 +112,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
         )
         )
 
 
         if self.model._meta.model_name == 'device':
         if self.model._meta.model_name == 'device':
+            base_query.add((Q(device_types=OuterRef('device_type')) | Q(device_types=None)), Q.AND)
             base_query.add((Q(roles=OuterRef('device_role')) | Q(roles=None)), Q.AND)
             base_query.add((Q(roles=OuterRef('device_role')) | Q(roles=None)), Q.AND)
             base_query.add((Q(sites=OuterRef('site')) | Q(sites=None)), Q.AND)
             base_query.add((Q(sites=OuterRef('site')) | Q(sites=None)), Q.AND)
             region_field = 'site__region'
             region_field = 'site__region'

+ 15 - 1
netbox/extras/tests/test_filters.py

@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
 from django.test import TestCase
 from django.test import TestCase
 
 
-from dcim.models import DeviceRole, Platform, Rack, Region, Site, SiteGroup
+from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
 from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
 from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
 from extras.filters import *
 from extras.filters import *
 from extras.models import *
 from extras.models import *
@@ -379,6 +379,14 @@ class ConfigContextTestCase(TestCase):
         )
         )
         Site.objects.bulk_create(sites)
         Site.objects.bulk_create(sites)
 
 
+        manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
+        device_types = (
+            DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
+            DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-3'),
+            DeviceType(manufacturer=manufacturer, model='Device Type 3', slug='device-type-4'),
+        )
+        DeviceType.objects.bulk_create(device_types)
+
         device_roles = (
         device_roles = (
             DeviceRole(name='Device Role 1', slug='device-role-1'),
             DeviceRole(name='Device Role 1', slug='device-role-1'),
             DeviceRole(name='Device Role 2', slug='device-role-2'),
             DeviceRole(name='Device Role 2', slug='device-role-2'),
@@ -433,6 +441,7 @@ class ConfigContextTestCase(TestCase):
             c.regions.set([regions[i]])
             c.regions.set([regions[i]])
             c.site_groups.set([site_groups[i]])
             c.site_groups.set([site_groups[i]])
             c.sites.set([sites[i]])
             c.sites.set([sites[i]])
+            c.roles.set([device_types[i]])
             c.roles.set([device_roles[i]])
             c.roles.set([device_roles[i]])
             c.platforms.set([platforms[i]])
             c.platforms.set([platforms[i]])
             c.cluster_groups.set([cluster_groups[i]])
             c.cluster_groups.set([cluster_groups[i]])
@@ -475,6 +484,11 @@ class ConfigContextTestCase(TestCase):
         params = {'site': [sites[0].slug, sites[1].slug]}
         params = {'site': [sites[0].slug, sites[1].slug]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
+    def test_device_type(self):
+        device_types = DeviceType.objects.all()[:2]
+        params = {'device_type_id': [device_types[0].pk, device_types[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_role(self):
     def test_role(self):
         device_roles = DeviceRole.objects.all()[:2]
         device_roles = DeviceRole.objects.all()[:2]
         params = {'role_id': [device_roles[0].pk, device_roles[1].pk]}
         params = {'role_id': [device_roles[0].pk, device_roles[1].pk]}

+ 16 - 2
netbox/templates/extras/configcontext.html

@@ -94,13 +94,27 @@
                             {% endif %}
                             {% endif %}
                         </td>
                         </td>
                     </tr>
                     </tr>
+                    <tr>
+                        <td>Device Types</td>
+                        <td>
+                            {% if object.device_types.all %}
+                                <ul>
+                                    {% for devicetype in object.device_types.all %}
+                                        <li><a href="{{ devicetype.get_absolute_url }}">{{ devicetype }}</a></li>
+                                    {% endfor %}
+                                </ul>
+                            {% else %}
+                                <span class="text-muted">None</span>
+                            {% endif %}
+                        </td>
+                    </tr>
                     <tr>
                     <tr>
                         <td>Roles</td>
                         <td>Roles</td>
                         <td>
                         <td>
                             {% if object.roles.all %}
                             {% if object.roles.all %}
                                 <ul>
                                 <ul>
-                                    {% for role in object.roles.all %}
-                                        <li><a href="{% url 'dcim:device_list' %}?role={{ role.slug }}">{{ role }}</a></li>
+                                    {% for devicerole in object.roles.all %}
+                                        <li><a href="{{ devicerole.get_absolute_url }}">{{ devicerole }}</a></li>
                                     {% endfor %}
                                     {% endfor %}
                                 </ul>
                                 </ul>
                             {% else %}
                             {% else %}

+ 1 - 0
netbox/templates/extras/configcontext_edit.html

@@ -17,6 +17,7 @@
             {% render_field form.regions %}
             {% render_field form.regions %}
             {% render_field form.site_groups %}
             {% render_field form.site_groups %}
             {% render_field form.sites %}
             {% render_field form.sites %}
+            {% render_field form.device_types %}
             {% render_field form.roles %}
             {% render_field form.roles %}
             {% render_field form.platforms %}
             {% render_field form.platforms %}
             {% render_field form.cluster_groups %}
             {% render_field form.cluster_groups %}