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(
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
'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
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):
"""
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.constants import *
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 utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField
@ -595,7 +595,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
)
tags = TaggableManager(through=TaggedItem)
objects = ConfigContextQuerySetMixin.as_manager()
objects = ConfigContextModelQuerySet.as_manager()
csv_headers = [
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',

View File

@ -1163,7 +1163,7 @@ class DeviceConfigView(ObjectView):
class DeviceConfigContextView(ObjectConfigContextView):
queryset = Device.objects.all()
queryset = Device.objects.annotate_config_context_data()
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
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)
# If the object has local config context data defined, merge it last

View File

@ -1,8 +1,8 @@
from collections import OrderedDict
from django.contrib.postgres.aggregates import JSONBAgg
from django.db.models import OuterRef, Subquery, Q, QuerySet
from utilities.query_functions import EmptyGroupByJSONBAgg
from utilities.querysets import RestrictedQuerySet
@ -60,18 +60,14 @@ class ConfigContextQuerySet(RestrictedQuerySet):
).order_by('weight', 'name')
class EmptyGroupByJSONBAgg(JSONBAgg):
contains_aggregate = False
class ConfigContextModelQuerySet(RestrictedQuerySet):
class ConfigContextQuerySetMixin(RestrictedQuerySet):
def add_config_context_annotation(self):
def annotate_config_context_data(self):
from extras.models import ConfigContext
return self.annotate(
config_contexts=Subquery(
config_context_data=Subquery(
ConfigContext.objects.filter(
self._add_config_context_filters()
self._get_config_context_filters()
).order_by(
'weight',
'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':
return Q(
Q(sites=OuterRef('site')) | Q(sites=None),
Q(roles=OuterRef('device_role')) | Q(roles=None),
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,
base_query.add((Q(roles=OuterRef('device_role')) | Q(roles=None)), Q.AND)
base_query.add(
(Q(
regions__tree_id=OuterRef('site__region__tree_id'),
regions__level__lte=OuterRef('site__region__level'),
regions__lft__lte=OuterRef('site__region__lft'),
regions__rght__gte=OuterRef('site__region__rght'),
) | Q(regions=None)),
Q.AND
)
else:
return Q(
Q(sites=OuterRef('site')) | Q(sites=None),
Q(roles=OuterRef('role')) | Q(roles=None),
Q(platforms=OuterRef('platform')) | Q(platforms=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),
Q(tenants=OuterRef('tenant')) | Q(tenants=None),
Q(tags=OuterRef('tags')) | Q(tags=None),
is_active=True,
base_query.add((Q(sites=OuterRef('site')) | Q(sites=None)), Q.AND)
elif self.model._meta.model_name == 'virtualmachine':
base_query.add((Q(roles=OuterRef('role')) | Q(roles=None)), Q.AND)
base_query.add((Q(cluster_groups=OuterRef('cluster__group')) | Q(cluster_groups=None)), Q.AND)
base_query.add((Q(clusters=OuterRef('cluster')) | Q(clusters=None)), Q.AND)
base_query.add(
(Q(
regions__tree_id=OuterRef('cluster__site__region__tree_id'),
regions__level__lte=OuterRef('cluster__site__region__level'),
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
@ -7,3 +8,12 @@ class CollateAsChar(Func):
"""
function = 'C'
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
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):
"""
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.models import BaseInterface, Device
from extras.models import ChangeLoggedModel, ConfigContextModel, CustomFieldModel, ObjectChange, TaggedItem
from extras.querysets import ConfigContextModelQuerySet
from extras.utils import extras_features
from utilities.fields import NaturalOrderingField
from utilities.ordering import naturalize_interface
@ -282,7 +283,7 @@ class VirtualMachine(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
)
tags = TaggableManager(through=TaggedItem)
objects = RestrictedQuerySet.as_manager()
objects = ConfigContextModelQuerySet.as_manager()
csv_headers = [
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',

View File

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