mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #7784: Support cluster type assignment for config contexts
This commit is contained in:
@ -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
|
||||
|
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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,
|
||||
|
@ -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
netbox/extras/migrations/0067_configcontext_cluster_types.py
Normal file
17
netbox/extras/migrations/0067_configcontext_cluster_types.py
Normal file
@ -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'),
|
||||
),
|
||||
]
|
@ -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='+',
|
||||
|
@ -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),
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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]}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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 %}
|
||||
|
Reference in New Issue
Block a user