1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

add support for regions and vms

This commit is contained in:
John Anderson
2020-10-23 01:18:04 -04:00
parent 3ba18633de
commit 22d2289ed2
9 changed files with 87 additions and 49 deletions

View File

@ -340,20 +340,24 @@ class DeviceViewSet(CustomFieldModelViewSet):
queryset = Device.objects.prefetch_related( queryset = Device.objects.prefetch_related(
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay', 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags', 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
).add_config_context_annotation() )
#queryset = Device.objects.annotate(
# config_contexts=Subquery(
# ConfigContext.objects.filter(
# Q(sites=OuterRef('site')) | Q(sites=None)
# ).annotate(
# _data=EmptyGroupByJSONBAgg('data')
# ).values("_data")
# )
#)
filterset_class = filters.DeviceFilterSet filterset_class = filters.DeviceFilterSet
def get_queryset(self):
"""
Build the proper queryset based on the request context
If the `brief` query param equates to True or the `exclude` query param
includes `config_context` as a value, return the base queryset.
Else, return the queryset annotated with config context data
"""
request = self.get_serializer_context()['request']
if request.query_params.get('brief') or 'config_context' in request.query_params.get('exclude', []):
return self.queryset
return self.queryset.annotate_config_context_data()
def get_serializer_class(self): def get_serializer_class(self):
""" """
Select the specific serializer based on the request context. Select the specific serializer based on the request context.

View File

@ -15,7 +15,7 @@ from taggit.managers import TaggableManager
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from extras.models import ChangeLoggedModel, ConfigContextModel, CustomFieldModel, TaggedItem from extras.models import ChangeLoggedModel, ConfigContextModel, CustomFieldModel, TaggedItem
from extras.querysets import ConfigContextQuerySetMixin from extras.querysets import ConfigContextModelQuerySet
from extras.utils import extras_features from extras.utils import extras_features
from utilities.choices import ColorChoices from utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField from utilities.fields import ColorField, NaturalOrderingField
@ -595,7 +595,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
objects = ConfigContextQuerySetMixin.as_manager() objects = ConfigContextModelQuerySet.as_manager()
csv_headers = [ csv_headers = [
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',

View File

@ -1163,7 +1163,7 @@ class DeviceConfigView(ObjectView):
class DeviceConfigContextView(ObjectConfigContextView): class DeviceConfigContextView(ObjectConfigContextView):
queryset = Device.objects.all() queryset = Device.objects.annotate_config_context_data()
base_template = 'dcim/device.html' base_template = 'dcim/device.html'

View File

@ -542,10 +542,8 @@ class ConfigContextModel(models.Model):
# Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs # Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
data = OrderedDict() data = OrderedDict()
#for context in ConfigContext.objects.get_for_object(self):
# data = deepmerge(data, context.data)
for context in self.config_contexts: for context in self.config_context_data:
data = deepmerge(data, context) data = deepmerge(data, context)
# If the object has local config context data defined, merge it last # If the object has local config context data defined, merge it last

View File

@ -1,8 +1,8 @@
from collections import OrderedDict from collections import OrderedDict
from django.contrib.postgres.aggregates import JSONBAgg
from django.db.models import OuterRef, Subquery, Q, QuerySet from django.db.models import OuterRef, Subquery, Q, QuerySet
from utilities.query_functions import EmptyGroupByJSONBAgg
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
@ -60,18 +60,14 @@ class ConfigContextQuerySet(RestrictedQuerySet):
).order_by('weight', 'name') ).order_by('weight', 'name')
class EmptyGroupByJSONBAgg(JSONBAgg): class ConfigContextModelQuerySet(RestrictedQuerySet):
contains_aggregate = False
def annotate_config_context_data(self):
class ConfigContextQuerySetMixin(RestrictedQuerySet):
def add_config_context_annotation(self):
from extras.models import ConfigContext from extras.models import ConfigContext
return self.annotate( return self.annotate(
config_contexts=Subquery( config_context_data=Subquery(
ConfigContext.objects.filter( ConfigContext.objects.filter(
self._add_config_context_filters() self._get_config_context_filters()
).order_by( ).order_by(
'weight', 'weight',
'name' 'name'
@ -81,28 +77,42 @@ class ConfigContextQuerySetMixin(RestrictedQuerySet):
) )
) )
def _add_config_context_filters(self): def _get_config_context_filters(self):
base_query = Q(
Q(platforms=OuterRef('platform')) | Q(platforms=None),
Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None),
Q(tenants=OuterRef('tenant')) | Q(tenants=None),
Q(tags=OuterRef('tags')) | Q(tags=None),
is_active=True,
)
if self.model._meta.model_name == 'device': if self.model._meta.model_name == 'device':
return Q( base_query.add((Q(roles=OuterRef('device_role')) | Q(roles=None)), Q.AND)
Q(sites=OuterRef('site')) | Q(sites=None), base_query.add(
Q(roles=OuterRef('device_role')) | Q(roles=None), (Q(
Q(platforms=OuterRef('platform')) | Q(platforms=None), regions__tree_id=OuterRef('site__region__tree_id'),
Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None), regions__level__lte=OuterRef('site__region__level'),
Q(tenants=OuterRef('tenant')) | Q(tenants=None), regions__lft__lte=OuterRef('site__region__lft'),
Q(tags=OuterRef('tags')) | Q(tags=None), regions__rght__gte=OuterRef('site__region__rght'),
is_active=True, ) | Q(regions=None)),
Q.AND
) )
else: base_query.add((Q(sites=OuterRef('site')) | Q(sites=None)), Q.AND)
return Q(
Q(sites=OuterRef('site')) | Q(sites=None), elif self.model._meta.model_name == 'virtualmachine':
Q(roles=OuterRef('role')) | Q(roles=None), base_query.add((Q(roles=OuterRef('role')) | Q(roles=None)), Q.AND)
Q(platforms=OuterRef('platform')) | Q(platforms=None), base_query.add((Q(cluster_groups=OuterRef('cluster__group')) | Q(cluster_groups=None)), Q.AND)
Q(cluster_groups=OuterRef('cluster__group')) | Q(cluster_groups=None), base_query.add((Q(clusters=OuterRef('cluster')) | Q(clusters=None)), Q.AND)
Q(clusters=OuterRef('cluster')) | Q(clusters=None), base_query.add(
Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None), (Q(
Q(tenants=OuterRef('tenant')) | Q(tenants=None), regions__tree_id=OuterRef('cluster__site__region__tree_id'),
Q(tags=OuterRef('tags')) | Q(tags=None), regions__level__lte=OuterRef('cluster__site__region__level'),
is_active=True, regions__lft__lte=OuterRef('cluster__site__region__lft'),
regions__rght__gte=OuterRef('cluster__site__region__rght'),
) | Q(regions=None)),
Q.AND
) )
base_query.add((Q(sites=OuterRef('cluster__site')) | Q(sites=None)), Q.AND)
return base_query

View File

@ -1,3 +1,4 @@
from django.contrib.postgres.aggregates import JSONBAgg
from django.db.models import F, Func from django.db.models import F, Func
@ -7,3 +8,12 @@ class CollateAsChar(Func):
""" """
function = 'C' function = 'C'
template = '(%(expressions)s) COLLATE "%(function)s"' template = '(%(expressions)s) COLLATE "%(function)s"'
class EmptyGroupByJSONBAgg(JSONBAgg):
"""
JSONBAgg is a builtin aggregation function which means it includes the use of a GROUP BY clause.
When used as an annotation for collecting config context data objects, the GROUP BY is
incorrect. This subclass overrides the Django ORM aggregation control to remove the GROUP BY.
"""
contains_aggregate = False

View File

@ -64,6 +64,21 @@ class VirtualMachineViewSet(CustomFieldModelViewSet):
) )
filterset_class = filters.VirtualMachineFilterSet filterset_class = filters.VirtualMachineFilterSet
def get_queryset(self):
"""
Build the proper queryset based on the request context
If the `brief` query param equates to True or the `exclude` query param
includes `config_context` as a value, return the base queryset.
Else, return the queryset annotated with config context data
"""
request = self.get_serializer_context()['request']
if request.query_params.get('brief') or 'config_context' in request.query_params.get('exclude', []):
return self.queryset
return self.queryset.annotate_config_context_data()
def get_serializer_class(self): def get_serializer_class(self):
""" """
Select the specific serializer based on the request context. Select the specific serializer based on the request context.

View File

@ -8,6 +8,7 @@ from taggit.managers import TaggableManager
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from dcim.models import BaseInterface, Device from dcim.models import BaseInterface, Device
from extras.models import ChangeLoggedModel, ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem from extras.models import ChangeLoggedModel, ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem
from extras.querysets import ConfigContextModelQuerySet
from extras.utils import extras_features from extras.utils import extras_features
from utilities.fields import NaturalOrderingField from utilities.fields import NaturalOrderingField
from utilities.ordering import naturalize_interface from utilities.ordering import naturalize_interface
@ -282,7 +283,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
) )
tags = TaggableManager(through=TaggedItem) tags = TaggableManager(through=TaggedItem)
objects = RestrictedQuerySet.as_manager() objects = ConfigContextModelQuerySet.as_manager()
csv_headers = [ csv_headers = [
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', 'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',

View File

@ -261,7 +261,7 @@ class VirtualMachineView(ObjectView):
class VirtualMachineConfigContextView(ObjectConfigContextView): class VirtualMachineConfigContextView(ObjectConfigContextView):
queryset = VirtualMachine.objects.all() queryset = VirtualMachine.objects.annotate_config_context_data()
base_template = 'virtualization/virtualmachine.html' base_template = 'virtualization/virtualmachine.html'