def get_related_attrs_scan_count(cls, attr_names, queryset=None): """ Calculates the total count for a combination of related attributes (``attr_names``). Results might be optionally limited using ``queryset``. Returns a ``len(attr_names)``-dimensional dict, one dimension per attribute name. Each leaf is a tuple with the count of the particular combination of attributes. Example:: { <Tag: thriller>: { <Author: Stephen King>: 120 }, … }, … }, """ objects = queryset or cls.objects objects = objects.select_related(*attr_names) # get a list (of dicts) with all object combinations and their counts attr_combinations_values = objects.values(*attr_names).annotate(count=models.Count("pk")).order_by() # a list of object lists, needed to determine possible object # combinations attrs_objects = [set(getattrs(objects, attr_name)) for attr_name in attr_names] # needed to lookup counts for certain object combinations counts_by_pk = values_to_hierarchical_dict(attr_combinations_values, attr_names) # needed to calculate percentiles (that's why it's already float) objects_count = float(len(objects)) # if object combination does not exist, this information is provided: default_leaf = 0 # assemble a complete hierarchical dictionary of possible # combinations of attributes, including their count and percentile out = HierarchicalOrderedDict() for combination in product(*attrs_objects): count_dict = counts_by_pk.get_by_path((getattr(o, "pk", None) for o in combination), None) if count_dict: count = count_dict["count"] else: count = default_leaf out.set_by_path(combination, count) return out
def lookups(self, request, model_admin): """ Returns a list of tuples. The first element in each tuple will appear in the URL query (the PK of the related model). The second element is the human-readable name for the option that will appear in the right sidebar on the admin page (``str(obj)``). """ scans = model_admin.resource_class._meta.model.objects.only( "%s_id" % attr_name, attr_name ).distinct( ).select_related( attr_name ) components = set(getattrs(scans, attr_name)) return [(getattr(c, "pk", None), str(c)) for c in components]
def get_related_attr_scan_intervals(cls, attr_name, queryset=None): """ Creates a series of time deltas between scans, grouped by objects found under ``attr_name``. Results might be optionally limited using ``queryset``. Returns a dict with the related objects as keys and a list of tuples as value each. The tuples are filled with the timestamp and the ``timedelta`` to the previous scan. Example:: { <Tag: thriller>: [ (datetime(…), timedelta(…)), (datetime(…), timedelta(…)), ], … } """ objects = queryset or cls.objects objects = objects.only("timestamp", attr_name).select_related(attr_name).order_by("timestamp") # initialize with empty lists (avoids cumbersome checks below) attr_objects = objects.only(attr_name).distinct() all_series = {o: [] for o in getattrs(attr_objects, attr_name)} for scan in objects: timestamp = scan.timestamp attr_object = getattr(scan, attr_name) inner_series = all_series.get(attr_object) if inner_series: last_timestamp = inner_series[-1][0] delta = timestamp - last_timestamp else: delta = timedelta() inner_series.append((timestamp, delta)) return all_series