From fe99edd6112c3c2e08e757a323d18f612f3c25b3 Mon Sep 17 00:00:00 2001 From: Johnny Date: Wed, 4 Dec 2019 00:42:08 +0000 Subject: [PATCH 1/2] Modifies filter chain to use Q objects. --- evennia/typeclasses/managers.py | 38 ++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index abaf389a1d..dbec3ae547 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -5,7 +5,7 @@ all Attributes and TypedObjects). """ import shlex -from django.db.models import Q +from django.db.models import Q, Count from evennia.utils import idmapper from evennia.utils.utils import make_iter, variable_from_module from evennia.typeclasses.attributes import Attribute @@ -236,7 +236,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ return self.get_tag(key=key, category=category, obj=obj, tagtype="alias") - def get_by_tag(self, key=None, category=None, tagtype=None): + def get_by_tag(self, key=None, category=None, tagtype=None, **kwargs): """ Return objects having tags with a given key or category or combination of the two. Also accepts multiple tags/category/tagtype @@ -250,7 +250,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. - + Returns: objects (list): Objects with matching tag. @@ -261,6 +261,10 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ if not (key or category): return [] + + global _Tag + if not _Tag: + from evennia.typeclasses.models import Tag as _Tag keys = make_iter(key) if key else [] categories = make_iter(category) if category else [] @@ -271,7 +275,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): query = ( self.filter(db_tags__db_tagtype__iexact=tagtype, db_tags__db_model__iexact=dbmodel) .distinct() - .order_by("id") + #.order_by("id") ) if n_keys > 0: @@ -286,16 +290,30 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): "get_by_tag needs a single category or a list of categories " "the same length as the list of tags." ) + + clauses = Q() for ikey, key in enumerate(keys): - query = query.filter( - db_tags__db_key__iexact=key, db_tags__db_category__iexact=categories[ikey] - ) + # Keep each key and category together, grouped by AND + clause = Q(db_key__iexact=key, db_category__iexact=categories[ikey]) + clauses |= clause + + query = query.filter(db_tags__in=_Tag.objects.filter(clauses)).annotate(num_tags=Count('db_tags', distinct=True)).filter(num_tags__gte=len(keys)) + + print(keys, query.query) + else: # only one or more categories given - for category in categories: - query = query.filter(db_tags__db_category__iexact=category) + clauses = Q() + uniq_categories = sorted(set(categories)) + for category in uniq_categories: + clause = Q(db_category__iexact=category,) + + # Join all discrete clauses with an OR + clauses |= clause + + query = query.filter(db_tags__in=_Tag.objects.filter(clauses)).annotate(num_tags=Count('db_tags__db_category', distinct=True)).filter(num_tags__gte=len(uniq_categories)) - return query + return query.order_by('-num_tags') def get_by_permission(self, key=None, category=None): """ From 7e3c693b297eaaf152ca042073a99428ed2c919b Mon Sep 17 00:00:00 2001 From: Johnny Date: Wed, 4 Dec 2019 03:15:35 +0000 Subject: [PATCH 2/2] Refactors get_by_tag to allow weighted searches. --- evennia/typeclasses/managers.py | 41 +++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index dbec3ae547..87ef05a3ea 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -251,6 +251,12 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): 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 + a weighted sort, so objects with more tag matches will outrank those + with fewer tag matches. + Returns: objects (list): Objects with matching tag. @@ -261,10 +267,12 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): """ if not (key or category): return [] - + global _Tag if not _Tag: from evennia.typeclasses.models import Tag as _Tag + + match = kwargs.get('match', 'all').lower().strip() keys = make_iter(key) if key else [] categories = make_iter(category) if category else [] @@ -275,7 +283,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): query = ( self.filter(db_tags__db_tagtype__iexact=tagtype, db_tags__db_model__iexact=dbmodel) .distinct() - #.order_by("id") + .order_by("id") ) if n_keys > 0: @@ -290,30 +298,29 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): "get_by_tag needs a single category or a list of categories " "the same length as the list of tags." ) - clauses = Q() for ikey, key in enumerate(keys): # Keep each key and category together, grouped by AND - clause = Q(db_key__iexact=key, db_category__iexact=categories[ikey]) - clauses |= clause - - query = query.filter(db_tags__in=_Tag.objects.filter(clauses)).annotate(num_tags=Count('db_tags', distinct=True)).filter(num_tags__gte=len(keys)) - - print(keys, query.query) - + clauses |= Q(db_key__iexact=key, db_category__iexact=categories[ikey]) + else: # only one or more categories given clauses = Q() uniq_categories = sorted(set(categories)) for category in uniq_categories: - clause = Q(db_category__iexact=category,) - - # Join all discrete clauses with an OR - clauses |= clause - - query = query.filter(db_tags__in=_Tag.objects.filter(clauses)).annotate(num_tags=Count('db_tags__db_category', distinct=True)).filter(num_tags__gte=len(uniq_categories)) + clauses |= Q(db_category__iexact=category,) - return query.order_by('-num_tags') + tags = _Tag.objects.filter(clauses) + query = query.filter(db_tags__in=tags).annotate(matches=Count('db_tags__pk', filter=Q(db_tags__in=tags), distinct=True)) + + # Default ALL: Match all of the tags and optionally more + if match == 'all': + query = query.filter(matches__gte=tags.count()) + # ANY: Match any single tag, ordered by weight + elif match == 'any': + query = query.order_by('-matches') + + return query def get_by_permission(self, key=None, category=None): """