Преглед на файлове

Closes #7784: Support cluster type assignment for config contexts

jeremystretch преди 4 години
родител
ревизия
77dd684916

+ 3 - 0
docs/release-notes/version-3.2.md

@@ -43,6 +43,7 @@ FIELD_CHOICES = {
 * [#7650](https://github.com/netbox-community/netbox/issues/7650) - Add support for local account password validation
 * [#7681](https://github.com/netbox-community/netbox/issues/7681) - Add `service_id` field for provider networks
 * [#7759](https://github.com/netbox-community/netbox/issues/7759) - Improved the user preferences form
+* [#7784](https://github.com/netbox-community/netbox/issues/7784) - Support cluster type assignment for config contexts
 * [#8168](https://github.com/netbox-community/netbox/issues/8168) - Add `min_vid` and `max_vid` fields to VLAN group
 
 ### Other Changes
@@ -77,6 +78,8 @@ FIELD_CHOICES = {
     * Added `module` field
 * dcim.Site
     * Removed the `asn`, `contact_name`, `contact_phone`, and `contact_email` fields
+* extras.ConfigContext
+    * Add `cluster_types` field
 * ipam.VLANGroup
     * Added the `/availables-vlans/` endpoint
     * Added the `min_vid` and `max_vid` fields

+ 12 - 4
netbox/extras/api/serializers.py

@@ -19,8 +19,10 @@ from tenancy.api.nested_serializers import NestedTenantSerializer, NestedTenantG
 from tenancy.models import Tenant, TenantGroup
 from users.api.nested_serializers import NestedUserSerializer
 from utilities.api import get_serializer_for_model
-from virtualization.api.nested_serializers import NestedClusterGroupSerializer, NestedClusterSerializer
-from virtualization.models import Cluster, ClusterGroup
+from virtualization.api.nested_serializers import (
+    NestedClusterGroupSerializer, NestedClusterSerializer, NestedClusterTypeSerializer,
+)
+from virtualization.models import Cluster, ClusterGroup, ClusterType
 from .nested_serializers import *
 
 __all__ = (
@@ -267,6 +269,12 @@ class ConfigContextSerializer(ValidatedModelSerializer):
         required=False,
         many=True
     )
+    cluster_types = SerializedPKRelatedField(
+        queryset=ClusterType.objects.all(),
+        serializer=NestedClusterTypeSerializer,
+        required=False,
+        many=True
+    )
     cluster_groups = SerializedPKRelatedField(
         queryset=ClusterGroup.objects.all(),
         serializer=NestedClusterGroupSerializer,
@@ -302,8 +310,8 @@ class ConfigContextSerializer(ValidatedModelSerializer):
         model = ConfigContext
         fields = [
             'id', 'url', 'display', 'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites',
-            'device_types', 'roles', 'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
-            'data', 'created', 'last_updated',
+            'device_types', 'roles', 'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups',
+            'tenants', 'tags', 'data', 'created', 'last_updated',
         ]
 
 

+ 12 - 1
netbox/extras/filtersets.py

@@ -7,7 +7,7 @@ from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGrou
 from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet
 from tenancy.models import Tenant, TenantGroup
 from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
-from virtualization.models import Cluster, ClusterGroup
+from virtualization.models import Cluster, ClusterGroup, ClusterType
 from .choices import *
 from .models import *
 
@@ -279,6 +279,17 @@ class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
         to_field_name='slug',
         label='Platform (slug)',
     )
+    cluster_type_id = django_filters.ModelMultipleChoiceFilter(
+        field_name='cluster_types',
+        queryset=ClusterType.objects.all(),
+        label='Cluster type',
+    )
+    cluster_type = django_filters.ModelMultipleChoiceFilter(
+        field_name='cluster_types__slug',
+        queryset=ClusterType.objects.all(),
+        to_field_name='slug',
+        label='Cluster type (slug)',
+    )
     cluster_group_id = django_filters.ModelMultipleChoiceFilter(
         field_name='cluster_groups',
         queryset=ClusterGroup.objects.all(),

+ 8 - 2
netbox/extras/forms/filtersets.py

@@ -12,7 +12,7 @@ from utilities.forms import (
     add_blank_choice, APISelectMultiple, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DateTimePicker,
     DynamicModelMultipleChoiceField, FilterForm, StaticSelect, StaticSelectMultiple, BOOLEAN_WITH_BLANK_CHOICES,
 )
-from virtualization.models import Cluster, ClusterGroup
+from virtualization.models import Cluster, ClusterGroup, ClusterType
 
 __all__ = (
     'ConfigContextFilterForm',
@@ -158,7 +158,7 @@ class ConfigContextFilterForm(FilterForm):
         ['q', 'tag'],
         ['region_id', 'site_group_id', 'site_id'],
         ['device_type_id', 'platform_id', 'role_id'],
-        ['cluster_group_id', 'cluster_id'],
+        ['cluster_type_id', 'cluster_group_id', 'cluster_id'],
         ['tenant_group_id', 'tenant_id']
     ]
     region_id = DynamicModelMultipleChoiceField(
@@ -197,6 +197,12 @@ class ConfigContextFilterForm(FilterForm):
         label=_('Platforms'),
         fetch_trigger='open'
     )
+    cluster_type_id = DynamicModelMultipleChoiceField(
+        queryset=ClusterType.objects.all(),
+        required=False,
+        label=_('Cluster types'),
+        fetch_trigger='open'
+    )
     cluster_group_id = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
         required=False,

+ 6 - 2
netbox/extras/forms/models.py

@@ -10,7 +10,7 @@ from utilities.forms import (
     add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField,
     ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField, StaticSelect,
 )
-from virtualization.models import Cluster, ClusterGroup
+from virtualization.models import Cluster, ClusterGroup, ClusterType
 
 __all__ = (
     'AddRemoveTagsForm',
@@ -165,6 +165,10 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
         queryset=Platform.objects.all(),
         required=False
     )
+    cluster_types = DynamicModelMultipleChoiceField(
+        queryset=ClusterType.objects.all(),
+        required=False
+    )
     cluster_groups = DynamicModelMultipleChoiceField(
         queryset=ClusterGroup.objects.all(),
         required=False
@@ -193,7 +197,7 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
         model = ConfigContext
         fields = (
             'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites', 'roles', 'device_types',
-            'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data',
+            'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data',
         )
 
 

+ 17 - 0
netbox/extras/migrations/0067_configcontext_cluster_types.py

@@ -0,0 +1,17 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('virtualization', '0026_vminterface_bridge'),
+        ('extras', '0066_customfield_name_validation'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='configcontext',
+            name='cluster_types',
+            field=models.ManyToManyField(blank=True, related_name='_extras_configcontext_cluster_types_+', to='virtualization.ClusterType'),
+        ),
+    ]

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

@@ -71,6 +71,11 @@ class ConfigContext(ChangeLoggedModel):
         related_name='+',
         blank=True
     )
+    cluster_types = models.ManyToManyField(
+        to='virtualization.ClusterType',
+        related_name='+',
+        blank=True
+    )
     cluster_groups = models.ManyToManyField(
         to='virtualization.ClusterGroup',
         related_name='+',

+ 4 - 1
netbox/extras/querysets.py

@@ -22,8 +22,9 @@ class ConfigContextQuerySet(RestrictedQuerySet):
         # Device type assignment is relevant only for Devices
         device_type = getattr(obj, 'device_type', None)
 
-        # Get assigned Cluster and ClusterGroup, if any
+        # Get assigned cluster, group, and type (if any)
         cluster = getattr(obj, 'cluster', None)
+        cluster_type = getattr(cluster, 'type', None)
         cluster_group = getattr(cluster, 'group', None)
 
         # Get the group of the assigned tenant, if any
@@ -44,6 +45,7 @@ class ConfigContextQuerySet(RestrictedQuerySet):
             Q(device_types=device_type) | Q(device_types=None),
             Q(roles=role) | Q(roles=None),
             Q(platforms=obj.platform) | Q(platforms=None),
+            Q(cluster_types=cluster_type) | Q(cluster_types=None),
             Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
             Q(clusters=cluster) | Q(clusters=None),
             Q(tenant_groups=tenant_group) | Q(tenant_groups=None),
@@ -93,6 +95,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
         }
         base_query = Q(
             Q(platforms=OuterRef('platform')) | Q(platforms=None),
+            Q(cluster_types=OuterRef('cluster__type')) | Q(cluster_types=None),
             Q(cluster_groups=OuterRef('cluster__group')) | Q(cluster_groups=None),
             Q(clusters=OuterRef('cluster')) | Q(clusters=None),
             Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None),

+ 1 - 1
netbox/extras/tables.py

@@ -193,7 +193,7 @@ class ConfigContextTable(BaseTable):
         model = ConfigContext
         fields = (
             'pk', 'id', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles',
-            'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants',
+            'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants',
         )
         default_columns = ('pk', 'name', 'weight', 'is_active', 'description')
 

+ 18 - 4
netbox/extras/tests/test_filtersets.py

@@ -399,6 +399,13 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
         )
         Platform.objects.bulk_create(platforms)
 
+        cluster_types = (
+            ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
+            ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
+            ClusterType(name='Cluster Type 3', slug='cluster-type-3'),
+        )
+        ClusterType.objects.bulk_create(cluster_types)
+
         cluster_groups = (
             ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
             ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
@@ -406,11 +413,10 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
         )
         ClusterGroup.objects.bulk_create(cluster_groups)
 
-        cluster_type = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
         clusters = (
-            Cluster(name='Cluster 1', type=cluster_type),
-            Cluster(name='Cluster 2', type=cluster_type),
-            Cluster(name='Cluster 3', type=cluster_type),
+            Cluster(name='Cluster 1', type=cluster_types[0]),
+            Cluster(name='Cluster 2', type=cluster_types[1]),
+            Cluster(name='Cluster 3', type=cluster_types[2]),
         )
         Cluster.objects.bulk_create(clusters)
 
@@ -442,6 +448,7 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
             c.device_types.set([device_types[i]])
             c.roles.set([device_roles[i]])
             c.platforms.set([platforms[i]])
+            c.cluster_types.set([cluster_types[i]])
             c.cluster_groups.set([cluster_groups[i]])
             c.clusters.set([clusters[i]])
             c.tenant_groups.set([tenant_groups[i]])
@@ -504,6 +511,13 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
         params = {'cluster_group': [cluster_groups[0].slug, cluster_groups[1].slug]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
+    def test_cluster_type(self):
+        cluster_types = ClusterType.objects.all()[:2]
+        params = {'cluster_type_id': [cluster_types[0].pk, cluster_types[1].pk]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+        params = {'cluster_type': [cluster_types[0].slug, cluster_types[1].slug]}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
+
     def test_cluster(self):
         clusters = Cluster.objects.all()[:2]
         params = {'cluster_id': [clusters[0].pk, clusters[1].pk]}

+ 27 - 30
netbox/extras/tests/test_models.py

@@ -216,80 +216,77 @@ class ConfigContextTest(TestCase):
         self.assertEqual(device.get_config_context(), annotated_queryset[0].get_config_context())
 
     def test_annotation_same_as_get_for_object_virtualmachine_relations(self):
+        cluster_type = ClusterType.objects.create(name="Cluster Type")
+        cluster_group = ClusterGroup.objects.create(name="Cluster Group")
+        cluster = Cluster.objects.create(name="Cluster", group=cluster_group, type=cluster_type)
 
         site_context = ConfigContext.objects.create(
             name="site",
             weight=100,
-            data={
-                "site": 1
-            }
+            data={"site": 1}
         )
         site_context.sites.add(self.site)
+
         region_context = ConfigContext.objects.create(
             name="region",
             weight=100,
-            data={
-                "region": 1
-            }
+            data={"region": 1}
         )
         region_context.regions.add(self.region)
+
         sitegroup_context = ConfigContext.objects.create(
             name="sitegroup",
             weight=100,
-            data={
-                "sitegroup": 1
-            }
+            data={"sitegroup": 1}
         )
         sitegroup_context.site_groups.add(self.sitegroup)
+
         platform_context = ConfigContext.objects.create(
             name="platform",
             weight=100,
-            data={
-                "platform": 1
-            }
+            data={"platform": 1}
         )
         platform_context.platforms.add(self.platform)
+
         tenant_group_context = ConfigContext.objects.create(
             name="tenant group",
             weight=100,
-            data={
-                "tenant_group": 1
-            }
+            data={"tenant_group": 1}
         )
         tenant_group_context.tenant_groups.add(self.tenantgroup)
+
         tenant_context = ConfigContext.objects.create(
             name="tenant",
             weight=100,
-            data={
-                "tenant": 1
-            }
+            data={"tenant": 1}
         )
         tenant_context.tenants.add(self.tenant)
+
         tag_context = ConfigContext.objects.create(
             name="tag",
             weight=100,
-            data={
-                "tag": 1
-            }
+            data={"tag": 1}
         )
         tag_context.tags.add(self.tag)
-        cluster_group = ClusterGroup.objects.create(name="Cluster Group")
+
+        cluster_type_context = ConfigContext.objects.create(
+            name="cluster type",
+            weight=100,
+            data={"cluster_type": 1}
+        )
+        cluster_type_context.cluster_types.add(cluster_type)
+
         cluster_group_context = ConfigContext.objects.create(
             name="cluster group",
             weight=100,
-            data={
-                "cluster_group": 1
-            }
+            data={"cluster_group": 1}
         )
         cluster_group_context.cluster_groups.add(cluster_group)
-        cluster_type = ClusterType.objects.create(name="Cluster Type 1")
-        cluster = Cluster.objects.create(name="Cluster", group=cluster_group, type=cluster_type)
+
         cluster_context = ConfigContext.objects.create(
             name="cluster",
             weight=100,
-            data={
-                "cluster": 1
-            }
+            data={"cluster": 1}
         )
         cluster_context.clusters.add(cluster)
 

+ 1 - 0
netbox/extras/views.py

@@ -285,6 +285,7 @@ class ConfigContextView(generic.ObjectView):
             ('Device Types', instance.device_types.all),
             ('Roles', instance.roles.all),
             ('Platforms', instance.platforms.all),
+            ('Cluster Types', instance.cluster_types.all),
             ('Cluster Groups', instance.cluster_groups.all),
             ('Clusters', instance.clusters.all),
             ('Tenant Groups', instance.tenant_groups.all),

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

@@ -20,6 +20,7 @@
             {% render_field form.device_types %}
             {% render_field form.roles %}
             {% render_field form.platforms %}
+            {% render_field form.cluster_types %}
             {% render_field form.cluster_groups %}
             {% render_field form.clusters %}
             {% render_field form.tenant_groups %}