Ver Fonte

add support for regions and vms

John Anderson há 5 anos atrás
pai
commit
22d2289ed2

+ 16 - 12
netbox/dcim/api/views.py

@@ -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.

+ 2 - 2
netbox/dcim/models/devices.py

@@ -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',

+ 1 - 1
netbox/dcim/views.py

@@ -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'
 
 
 
 

+ 1 - 3
netbox/extras/models/models.py

@@ -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

+ 39 - 29
netbox/extras/querysets.py

@@ -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):
-    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
         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(
-                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

+ 10 - 0
netbox/utilities/query_functions.py

@@ -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

+ 15 - 0
netbox/virtualization/api/views.py

@@ -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.

+ 2 - 1
netbox/virtualization/models.py

@@ -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',

+ 1 - 1
netbox/virtualization/views.py

@@ -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'