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):
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
to do this using .annotate() on the QuerySet to count the unique parents of each prefix, that approach introduces
performance issues at scale.
Because we're adding a non-field attribute to the model, annotation must be made *after* any QuerySet
modifications.
Annotate the number of parent and child prefixes for each Prefix. Raw SQL is needed for these subqueries
because we need to cast NULL VRF values to integers for comparison. (NULL != NULL).
"""
queryset = self
stack = []
for p in queryset:
try:
prev_p = stack[-1]
except IndexError:
prev_p = None
if prev_p is not None:
while (p.prefix not in prev_p.prefix) or p.prefix == prev_p.prefix:
stack.pop()
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))
return self.extra(
select={
'parents': 'SELECT COUNT(U0."prefix") AS "c" '
'FROM "ipam_prefix" U0 '
'WHERE (U0."prefix" >> "ipam_prefix"."prefix" '
'AND COALESCE(U0."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
'children': 'SELECT COUNT(U1."prefix") AS "c" '
'FROM "ipam_prefix" U1 '
'WHERE (U1."prefix" << "ipam_prefix"."prefix" '
'AND COALESCE(U1."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
}
)

View File

@ -221,9 +221,7 @@ class AggregateView(ObjectView):
'site', 'role'
).order_by(
'prefix'
).annotate_depth(
limit=0
)
).annotate_tree()
# Add available prefixes to the table if requested
if request.GET.get('show_available', 'true') == 'true':
@ -326,11 +324,6 @@ class PrefixListView(ObjectListView):
table = tables.PrefixDetailTable
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):
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)
).prefetch_related(
'site', 'role'
).annotate_depth()
).annotate_tree()
parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
parent_prefix_table.exclude = ('vrf',)
@ -386,7 +379,7 @@ class PrefixPrefixesView(ObjectView):
# Child prefixes table
child_prefixes = prefix.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
'site', 'vlan', 'role',
).annotate_depth(limit=0)
).annotate_tree()
# Add available prefixes to the table if requested
if child_prefixes and request.GET.get('show_available', 'true') == 'true':