2
0
Эх сурвалжийг харах

account for null value annotations

John Anderson 5 жил өмнө
parent
commit
82f5d0070e

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

@@ -543,7 +543,14 @@ 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 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
+            config_context_data = ConfigContext.objects.get_for_object(self, aggregate_data=True)
+        else:
+            # The attribute may exist, but the annotated value could be None if there is no config context data
+            config_context_data = self.config_context_data or []
+
+        for context in 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

+ 25 - 3
netbox/extras/querysets.py

@@ -1,5 +1,6 @@
 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.query_functions import EmptyGroupByJSONBAgg
@@ -24,9 +25,12 @@ class CustomFieldQueryset:
 
 
 class ConfigContextQuerySet(RestrictedQuerySet):
 class ConfigContextQuerySet(RestrictedQuerySet):
 
 
-    def get_for_object(self, obj):
+    def get_for_object(self, obj, aggregate_data=False):
         """
         """
         Return all applicable ConfigContexts for a given object. Only active ConfigContexts will be included.
         Return all applicable ConfigContexts for a given object. Only active ConfigContexts will be included.
+
+        Args:
+          aggregate_data: If True, use the JSONBAgg aggregate function to return only the list of JSON data objects
         """
         """
 
 
         # `device_role` for Device; `role` for VirtualMachine
         # `device_role` for Device; `role` for VirtualMachine
@@ -46,7 +50,7 @@ class ConfigContextQuerySet(RestrictedQuerySet):
         else:
         else:
             regions = []
             regions = []
 
 
-        return self.filter(
+        queryset = self.filter(
             Q(regions__in=regions) | Q(regions=None),
             Q(regions__in=regions) | Q(regions=None),
             Q(sites=obj.site) | Q(sites=None),
             Q(sites=obj.site) | Q(sites=None),
             Q(roles=role) | Q(roles=None),
             Q(roles=role) | Q(roles=None),
@@ -59,10 +63,28 @@ class ConfigContextQuerySet(RestrictedQuerySet):
             is_active=True,
             is_active=True,
         ).order_by('weight', 'name')
         ).order_by('weight', 'name')
 
 
+        if aggregate_data:
+            queryset = queryset.aggregate(config_context_data=JSONBAgg('data'))['config_context_data']
+
+        return queryset
+
 
 
 class ConfigContextModelQuerySet(RestrictedQuerySet):
 class ConfigContextModelQuerySet(RestrictedQuerySet):
+    """
+    QuerySet manager used by models which support ConfigContext (device and virtual machine).
+
+    Includes a method which appends an annotation of aggregated config context JSON data objects. This is
+    implemented as a subquery which performs all the joins necessary to filter relevant config context objects.
+    This offers a substantial performance gain over ConfigContextQuerySet.get_for_object() when dealing with
+    multiple objects.
+
+    This allows the annotation to be entirely optional.
+    """
 
 
     def annotate_config_context_data(self):
     def annotate_config_context_data(self):
+        """
+        Attach the subquery annotation to the base queryset
+        """
         from extras.models import ConfigContext
         from extras.models import ConfigContext
         return self.annotate(
         return self.annotate(
             config_context_data=Subquery(
             config_context_data=Subquery(
@@ -78,7 +100,7 @@ class ConfigContextModelQuerySet(RestrictedQuerySet):
         )
         )
 
 
     def _get_config_context_filters(self):
     def _get_config_context_filters(self):
-
+        # Construct the set of Q objects for the specific object types
         base_query = Q(
         base_query = Q(
             Q(platforms=OuterRef('platform')) | Q(platforms=None),
             Q(platforms=OuterRef('platform')) | Q(platforms=None),
             Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None),
             Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None),