Coverage for utilities/query.py: 95%
27 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django.db.models import Count, OuterRef, QuerySet, Subquery
2from django.db.models.functions import Coalesce
4__all__ = (
5 'count_related',
6 'dict_to_filter_params',
7 'reapply_model_ordering',
8)
11def count_related(model, field):
12 """
13 Return a Subquery suitable for annotating a child object count.
14 """
15 subquery = Subquery(
16 model.objects.filter(
17 **{field: OuterRef('pk')}
18 ).order_by().values(
19 field
20 ).annotate(
21 c=Count('*')
22 ).values('c')
23 )
25 return Coalesce(subquery, 0)
28def dict_to_filter_params(d, prefix=''):
29 """
30 Translate a dictionary of attributes to a nested set of parameters suitable for QuerySet filtering. For example:
32 {
33 "name": "Foo",
34 "rack": {
35 "facility_id": "R101"
36 }
37 }
39 Becomes:
41 {
42 "name": "Foo",
43 "rack__facility_id": "R101"
44 }
46 And can be employed as filter parameters:
48 Device.objects.filter(**dict_to_filter(attrs_dict))
49 """
50 params = {}
51 for key, val in d.items():
52 k = prefix + key
53 if isinstance(val, dict):
54 params.update(dict_to_filter_params(val, k + '__'))
55 else:
56 params[k] = val
57 return params
60# TODO: Remove in NetBox v5.0. MPTT support is retained only for plugins that have
61# not yet migrated their tree models to netbox.models.ltree.LtreeModel; NetBox core
62# no longer uses django-mptt. When MPTT support is dropped, delete this helper and
63# its call in reapply_model_ordering().
64def _is_mptt_model(model) -> bool:
65 """
66 Whether `model` is backed by django-mptt (deprecated). MPTT applies its own
67 tree ordering via the TreeManager, so such querysets must NOT have plain
68 model-level ordering reapplied after .annotate().
69 """
70 from mptt.managers import TreeManager
72 return any(isinstance(manager, TreeManager) for manager in model._meta.local_managers)
75def reapply_model_ordering(queryset: QuerySet) -> QuerySet:
76 """
77 Reapply model-level ordering in case it has been lost through .annotate().
78 https://code.djangoproject.com/ticket/32811
79 """
80 # Models ordered by a trigger-maintained ltree column (`sort_path`/`path`) are
81 # exempt. Key the check on the ordering itself, NOT on LtreeManager presence:
82 # InventoryItem/InventoryItemTemplate use an LtreeManager only for path
83 # maintenance but order by a regular column (name), so they DO need their
84 # ordering reapplied after .annotate() strips it (Django #32811).
85 ordering = queryset.model._meta.ordering or ()
86 if any(isinstance(f, str) and f.lstrip('-') in ('sort_path', 'path') for f in ordering):
87 return queryset
89 # TODO: Remove in NetBox v5.0 (see _is_mptt_model). Plugins may still use MPTT
90 # via the generic bulk views, so keep exempting MPTT-based models for now.
91 if _is_mptt_model(queryset.model): 91 ↛ 92line 91 didn't jump to line 92 because the condition on line 91 was never true
92 return queryset
94 if queryset.ordered:
95 return queryset
97 ordering = queryset.model._meta.ordering
98 return queryset.order_by(*ordering)