querysets.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. from django.contrib.postgres.aggregates import JSONBAgg
  2. from django.db.models import OuterRef, Subquery, Q
  3. from extras.models.tags import TaggedItem
  4. from utilities.query_functions import EmptyGroupByJSONBAgg
  5. from utilities.querysets import RestrictedQuerySet
  6. class ConfigContextQuerySet(RestrictedQuerySet):
  7. def get_for_object(self, obj, aggregate_data=False):
  8. """
  9. Return all applicable ConfigContexts for a given object. Only active ConfigContexts will be included.
  10. Args:
  11. aggregate_data: If True, use the JSONBAgg aggregate function to return only the list of JSON data objects
  12. """
  13. # `device_role` for Device; `role` for VirtualMachine
  14. role = getattr(obj, 'device_role', None) or obj.role
  15. # Virtualization cluster for VirtualMachine
  16. cluster = getattr(obj, 'cluster', None)
  17. cluster_group = getattr(cluster, 'group', None)
  18. # Get the group of the assigned tenant, if any
  19. tenant_group = obj.tenant.group if obj.tenant else None
  20. # Match against the directly assigned region as well as any parent regions.
  21. region = getattr(obj.site, 'region', None)
  22. if region:
  23. regions = region.get_ancestors(include_self=True)
  24. else:
  25. regions = []
  26. queryset = self.filter(
  27. Q(regions__in=regions) | Q(regions=None),
  28. Q(sites=obj.site) | Q(sites=None),
  29. Q(roles=role) | Q(roles=None),
  30. Q(platforms=obj.platform) | Q(platforms=None),
  31. Q(cluster_groups=cluster_group) | Q(cluster_groups=None),
  32. Q(clusters=cluster) | Q(clusters=None),
  33. Q(tenant_groups=tenant_group) | Q(tenant_groups=None),
  34. Q(tenants=obj.tenant) | Q(tenants=None),
  35. Q(tags__slug__in=obj.tags.slugs()) | Q(tags=None),
  36. is_active=True,
  37. ).order_by('weight', 'name').distinct()
  38. if aggregate_data:
  39. return queryset.aggregate(
  40. config_context_data=JSONBAgg('data', ordering=['weight', 'name'])
  41. )['config_context_data']
  42. return queryset
  43. class ConfigContextModelQuerySet(RestrictedQuerySet):
  44. """
  45. QuerySet manager used by models which support ConfigContext (device and virtual machine).
  46. Includes a method which appends an annotation of aggregated config context JSON data objects. This is
  47. implemented as a subquery which performs all the joins necessary to filter relevant config context objects.
  48. This offers a substantial performance gain over ConfigContextQuerySet.get_for_object() when dealing with
  49. multiple objects.
  50. This allows the annotation to be entirely optional.
  51. """
  52. def annotate_config_context_data(self):
  53. """
  54. Attach the subquery annotation to the base queryset
  55. """
  56. from extras.models import ConfigContext
  57. return self.annotate(
  58. config_context_data=Subquery(
  59. ConfigContext.objects.filter(
  60. self._get_config_context_filters()
  61. ).annotate(
  62. _data=EmptyGroupByJSONBAgg('data', ordering=['weight', 'name'])
  63. ).values("_data")
  64. )
  65. ).distinct()
  66. def _get_config_context_filters(self):
  67. # Construct the set of Q objects for the specific object types
  68. tag_query_filters = {
  69. "object_id": OuterRef(OuterRef('pk')),
  70. "content_type__app_label": self.model._meta.app_label,
  71. "content_type__model": self.model._meta.model_name
  72. }
  73. base_query = Q(
  74. Q(platforms=OuterRef('platform')) | Q(platforms=None),
  75. Q(cluster_groups=OuterRef('cluster__group')) | Q(cluster_groups=None),
  76. Q(clusters=OuterRef('cluster')) | Q(clusters=None),
  77. Q(tenant_groups=OuterRef('tenant__group')) | Q(tenant_groups=None),
  78. Q(tenants=OuterRef('tenant')) | Q(tenants=None),
  79. Q(
  80. tags__pk__in=Subquery(
  81. TaggedItem.objects.filter(
  82. **tag_query_filters
  83. ).values_list(
  84. 'tag_id',
  85. flat=True
  86. )
  87. )
  88. ) | Q(tags=None),
  89. is_active=True,
  90. )
  91. if self.model._meta.model_name == 'device':
  92. base_query.add((Q(roles=OuterRef('device_role')) | Q(roles=None)), Q.AND)
  93. base_query.add((Q(sites=OuterRef('site')) | Q(sites=None)), Q.AND)
  94. region_field = 'site__region'
  95. elif self.model._meta.model_name == 'virtualmachine':
  96. base_query.add((Q(roles=OuterRef('role')) | Q(roles=None)), Q.AND)
  97. base_query.add((Q(sites=OuterRef('cluster__site')) | Q(sites=None)), Q.AND)
  98. region_field = 'cluster__site__region'
  99. base_query.add(
  100. (Q(
  101. regions__tree_id=OuterRef(f'{region_field}__tree_id'),
  102. regions__level__lte=OuterRef(f'{region_field}__level'),
  103. regions__lft__lte=OuterRef(f'{region_field}__lft'),
  104. regions__rght__gte=OuterRef(f'{region_field}__rght'),
  105. ) | Q(regions=None)),
  106. Q.AND
  107. )
  108. return base_query