mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
Closes #9582: Enable assigning config contexts based on device location
This commit is contained in:
parent
341615668b
commit
379880cd84
@ -5,9 +5,11 @@ Sometimes it is desirable to associate additional data with a group of devices o
|
|||||||
* Region
|
* Region
|
||||||
* Site group
|
* Site group
|
||||||
* Site
|
* Site
|
||||||
|
* Location (devices only)
|
||||||
* Device type (devices only)
|
* Device type (devices only)
|
||||||
* Role
|
* Role
|
||||||
* Platform
|
* Platform
|
||||||
|
* Cluster type (VMs only)
|
||||||
* Cluster group (VMs only)
|
* Cluster group (VMs only)
|
||||||
* Cluster (VMs only)
|
* Cluster (VMs only)
|
||||||
* Tenant group
|
* Tenant group
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
* [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
|
* [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
|
||||||
* [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results
|
* [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results
|
||||||
* [#9166](https://github.com/netbox-community/netbox/issues/9166) - Add UI visibility toggle for custom fields
|
* [#9166](https://github.com/netbox-community/netbox/issues/9166) - Add UI visibility toggle for custom fields
|
||||||
|
* [#9582](https://github.com/netbox-community/netbox/issues/9582) - Enable assigning config contexts based on device location
|
||||||
|
|
||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
@ -45,6 +46,8 @@
|
|||||||
* Added required `status` field (default value: `active`)
|
* Added required `status` field (default value: `active`)
|
||||||
* dcim.Rack
|
* dcim.Rack
|
||||||
* The `elevation` endpoint now includes half-height rack units, and utilizes decimal values for the ID and name of each unit
|
* The `elevation` endpoint now includes half-height rack units, and utilizes decimal values for the ID and name of each unit
|
||||||
|
* extras.ConfigContext
|
||||||
|
* Added the `locations` many-to-many field to track the assignment of ConfigContexts to Locations
|
||||||
* extras.CustomField
|
* extras.CustomField
|
||||||
* Added `group_name` and `ui_visibility` fields
|
* Added `group_name` and `ui_visibility` fields
|
||||||
* ipam.IPAddress
|
* ipam.IPAddress
|
||||||
|
@ -5,10 +5,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 (
|
||||||
NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer, NestedRegionSerializer,
|
NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer,
|
||||||
NestedSiteSerializer, NestedSiteGroupSerializer,
|
NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
|
||||||
)
|
)
|
||||||
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Location, Platform, 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
|
||||||
@ -272,6 +272,12 @@ class ConfigContextSerializer(ValidatedModelSerializer):
|
|||||||
required=False,
|
required=False,
|
||||||
many=True
|
many=True
|
||||||
)
|
)
|
||||||
|
locations = SerializedPKRelatedField(
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
serializer=NestedLocationSerializer,
|
||||||
|
required=False,
|
||||||
|
many=True
|
||||||
|
)
|
||||||
device_types = SerializedPKRelatedField(
|
device_types = SerializedPKRelatedField(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
serializer=NestedDeviceTypeSerializer,
|
serializer=NestedDeviceTypeSerializer,
|
||||||
@ -331,8 +337,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',
|
||||||
'device_types', 'roles', 'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups',
|
'locations', 'device_types', 'roles', 'platforms', 'cluster_types', 'cluster_groups', 'clusters',
|
||||||
'tenants', 'tags', 'data', 'created', 'last_updated',
|
'tenant_groups', 'tenants', 'tags', 'data', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ class JournalEntryViewSet(NetBoxModelViewSet):
|
|||||||
|
|
||||||
class ConfigContextViewSet(NetBoxModelViewSet):
|
class ConfigContextViewSet(NetBoxModelViewSet):
|
||||||
queryset = ConfigContext.objects.prefetch_related(
|
queryset = ConfigContext.objects.prefetch_related(
|
||||||
'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
|
'regions', 'site_groups', 'sites', 'locations', 'roles', 'platforms', 'tenant_groups', 'tenants',
|
||||||
)
|
)
|
||||||
serializer_class = serializers.ConfigContextSerializer
|
serializer_class = serializers.ConfigContextSerializer
|
||||||
filterset_class = filtersets.ConfigContextFilterSet
|
filterset_class = filtersets.ConfigContextFilterSet
|
||||||
|
@ -3,7 +3,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
||||||
from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet
|
from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
|
from utilities.filters import ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter
|
||||||
@ -255,6 +255,17 @@ class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
|
location_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='locations',
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
label='Location',
|
||||||
|
)
|
||||||
|
location = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='locations__slug',
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Location (slug)',
|
||||||
|
)
|
||||||
device_type_id = django_filters.ModelMultipleChoiceFilter(
|
device_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='device_types',
|
field_name='device_types',
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
|
@ -3,7 +3,7 @@ from django.contrib.auth.models import User
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Location, Platform, 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
|
||||||
@ -170,7 +170,7 @@ class TagFilterForm(FilterForm):
|
|||||||
class ConfigContextFilterForm(FilterForm):
|
class ConfigContextFilterForm(FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag_id')),
|
(None, ('q', 'tag_id')),
|
||||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||||
('Device', ('device_type_id', 'platform_id', 'role_id')),
|
('Device', ('device_type_id', 'platform_id', 'role_id')),
|
||||||
('Cluster', ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
|
('Cluster', ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
|
||||||
('Tenant', ('tenant_group_id', 'tenant_id'))
|
('Tenant', ('tenant_group_id', 'tenant_id'))
|
||||||
@ -190,6 +190,11 @@ class ConfigContextFilterForm(FilterForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label=_('Sites')
|
label=_('Sites')
|
||||||
)
|
)
|
||||||
|
location_id = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
required=False,
|
||||||
|
label=_('Locations')
|
||||||
|
)
|
||||||
device_type_id = DynamicModelMultipleChoiceField(
|
device_type_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Location, Platform, 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
|
||||||
@ -166,6 +166,10 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
|||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
locations = DynamicModelMultipleChoiceField(
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
device_types = DynamicModelMultipleChoiceField(
|
device_types = DynamicModelMultipleChoiceField(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
@ -202,15 +206,22 @@ class ConfigContextForm(BootstrapMixin, forms.ModelForm):
|
|||||||
queryset=Tag.objects.all(),
|
queryset=Tag.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
data = JSONField(
|
data = JSONField()
|
||||||
label=''
|
|
||||||
|
fieldsets = (
|
||||||
|
('Config Context', ('name', 'weight', 'description', 'data', 'is_active')),
|
||||||
|
('Assignment', (
|
||||||
|
'regions', 'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
|
||||||
|
'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConfigContext
|
model = ConfigContext
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'weight', 'description', 'is_active', 'regions', 'site_groups', 'sites', 'roles', 'device_types',
|
'name', 'weight', 'description', 'data', 'is_active', 'regions', 'site_groups', 'sites', 'locations',
|
||||||
'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data',
|
'roles', 'device_types', 'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups',
|
||||||
|
'tenants', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
19
netbox/extras/migrations/0076_configcontext_locations.py
Normal file
19
netbox/extras/migrations/0076_configcontext_locations.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 4.0.5 on 2022-06-22 19:13
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0156_location_status'),
|
||||||
|
('extras', '0075_customfield_ui_visibility'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='configcontext',
|
||||||
|
name='locations',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='+', to='dcim.location'),
|
||||||
|
),
|
||||||
|
]
|
@ -1,5 +1,3 @@
|
|||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -55,6 +53,11 @@ class ConfigContext(WebhooksMixin, ChangeLoggedModel):
|
|||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
locations = models.ManyToManyField(
|
||||||
|
to='dcim.Location',
|
||||||
|
related_name='+',
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
device_types = models.ManyToManyField(
|
device_types = models.ManyToManyField(
|
||||||
to='dcim.DeviceType',
|
to='dcim.DeviceType',
|
||||||
related_name='+',
|
related_name='+',
|
||||||
@ -138,11 +141,10 @@ class ConfigContextModel(models.Model):
|
|||||||
|
|
||||||
def get_config_context(self):
|
def get_config_context(self):
|
||||||
"""
|
"""
|
||||||
|
Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs.
|
||||||
Return the rendered configuration context for a device or VM.
|
Return the rendered configuration context for a device or VM.
|
||||||
"""
|
"""
|
||||||
|
data = {}
|
||||||
# Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
|
|
||||||
data = OrderedDict()
|
|
||||||
|
|
||||||
if not hasattr(self, 'config_context_data'):
|
if not hasattr(self, 'config_context_data'):
|
||||||
# The annotation is not available, so we fall back to manually querying for the config context objects
|
# The annotation is not available, so we fall back to manually querying for the config context objects
|
||||||
|
@ -19,8 +19,9 @@ 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
|
||||||
|
|
||||||
# Device type assignment is relevant only for Devices
|
# Device type and location assignment is relevant only for Devices
|
||||||
device_type = getattr(obj, 'device_type', None)
|
device_type = getattr(obj, 'device_type', None)
|
||||||
|
location = getattr(obj, 'location', None)
|
||||||
|
|
||||||
# Get assigned cluster, group, and type (if any)
|
# Get assigned cluster, group, and type (if any)
|
||||||
cluster = getattr(obj, 'cluster', None)
|
cluster = getattr(obj, 'cluster', None)
|
||||||
@ -42,6 +43,7 @@ class ConfigContextQuerySet(RestrictedQuerySet):
|
|||||||
Q(regions__in=regions) | Q(regions=None),
|
Q(regions__in=regions) | Q(regions=None),
|
||||||
Q(site_groups__in=sitegroups) | Q(site_groups=None),
|
Q(site_groups__in=sitegroups) | Q(site_groups=None),
|
||||||
Q(sites=obj.site) | Q(sites=None),
|
Q(sites=obj.site) | Q(sites=None),
|
||||||
|
Q(locations=location) | Q(locations=None),
|
||||||
Q(device_types=device_type) | Q(device_types=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),
|
||||||
@ -114,6 +116,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.model._meta.model_name == 'device':
|
if self.model._meta.model_name == 'device':
|
||||||
|
base_query.add((Q(locations=OuterRef('location')) | Q(locations=None)), Q.AND)
|
||||||
base_query.add((Q(device_types=OuterRef('device_type')) | Q(device_types=None)), Q.AND)
|
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)
|
||||||
|
@ -167,8 +167,9 @@ class ConfigContextTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = ConfigContext
|
model = ConfigContext
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles', 'platforms',
|
'pk', 'id', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'locations', 'roles',
|
||||||
'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'created', 'last_updated',
|
'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'created',
|
||||||
|
'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'weight', 'is_active', 'description')
|
default_columns = ('pk', 'name', 'weight', 'is_active', 'description')
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from circuits.models import Provider
|
from circuits.models import Provider
|
||||||
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Location, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
||||||
from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
|
from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
|
||||||
from extras.filtersets import *
|
from extras.filtersets import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
@ -368,9 +368,9 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
regions = (
|
regions = (
|
||||||
Region(name='Test Region 1', slug='test-region-1'),
|
Region(name='Region 1', slug='region-1'),
|
||||||
Region(name='Test Region 2', slug='test-region-2'),
|
Region(name='Region 2', slug='region-2'),
|
||||||
Region(name='Test Region 3', slug='test-region-3'),
|
Region(name='Region 3', slug='region-3'),
|
||||||
)
|
)
|
||||||
for r in regions:
|
for r in regions:
|
||||||
r.save()
|
r.save()
|
||||||
@ -384,12 +384,20 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
site_group.save()
|
site_group.save()
|
||||||
|
|
||||||
sites = (
|
sites = (
|
||||||
Site(name='Test Site 1', slug='test-site-1'),
|
Site(name='Site 1', slug='site-1'),
|
||||||
Site(name='Test Site 2', slug='test-site-2'),
|
Site(name='Site 2', slug='site-2'),
|
||||||
Site(name='Test Site 3', slug='test-site-3'),
|
Site(name='Site 3', slug='site-3'),
|
||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
|
locations = (
|
||||||
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
|
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||||
|
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||||
|
)
|
||||||
|
for location in locations:
|
||||||
|
location.save()
|
||||||
|
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
device_types = (
|
device_types = (
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
||||||
@ -460,6 +468,7 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
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.locations.set([locations[i]])
|
||||||
c.device_types.set([device_types[i]])
|
c.device_types.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]])
|
||||||
@ -501,6 +510,13 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
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_location(self):
|
||||||
|
locations = Location.objects.all()[:2]
|
||||||
|
params = {'location_id': [locations[0].pk, locations[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'location': [locations[0].slug, locations[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_device_type(self):
|
def test_device_type(self):
|
||||||
device_types = DeviceType.objects.all()[:2]
|
device_types = DeviceType.objects.all()[:2]
|
||||||
params = {'device_type_id': [device_types[0].pk, device_types[1].pk]}
|
params = {'device_type_id': [device_types[0].pk, device_types[1].pk]}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Region, Site, SiteGroup
|
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Platform, Region, Site, SiteGroup
|
||||||
from extras.models import ConfigContext, Tag
|
from extras.models import ConfigContext, Tag
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||||
@ -29,7 +29,8 @@ class ConfigContextTest(TestCase):
|
|||||||
self.devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
self.devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
||||||
self.region = Region.objects.create(name="Region")
|
self.region = Region.objects.create(name="Region")
|
||||||
self.sitegroup = SiteGroup.objects.create(name="Site Group")
|
self.sitegroup = SiteGroup.objects.create(name="Site Group")
|
||||||
self.site = Site.objects.create(name='Site-1', slug='site-1', region=self.region, group=self.sitegroup)
|
self.site = Site.objects.create(name='Site 1', slug='site-1', region=self.region, group=self.sitegroup)
|
||||||
|
self.location = Location.objects.create(name='Location 1', slug='location-1', site=self.site)
|
||||||
self.platform = Platform.objects.create(name="Platform")
|
self.platform = Platform.objects.create(name="Platform")
|
||||||
self.tenantgroup = TenantGroup.objects.create(name="Tenant Group")
|
self.tenantgroup = TenantGroup.objects.create(name="Tenant Group")
|
||||||
self.tenant = Tenant.objects.create(name="Tenant", group=self.tenantgroup)
|
self.tenant = Tenant.objects.create(name="Tenant", group=self.tenantgroup)
|
||||||
@ -40,7 +41,8 @@ class ConfigContextTest(TestCase):
|
|||||||
name='Device 1',
|
name='Device 1',
|
||||||
device_type=self.devicetype,
|
device_type=self.devicetype,
|
||||||
device_role=self.devicerole,
|
device_role=self.devicerole,
|
||||||
site=self.site
|
site=self.site,
|
||||||
|
location=self.location
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_higher_weight_wins(self):
|
def test_higher_weight_wins(self):
|
||||||
@ -144,15 +146,6 @@ class ConfigContextTest(TestCase):
|
|||||||
self.assertEqual(self.device.get_config_context(), annotated_queryset[0].get_config_context())
|
self.assertEqual(self.device.get_config_context(), annotated_queryset[0].get_config_context())
|
||||||
|
|
||||||
def test_annotation_same_as_get_for_object_device_relations(self):
|
def test_annotation_same_as_get_for_object_device_relations(self):
|
||||||
|
|
||||||
site_context = ConfigContext.objects.create(
|
|
||||||
name="site",
|
|
||||||
weight=100,
|
|
||||||
data={
|
|
||||||
"site": 1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
site_context.sites.add(self.site)
|
|
||||||
region_context = ConfigContext.objects.create(
|
region_context = ConfigContext.objects.create(
|
||||||
name="region",
|
name="region",
|
||||||
weight=100,
|
weight=100,
|
||||||
@ -169,6 +162,22 @@ class ConfigContextTest(TestCase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
sitegroup_context.site_groups.add(self.sitegroup)
|
sitegroup_context.site_groups.add(self.sitegroup)
|
||||||
|
site_context = ConfigContext.objects.create(
|
||||||
|
name="site",
|
||||||
|
weight=100,
|
||||||
|
data={
|
||||||
|
"site": 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
site_context.sites.add(self.site)
|
||||||
|
location_context = ConfigContext.objects.create(
|
||||||
|
name="location",
|
||||||
|
weight=100,
|
||||||
|
data={
|
||||||
|
"location": 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
location_context.locations.add(self.location)
|
||||||
platform_context = ConfigContext.objects.create(
|
platform_context = ConfigContext.objects.create(
|
||||||
name="platform",
|
name="platform",
|
||||||
weight=100,
|
weight=100,
|
||||||
@ -205,6 +214,7 @@ class ConfigContextTest(TestCase):
|
|||||||
device = Device.objects.create(
|
device = Device.objects.create(
|
||||||
name="Device 2",
|
name="Device 2",
|
||||||
site=self.site,
|
site=self.site,
|
||||||
|
location=self.location,
|
||||||
tenant=self.tenant,
|
tenant=self.tenant,
|
||||||
platform=self.platform,
|
platform=self.platform,
|
||||||
device_role=self.devicerole,
|
device_role=self.devicerole,
|
||||||
@ -220,13 +230,6 @@ class ConfigContextTest(TestCase):
|
|||||||
cluster_group = ClusterGroup.objects.create(name="Cluster Group")
|
cluster_group = ClusterGroup.objects.create(name="Cluster Group")
|
||||||
cluster = Cluster.objects.create(name="Cluster", group=cluster_group, type=cluster_type)
|
cluster = Cluster.objects.create(name="Cluster", group=cluster_group, type=cluster_type)
|
||||||
|
|
||||||
site_context = ConfigContext.objects.create(
|
|
||||||
name="site",
|
|
||||||
weight=100,
|
|
||||||
data={"site": 1}
|
|
||||||
)
|
|
||||||
site_context.sites.add(self.site)
|
|
||||||
|
|
||||||
region_context = ConfigContext.objects.create(
|
region_context = ConfigContext.objects.create(
|
||||||
name="region",
|
name="region",
|
||||||
weight=100,
|
weight=100,
|
||||||
@ -241,6 +244,13 @@ class ConfigContextTest(TestCase):
|
|||||||
)
|
)
|
||||||
sitegroup_context.site_groups.add(self.sitegroup)
|
sitegroup_context.site_groups.add(self.sitegroup)
|
||||||
|
|
||||||
|
site_context = ConfigContext.objects.create(
|
||||||
|
name="site",
|
||||||
|
weight=100,
|
||||||
|
data={"site": 1}
|
||||||
|
)
|
||||||
|
site_context.sites.add(self.site)
|
||||||
|
|
||||||
platform_context = ConfigContext.objects.create(
|
platform_context = ConfigContext.objects.create(
|
||||||
name="platform",
|
name="platform",
|
||||||
weight=100,
|
weight=100,
|
||||||
|
@ -281,6 +281,7 @@ class ConfigContextView(generic.ObjectView):
|
|||||||
('Regions', instance.regions.all),
|
('Regions', instance.regions.all),
|
||||||
('Site Groups', instance.site_groups.all),
|
('Site Groups', instance.site_groups.all),
|
||||||
('Sites', instance.sites.all),
|
('Sites', instance.sites.all),
|
||||||
|
('Locations', instance.locations.all),
|
||||||
('Device Types', instance.device_types.all),
|
('Device Types', instance.device_types.all),
|
||||||
('Roles', instance.roles.all),
|
('Roles', instance.roles.all),
|
||||||
('Platforms', instance.platforms.all),
|
('Platforms', instance.platforms.all),
|
||||||
@ -311,7 +312,6 @@ class ConfigContextView(generic.ObjectView):
|
|||||||
class ConfigContextEditView(generic.ObjectEditView):
|
class ConfigContextEditView(generic.ObjectEditView):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
form = forms.ConfigContextForm
|
form = forms.ConfigContextForm
|
||||||
template_name = 'extras/configcontext_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextBulkEditView(generic.BulkEditView):
|
class ConfigContextBulkEditView(generic.BulkEditView):
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">Config Context</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
{% render_field form.name %}
|
|
||||||
{% render_field form.weight %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
{% render_field form.is_active %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">Assignment</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
{% 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_types %}
|
|
||||||
{% render_field form.cluster_groups %}
|
|
||||||
{% render_field form.clusters %}
|
|
||||||
{% render_field form.tenant_groups %}
|
|
||||||
{% render_field form.tenants %}
|
|
||||||
{% render_field form.tags %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">Data</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
{% render_field form.data %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
Loading…
Reference in New Issue
Block a user