nautobot.apps.querysets - Nautobot Documentation

Skip to content

# `nautobot.apps.querysets`

Nautobot QuerySet classes and utilities.

## `nautobot.apps.querysets.ConfigContextModelQuerySet`

Bases: `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.

Source code in `nautobot/extras/querysets.py`

     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    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.

            Order By clause in Subquery is not guaranteed to be respected within the aggregated JSON array, which is why
            we include "weight" and "name" into the result so that we can sort it within Python to ensure correctness.

            TODO This method does not accurately reflect location inheritance because of the reasons stated in _get_config_context_filters()
            Do not use this method by itself, use get_config_context() method directly on ConfigContextModel instead.
            """
            from nautobot.extras.models import ConfigContext

            return self.annotate(
                config_context_data=Subquery(
                    ConfigContext.objects.filter(self._get_config_context_filters())
                    .order_by("weight", "name")
                    .annotate(
                        _data=EmptyGroupByJSONBAgg(
                            JSONObject(
                                data=F("data"),
                                name=F("name"),
                                weight=F("weight"),
                            )
                        )
                    )
                    .values("_data")
                )
            ).distinct()

        def _get_config_context_filters(self):
            """
            This method is constructing the set of Q objects for the specific object types.
            Note that locations filters are not included in the method because the filter needs the
            ability to query the ancestors for a particular tree node for subquery and we lost it since
            moving from mptt to django-tree-queries https://github.com/matthiask/django-tree-queries/issues/54.
            """
            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_groups=OuterRef("cluster__cluster_group")) | Q(cluster_groups=None),
                Q(clusters=OuterRef("cluster")) | Q(clusters=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,
            )
            base_query.add((Q(roles=OuterRef("role")) | Q(roles=None)), Q.AND)

            from nautobot.dcim.models import Location
            from nautobot.tenancy.models import TenantGroup

            if self.model._meta.model_name == "device":
                location_query_string = "location"
                base_query.add((Q(device_types=OuterRef("device_type")) | Q(device_types=None)), Q.AND)
                base_query.add(
                    (Q(device_redundancy_groups=OuterRef("device_redundancy_group")) | Q(device_redundancy_groups=None)),
                    Q.AND,
                )
            else:
                location_query_string = "cluster__location"

            location_query = Q(locations=None) | Q(locations=OuterRef(location_query_string))
            for _ in range(Location.objects.max_depth + 1):
                location_query_string += "__parent"
                location_query |= Q(locations=OuterRef(location_query_string))

            base_query.add((location_query), Q.AND)

            tenant_group_query_string = "tenant__tenant_group"
            tenant_group_query = Q(tenant_groups=None) | Q(tenant_groups=OuterRef(tenant_group_query_string))
            for _ in range(TenantGroup.objects.max_depth + 1):
                tenant_group_query_string += "__parent"
                tenant_group_query |= Q(tenant_groups=OuterRef(tenant_group_query_string))
            base_query.add((tenant_group_query), Q.AND)
            return base_query

### `annotate_config_context_data()`

Attach the subquery annotation to the base queryset.

Order By clause in Subquery is not guaranteed to be respected within the aggregated JSON array, which is why we include "weight" and "name" into the result so that we can sort it within Python to ensure correctness.

TODO This method does not accurately reflect location inheritance because of the reasons stated in _get_config_context_filters() Do not use this method by itself, use get_config_context() method directly on ConfigContextModel instead.

Source code in `nautobot/extras/querysets.py`

     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    def annotate_config_context_data(self):
        """
        Attach the subquery annotation to the base queryset.

        Order By clause in Subquery is not guaranteed to be respected within the aggregated JSON array, which is why
        we include "weight" and "name" into the result so that we can sort it within Python to ensure correctness.

        TODO This method does not accurately reflect location inheritance because of the reasons stated in _get_config_context_filters()
        Do not use this method by itself, use get_config_context() method directly on ConfigContextModel instead.
        """
        from nautobot.extras.models import ConfigContext

        return self.annotate(
            config_context_data=Subquery(
                ConfigContext.objects.filter(self._get_config_context_filters())
                .order_by("weight", "name")
                .annotate(
                    _data=EmptyGroupByJSONBAgg(
                        JSONObject(
                            data=F("data"),
                            name=F("name"),
                            weight=F("weight"),
                        )
                    )
                )
                .values("_data")
            )
        ).distinct()