mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Closes #4833: Allow assigning config contexts by device type
This commit is contained in:
@ -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:
|
||||
|
||||
* Region
|
||||
* Site group
|
||||
* Site
|
||||
* Device type (devices only)
|
||||
* Role
|
||||
* Platform
|
||||
* Cluster group
|
||||
* Cluster
|
||||
* Cluster group (VMs only)
|
||||
* Cluster (VMs only)
|
||||
* Tenant group
|
||||
* Tenant
|
||||
* Tag
|
||||
|
@ -84,6 +84,7 @@ A new Cloud model has been introduced to represent the boundary of a network tha
|
||||
|
||||
### 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
|
||||
* [#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
|
||||
|
@ -4,10 +4,10 @@ from drf_yasg.utils import swagger_serializer_method
|
||||
from rest_framework import serializers
|
||||
|
||||
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.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
@ -251,6 +251,12 @@ class ConfigContextSerializer(ValidatedModelSerializer):
|
||||
required=False,
|
||||
many=True
|
||||
)
|
||||
device_types = SerializedPKRelatedField(
|
||||
queryset=DeviceType.objects.all(),
|
||||
serializer=NestedDeviceTypeSerializer,
|
||||
required=False,
|
||||
many=True
|
||||
)
|
||||
roles = SerializedPKRelatedField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
serializer=NestedDeviceRoleSerializer,
|
||||
@ -298,8 +304,8 @@ class ConfigContextSerializer(ValidatedModelSerializer):
|
||||
model = ConfigContext
|
||||
fields = [
|
||||
'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',
|
||||
]
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
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 utilities.filters import BaseFilterSet, ContentTypeFilter
|
||||
from virtualization.models import Cluster, ClusterGroup
|
||||
@ -206,6 +206,11 @@ class ConfigContextFilterSet(BaseFilterSet):
|
||||
to_field_name='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(
|
||||
field_name='roles',
|
||||
queryset=DeviceRole.objects.all(),
|
||||
|
@ -4,11 +4,11 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import mark_safe
|
||||
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 utilities.forms import (
|
||||
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,
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup
|
||||
@ -218,6 +218,10 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
||||
queryset=Site.objects.all(),
|
||||
required=False
|
||||
)
|
||||
device_types = DynamicModelMultipleChoiceField(
|
||||
queryset=DeviceType.objects.all(),
|
||||
required=False
|
||||
)
|
||||
roles = DynamicModelMultipleChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
required=False
|
||||
@ -253,8 +257,8 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = ConfigContext
|
||||
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,
|
||||
label=_('Sites')
|
||||
)
|
||||
device_type_id = DynamicModelMultipleChoiceField(
|
||||
queryset=DeviceType.objects.all(),
|
||||
required=False,
|
||||
label=_('Device types')
|
||||
)
|
||||
role_id = DynamicModelMultipleChoiceField(
|
||||
queryset=DeviceRole.objects.all(),
|
||||
required=False,
|
||||
|
@ -14,4 +14,9 @@ class Migration(migrations.Migration):
|
||||
name='site_groups',
|
||||
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'),
|
||||
),
|
||||
]
|
@ -6,7 +6,7 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0056_sitegroup'),
|
||||
('extras', '0056_extend_configcontext'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -56,6 +56,11 @@ class ConfigContext(ChangeLoggedModel):
|
||||
related_name='+',
|
||||
blank=True
|
||||
)
|
||||
device_types = models.ManyToManyField(
|
||||
to='dcim.DeviceType',
|
||||
related_name='+',
|
||||
blank=True
|
||||
)
|
||||
roles = models.ManyToManyField(
|
||||
to='dcim.DeviceRole',
|
||||
related_name='+',
|
||||
|
@ -19,7 +19,10 @@ class ConfigContextQuerySet(RestrictedQuerySet):
|
||||
# `device_role` for Device; `role` for VirtualMachine
|
||||
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_group = getattr(cluster, 'group', None)
|
||||
|
||||
@ -36,6 +39,7 @@ class ConfigContextQuerySet(RestrictedQuerySet):
|
||||
queryset = self.filter(
|
||||
Q(regions__in=regions) | Q(regions=None),
|
||||
Q(sites=obj.site) | Q(sites=None),
|
||||
Q(device_types=device_type) | Q(device_types=None),
|
||||
Q(roles=role) | Q(roles=None),
|
||||
Q(platforms=obj.platform) | Q(platforms=None),
|
||||
Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
|
||||
@ -108,6 +112,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
|
||||
)
|
||||
|
||||
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(sites=OuterRef('site')) | Q(sites=None)), Q.AND)
|
||||
region_field = 'site__region'
|
||||
|
@ -4,7 +4,7 @@ from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
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.filters import *
|
||||
from extras.models import *
|
||||
@ -379,6 +379,14 @@ class ConfigContextTestCase(TestCase):
|
||||
)
|
||||
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 = (
|
||||
DeviceRole(name='Device Role 1', slug='device-role-1'),
|
||||
DeviceRole(name='Device Role 2', slug='device-role-2'),
|
||||
@ -433,6 +441,7 @@ class ConfigContextTestCase(TestCase):
|
||||
c.regions.set([regions[i]])
|
||||
c.site_groups.set([site_groups[i]])
|
||||
c.sites.set([sites[i]])
|
||||
c.roles.set([device_types[i]])
|
||||
c.roles.set([device_roles[i]])
|
||||
c.platforms.set([platforms[i]])
|
||||
c.cluster_groups.set([cluster_groups[i]])
|
||||
@ -475,6 +484,11 @@ class ConfigContextTestCase(TestCase):
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
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):
|
||||
device_roles = DeviceRole.objects.all()[:2]
|
||||
params = {'role_id': [device_roles[0].pk, device_roles[1].pk]}
|
||||
|
@ -94,13 +94,27 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</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>
|
||||
<td>Roles</td>
|
||||
<td>
|
||||
{% if object.roles.all %}
|
||||
<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 %}
|
||||
</ul>
|
||||
{% else %}
|
||||
|
@ -17,6 +17,7 @@
|
||||
{% render_field form.regions %}
|
||||
{% render_field form.site_groups %}
|
||||
{% render_field form.sites %}
|
||||
{% render_field form.device_types %}
|
||||
{% render_field form.roles %}
|
||||
{% render_field form.platforms %}
|
||||
{% render_field form.cluster_groups %}
|
||||
|
Reference in New Issue
Block a user