2121
2222from .data import TagDataQuerySet
2323from .models import ObjectTag , Tag , Taxonomy
24- from .models .utils import ConcatNull
24+ from .models .utils import ConcatNull , StringAgg
2525
2626# Export this as part of the API
2727TagDoesNotExist = Tag .DoesNotExist
@@ -196,7 +196,7 @@ def get_object_tags(
196196 return tags
197197
198198
199- def get_object_tag_counts (object_id_pattern : str ) -> dict [str , int ]:
199+ def get_object_tag_counts (object_id_pattern : str , count_implicit = False ) -> dict [str , int ]:
200200 """
201201 Given an object ID, a "starts with" glob pattern like
202202 "course-v1:foo+bar+baz@*", or a list of "comma,separated,IDs", return a
@@ -217,8 +217,36 @@ def get_object_tag_counts(object_id_pattern: str) -> dict[str, int]:
217217 qs = qs .exclude (taxonomy_id = None ) # The whole taxonomy was deleted
218218 qs = qs .exclude (taxonomy__enabled = False ) # The whole taxonomy is disabled
219219 qs = qs .exclude (tag_id = None , taxonomy__allow_free_text = False ) # The taxonomy exists but the tag is deleted
220- qs = qs .values ("object_id" ).annotate (num_tags = models .Count ("id" )).order_by ("object_id" )
221- return {row ["object_id" ]: row ["num_tags" ] for row in qs }
220+ if count_implicit :
221+ # Counting the implicit tags is tricky, because if two "grandchild" tags have the same implicit parent tag, we
222+ # need to count that parent tag only once. To do that, we collect all the ancestor tag IDs into an aggregate
223+ # string, and then count the unique values using python
224+ qs = qs .values ("object_id" ).annotate (
225+ num_tags = models .Count ("id" ),
226+ tag_ids_str_1 = StringAgg ("tag_id" ),
227+ tag_ids_str_2 = StringAgg ("tag__parent_id" ),
228+ tag_ids_str_3 = StringAgg ("tag__parent__parent_id" ),
229+ tag_ids_str_4 = StringAgg ("tag__parent__parent__parent_id" ),
230+ ).order_by ("object_id" )
231+ result = {}
232+ for row in qs :
233+ # ObjectTags for free text taxonomies will be included in "num_tags" count, but not "tag_ids_str_1" since
234+ # they have no tag ID. We can compute how many free text tags each object has now:
235+ if row ["tag_ids_str_1" ]:
236+ num_free_text_tags = row ["num_tags" ] - len (row ["tag_ids_str_1" ].split ("," ))
237+ else :
238+ num_free_text_tags = row ["num_tags" ]
239+ # Then we count the total number of *unique* Tags for this object, both implicit and explicit:
240+ other_tag_ids = set ()
241+ for field in ("tag_ids_str_1" , "tag_ids_str_2" , "tag_ids_str_3" , "tag_ids_str_4" ):
242+ if row [field ] is not None :
243+ for tag_id in row [field ].split ("," ):
244+ other_tag_ids .add (int (tag_id ))
245+ result [row ["object_id" ]] = num_free_text_tags + len (other_tag_ids )
246+ return result
247+ else :
248+ qs = qs .values ("object_id" ).annotate (num_tags = models .Count ("id" )).order_by ("object_id" )
249+ return {row ["object_id" ]: row ["num_tags" ] for row in qs }
222250
223251
224252def delete_object_tags (object_id : str ):
0 commit comments