Replace annotate_depth() with annotate_tree()

This commit is contained in:
Jeremy Stretch 2020-08-05 15:55:47 -04:00
parent bdb8263610
commit 0d68d0c059
2 changed files with 18 additions and 39 deletions

View File

@ -3,34 +3,20 @@ from utilities.querysets import RestrictedQuerySet
class PrefixQuerySet(RestrictedQuerySet): class PrefixQuerySet(RestrictedQuerySet):
def annotate_depth(self, limit=None): def annotate_tree(self):
""" """
Iterate through a QuerySet of Prefixes and annotate the hierarchical level of each. While it would be preferable Annotate the number of parent and child prefixes for each Prefix. Raw SQL is needed for these subqueries
to do this using .annotate() on the QuerySet to count the unique parents of each prefix, that approach introduces because we need to cast NULL VRF values to integers for comparison. (NULL != NULL).
performance issues at scale.
Because we're adding a non-field attribute to the model, annotation must be made *after* any QuerySet
modifications.
""" """
queryset = self return self.extra(
stack = [] select={
for p in queryset: 'parents': 'SELECT COUNT(U0."prefix") AS "c" '
try: 'FROM "ipam_prefix" U0 '
prev_p = stack[-1] 'WHERE (U0."prefix" >> "ipam_prefix"."prefix" '
except IndexError: 'AND COALESCE(U0."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
prev_p = None 'children': 'SELECT COUNT(U1."prefix") AS "c" '
if prev_p is not None: 'FROM "ipam_prefix" U1 '
while (p.prefix not in prev_p.prefix) or p.prefix == prev_p.prefix: 'WHERE (U1."prefix" << "ipam_prefix"."prefix" '
stack.pop() 'AND COALESCE(U1."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
try: }
prev_p = stack[-1] )
except IndexError:
prev_p = None
break
if prev_p is not None:
prev_p.has_children = True
stack.append(p)
p.depth = len(stack) - 1
if limit is None:
return queryset
return list(filter(lambda p: p.depth <= limit, queryset))

View File

@ -221,9 +221,7 @@ class AggregateView(ObjectView):
'site', 'role' 'site', 'role'
).order_by( ).order_by(
'prefix' 'prefix'
).annotate_depth( ).annotate_tree()
limit=0
)
# Add available prefixes to the table if requested # Add available prefixes to the table if requested
if request.GET.get('show_available', 'true') == 'true': if request.GET.get('show_available', 'true') == 'true':
@ -326,11 +324,6 @@ class PrefixListView(ObjectListView):
table = tables.PrefixDetailTable table = tables.PrefixDetailTable
template_name = 'ipam/prefix_list.html' template_name = 'ipam/prefix_list.html'
def alter_queryset(self, request):
# Show only top-level prefixes by default (unless searching)
limit = None if request.GET.get('expand') or request.GET.get('q') else 0
return self.queryset.annotate_depth(limit=limit)
class PrefixView(ObjectView): class PrefixView(ObjectView):
queryset = Prefix.objects.prefetch_related('vrf', 'site__region', 'tenant__group', 'vlan__group', 'role') queryset = Prefix.objects.prefetch_related('vrf', 'site__region', 'tenant__group', 'vlan__group', 'role')
@ -353,7 +346,7 @@ class PrefixView(ObjectView):
prefix__net_contains=str(prefix.prefix) prefix__net_contains=str(prefix.prefix)
).prefetch_related( ).prefetch_related(
'site', 'role' 'site', 'role'
).annotate_depth() ).annotate_tree()
parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False) parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
parent_prefix_table.exclude = ('vrf',) parent_prefix_table.exclude = ('vrf',)
@ -386,7 +379,7 @@ class PrefixPrefixesView(ObjectView):
# Child prefixes table # Child prefixes table
child_prefixes = prefix.get_child_prefixes().restrict(request.user, 'view').prefetch_related( child_prefixes = prefix.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
'site', 'vlan', 'role', 'site', 'vlan', 'role',
).annotate_depth(limit=0) ).annotate_tree()
# Add available prefixes to the table if requested # Add available prefixes to the table if requested
if child_prefixes and request.GET.get('show_available', 'true') == 'true': if child_prefixes and request.GET.get('show_available', 'true') == 'true':