from django.db.models import Count, OuterRef, Subquery, QuerySet from django.db.models.functions import Coalesce from utilities.mptt import TreeManager __all__ = ( 'count_related', 'dict_to_filter_params', 'reapply_model_ordering', ) def count_related(model, field): """ Return a Subquery suitable for annotating a child object count. """ subquery = Subquery( model.objects.filter( **{field: OuterRef('pk')} ).order_by().values( field ).annotate( c=Count('*') ).values('c') ) return Coalesce(subquery, 0) def dict_to_filter_params(d, prefix=''): """ Translate a dictionary of attributes to a nested set of parameters suitable for QuerySet filtering. For example: { "name": "Foo", "rack": { "facility_id": "R101" } } Becomes: { "name": "Foo", "rack__facility_id": "R101" } And can be employed as filter parameters: Device.objects.filter(**dict_to_filter(attrs_dict)) """ params = {} for key, val in d.items(): k = prefix + key if isinstance(val, dict): params.update(dict_to_filter_params(val, k + '__')) else: params[k] = val return params def reapply_model_ordering(queryset: QuerySet) -> QuerySet: """ Reapply model-level ordering in case it has been lost through .annotate(). https://code.djangoproject.com/ticket/32811 """ # MPTT-based models are exempt from this; use caution when annotating querysets of these models if any(isinstance(manager, TreeManager) for manager in queryset.model._meta.local_managers): return queryset elif queryset.ordered: return queryset ordering = queryset.model._meta.ordering return queryset.order_by(*ordering)