Просмотр исходного кода

Add CablePath.get_path() to prefetch path nodes

Jeremy Stretch 5 лет назад
Родитель
Сommit
70e966923a

+ 33 - 3
netbox/dcim/models/cables.py

@@ -1,3 +1,5 @@
+from collections import defaultdict
+
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 from django.core.exceptions import ObjectDoesNotExist, ValidationError
@@ -9,7 +11,7 @@ from taggit.managers import TaggableManager
 from dcim.choices import *
 from dcim.constants import *
 from dcim.fields import PathField
-from dcim.utils import decompile_path_node, path_node_to_object
+from dcim.utils import decompile_path_node
 from extras.models import ChangeLoggedModel, CustomFieldModel, TaggedItem
 from extras.utils import extras_features
 from utilities.fields import ColorField
@@ -366,8 +368,7 @@ class CablePath(models.Model):
         unique_together = ('origin_type', 'origin_id')
 
     def __str__(self):
-        path = ', '.join([str(path_node_to_object(node)) for node in self.path])
-        return f"Path #{self.pk}: {self.origin} to {self.destination} via ({path})"
+        return f"Path #{self.pk}: {self.origin} to {self.destination} ({len(self.path)} nodes)"
 
     def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
@@ -376,6 +377,35 @@ class CablePath(models.Model):
         model = self.origin._meta.model
         model.objects.filter(pk=self.origin.pk).update(_path=self.pk)
 
+    def get_path(self):
+        """
+        Return the path as a list of prefetched objects.
+        """
+        # Compile a list of IDs to prefetch for each type of model in the path
+        to_prefetch = defaultdict(list)
+        for node in self.path:
+            ct_id, object_id = decompile_path_node(node)
+            to_prefetch[ct_id].append(object_id)
+
+        # Prefetch path objects using one query per model type. Prefetch related devices where appropriate.
+        prefetched = {}
+        for ct_id, object_ids in to_prefetch.items():
+            model_class = ContentType.objects.get_for_id(ct_id).model_class()
+            queryset = model_class.objects.filter(pk__in=object_ids)
+            if hasattr(model_class, 'device'):
+                queryset = queryset.prefetch_related('device')
+            prefetched[ct_id] = {
+                obj.id: obj for obj in queryset
+            }
+
+        # Replicate the path using the prefetched objects.
+        path = []
+        for node in self.path:
+            ct_id, object_id = decompile_path_node(node)
+            path.append(prefetched[ct_id][object_id])
+
+        return path
+
     def get_total_length(self):
         """
         Return the sum of the length of each cable in the path.

+ 1 - 2
netbox/dcim/models/device_components.py

@@ -11,7 +11,6 @@ from taggit.managers import TaggableManager
 from dcim.choices import *
 from dcim.constants import *
 from dcim.fields import MACAddressField
-from dcim.utils import path_node_to_object
 from extras.models import ObjectChange, TaggedItem
 from extras.utils import extras_features
 from utilities.fields import NaturalOrderingField
@@ -172,7 +171,7 @@ class PathEndpoint(models.Model):
             return []
 
         # Construct the complete path
-        path = [self, *[path_node_to_object(obj) for obj in self._path.path]]
+        path = [self, *self._path.get_path()]
         while (len(path) + 1) % 3:
             # Pad to ensure we have complete three-tuples (e.g. for paths that end at a RearPort)
             path.append(None)

+ 0 - 9
netbox/dcim/utils.py

@@ -21,15 +21,6 @@ def object_to_path_node(obj):
     return compile_path_node(ct.pk, obj.pk)
 
 
-def path_node_to_object(repr):
-    """
-    Given a path node representation, return the corresponding object.
-    """
-    ct_id, object_id = decompile_path_node(repr)
-    model_class = ContentType.objects.get(pk=ct_id).model_class()
-    return model_class.objects.get(pk=int(object_id))
-
-
 def trace_path(node):
     from .models import FrontPort, RearPort
 

+ 1 - 1
netbox/dcim/views.py

@@ -2049,7 +2049,7 @@ class PathTraceView(ObjectView):
 
         return render(request, 'dcim/cable_trace.html', {
             'obj': obj,
-            'path': path,
+            'path': obj.trace(),
             'related_paths': related_paths,
             'total_length': path.get_total_length(),
         })

+ 2 - 1
netbox/templates/dcim/cable_trace.html

@@ -8,7 +8,7 @@
 {% block content %}
     <div class="row">
         <div class="col-md-5 col-sm-12 text-center">
-            {% for near_end, cable, far_end in path.origin.trace %}
+            {% for near_end, cable, far_end in path %}
 
                 {# Near end #}
                 {% if near_end.device %}
@@ -50,6 +50,7 @@
                     <div class="row">
                         <div class="text-center">
                             <h3 class="text-success text-center">Trace completed!</h3>
+                            <h5>Total segments: {{ path|length }}</h5>
                             {% if total_length %}
                                 <h5>Total length: {{ total_length|floatformat:"-2" }} Meters<h5>
                             {% endif %}