From b848d321f91fa3b445f4c5ee6a0f4b753c74af27 Mon Sep 17 00:00:00 2001 From: Johnny Date: Tue, 14 Jan 2020 22:05:02 +0000 Subject: [PATCH] Refactors object_totals to crunch more of the data db-side instead of app-side. --- evennia/commands/default/system.py | 6 ++--- evennia/typeclasses/managers.py | 38 ++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index 9084117ea0..00125388c7 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -606,9 +606,9 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): "|wtypeclass|n", "|wcount|n", "|w%|n", border="table", align="l" ) typetable.align = "l" - dbtotals = ObjectDB.objects.object_totals() - for path, count in dbtotals.items(): - typetable.add_row(path, count, "%.2f" % ((float(count) / nobjs) * 100)) + dbtotals = ObjectDB.objects.get_typeclass_totals() + for stat in dbtotals: + typetable.add_row(stat.get('typeclass', ''), stat.get('count', -1), "%.2f" % stat.get('percent', -1)) # last N table objs = ObjectDB.objects.all().order_by("db_date_created")[ diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 5b7ffe362f..0877b14167 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -5,7 +5,8 @@ all Attributes and TypedObjects). """ import shlex -from django.db.models import Q, Count +from django.db.models import F, Q, Count, ExpressionWrapper, FloatField +from django.db.models.functions import Cast from evennia.utils import idmapper from evennia.utils.utils import make_iter, variable_from_module from evennia.typeclasses.attributes import Attribute @@ -265,7 +266,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): tagtype (str, optional): 'type' of Tag, by default this is either `None` (a normal Tag), `alias` or `permission`. This always apply to all queried tags. - + Kwargs: match (str): ALL or ANY, determines whether the target object must match ALL of the provided tags/categories or ANY single one. ANY will perform @@ -499,7 +500,29 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): if max_dbref is not None: retval = retval.filter(id__lte=self.dbref(max_dbref, reqhash=False)) return retval - + + def get_typeclass_totals(self, *args, **kwargs) -> object: + """ + Returns a queryset of typeclass composition statistics. + + Returns: + qs (Queryset): A queryset of dicts containing the typeclass (name), + the count of objects with that typeclass and a float representing + the percentage of objects associated with the typeclass. + + """ + return self.values('db_typeclass_path').distinct().annotate( + # Get count of how many objects for each typeclass exist + count=Count('db_typeclass_path') + ).annotate( + # Rename db_typeclass_path field to something more human + typeclass=F('db_typeclass_path'), + # Calculate this class' percentage of total composition + percent=ExpressionWrapper( + ((F('count') / float(self.count())) * 100.0), output_field=FloatField() + ), + ).values('typeclass', 'count', 'percent') + def object_totals(self): """ Get info about database statistics. @@ -511,13 +534,8 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): object having that typeclass set on themselves). """ - dbtotals = {} - typeclass_paths = set(self.values_list("db_typeclass_path", flat=True)) - for typeclass_path in typeclass_paths: - dbtotals[typeclass_path] = self.filter( - db_typeclass_path=typeclass_path - ).count() - return dbtotals + stats = self.get_typeclass_totals().order_by('typeclass') + return {x.get('typeclass'):x.get('count') for x in stats} def typeclass_search( self, typeclass, include_children=False, include_parents=False