mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
151 lines
6.4 KiB
Python
151 lines
6.4 KiB
Python
from django.contrib.postgres.aggregates import JSONBAgg
|
|
from django.db.models import OuterRef, Subquery, Q
|
|
|
|
from extras.models.tags import TaggedItem
|
|
from utilities.query_functions import EmptyGroupByJSONBAgg
|
|
from utilities.querysets import RestrictedQuerySet
|
|
|
|
|
|
class ConfigContextQuerySet(RestrictedQuerySet):
|
|
|
|
def get_for_object(self, obj, aggregate_data=False):
|
|
"""
|
|
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
|
|
role = getattr(obj, 'device_role', None) or obj.role
|
|
|
|
# Device type assignment is relevant only for Devices
|
|
device_type = getattr(obj, 'device_type', None)
|
|
|
|
# Get assigned cluster, group, and type (if any)
|
|
cluster = getattr(obj, 'cluster', None)
|
|
cluster_type = getattr(cluster, 'type', None)
|
|
cluster_group = getattr(cluster, 'group', None)
|
|
|
|
# Get the group of the assigned tenant, if any
|
|
tenant_group = obj.tenant.group if obj.tenant else None
|
|
|
|
# Match against the directly assigned region as well as any parent regions.
|
|
region = getattr(obj.site, 'region', None)
|
|
regions = region.get_ancestors(include_self=True) if region else []
|
|
|
|
# Match against the directly assigned site group as well as any parent site groups.
|
|
sitegroup = getattr(obj.site, 'group', None)
|
|
sitegroups = sitegroup.get_ancestors(include_self=True) if sitegroup else []
|
|
|
|
queryset = self.filter(
|
|
Q(regions__in=regions) | Q(regions=None),
|
|
Q(site_groups__in=sitegroups) | Q(site_groups=None),
|
|
Q(sites=obj.site) | Q(sites=None),
|
|
Q(device_types=device_type) | Q(device_types=None),
|
|
Q(roles=role) | Q(roles=None),
|
|
Q(platforms=obj.platform) | Q(platforms=None),
|
|
Q(cluster_types=cluster_type) | Q(cluster_types=None),
|
|
Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
|
|
Q(clusters=cluster) | Q(clusters=None),
|
|
Q(tenant_groups=tenant_group) | Q(tenant_groups=None),
|
|
Q(tenants=obj.tenant) | Q(tenants=None),
|
|
Q(tags__slug__in=obj.tags.slugs()) | Q(tags=None),
|
|
is_active=True,
|
|
).order_by('weight', 'name').distinct()
|
|
|
|
if aggregate_data:
|
|
return queryset.aggregate(
|
|
config_context_data=JSONBAgg('data', ordering=['weight', 'name'])
|
|
)['config_context_data']
|
|
|
|
return queryset
|
|
|
|
|
|
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):
|
|
"""
|
|
Attach the subquery annotation to the base queryset
|
|
"""
|
|
from extras.models import ConfigContext
|
|
return self.annotate(
|
|
config_context_data=Subquery(
|
|
ConfigContext.objects.filter(
|
|
self._get_config_context_filters()
|
|
).annotate(
|
|
_data=EmptyGroupByJSONBAgg('data', ordering=['weight', 'name'])
|
|
).values("_data").order_by()
|
|
)
|
|
).distinct()
|
|
|
|
def _get_config_context_filters(self):
|
|
# Construct the set of Q objects for the specific object types
|
|
tag_query_filters = {
|
|
"object_id": OuterRef(OuterRef('pk')),
|
|
"content_type__app_label": self.model._meta.app_label,
|
|
"content_type__model": self.model._meta.model_name
|
|
}
|
|
base_query = Q(
|
|
Q(platforms=OuterRef('platform')) | Q(platforms=None),
|
|
Q(cluster_types=OuterRef('cluster__type')) | Q(cluster_types=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__pk__in=Subquery(
|
|
TaggedItem.objects.filter(
|
|
**tag_query_filters
|
|
).values_list(
|
|
'tag_id',
|
|
flat=True
|
|
)
|
|
)
|
|
) | Q(tags=None),
|
|
is_active=True,
|
|
)
|
|
|
|
if self.model._meta.model_name == 'device':
|
|
base_query.add((Q(device_types=OuterRef('device_type')) | Q(device_types=None)), Q.AND)
|
|
base_query.add((Q(roles=OuterRef('device_role')) | Q(roles=None)), Q.AND)
|
|
base_query.add((Q(sites=OuterRef('site')) | Q(sites=None)), Q.AND)
|
|
region_field = 'site__region'
|
|
sitegroup_field = 'site__group'
|
|
|
|
elif self.model._meta.model_name == 'virtualmachine':
|
|
base_query.add((Q(roles=OuterRef('role')) | Q(roles=None)), Q.AND)
|
|
base_query.add((Q(sites=OuterRef('cluster__site')) | Q(sites=None)), Q.AND)
|
|
base_query.add(Q(device_types=None), Q.AND)
|
|
region_field = 'cluster__site__region'
|
|
sitegroup_field = 'cluster__site__group'
|
|
|
|
base_query.add(
|
|
(Q(
|
|
regions__tree_id=OuterRef(f'{region_field}__tree_id'),
|
|
regions__level__lte=OuterRef(f'{region_field}__level'),
|
|
regions__lft__lte=OuterRef(f'{region_field}__lft'),
|
|
regions__rght__gte=OuterRef(f'{region_field}__rght'),
|
|
) | Q(regions=None)),
|
|
Q.AND
|
|
)
|
|
|
|
base_query.add(
|
|
(Q(
|
|
site_groups__tree_id=OuterRef(f'{sitegroup_field}__tree_id'),
|
|
site_groups__level__lte=OuterRef(f'{sitegroup_field}__level'),
|
|
site_groups__lft__lte=OuterRef(f'{sitegroup_field}__lft'),
|
|
site_groups__rght__gte=OuterRef(f'{sitegroup_field}__rght'),
|
|
) | Q(site_groups=None)),
|
|
Q.AND
|
|
)
|
|
|
|
return base_query
|