From b5d148b00a604c02e9f3622b5c0ffffb29649c6b Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 27 Jul 2020 20:44:14 +0200 Subject: [PATCH] Optimize/fix searching objects by-attribute-value --- evennia/objects/manager.py | 33 +++++++++------------------------ evennia/utils/picklefield.py | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 3e609991ff..a3db831149 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -154,7 +154,7 @@ class ObjectDBManager(TypedObjectManager): Args: attribute_name (str): Attribute key to search for. - attribute_value (str): Attribute value to search for. + attribute_value (any): Attribute value to search for. This can also be database objects. candidates (list, optional): Candidate objects to limit search to. typeclasses (list, optional): Python pats to restrict matches with. @@ -175,31 +175,16 @@ class ObjectDBManager(TypedObjectManager): ) type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() - # This doesn't work if attribute_value is an object. Workaround below - - if isinstance(attribute_value, (str, int, float, bool)): - return self.filter( + results = ( + self + .filter( cand_restriction & type_restriction - & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value) - ).order_by("id") - else: - # We must loop for safety since the referenced lookup gives deepcopy error if attribute value is an object. - global _ATTR - if not _ATTR: - from evennia.typeclasses.models import Attribute as _ATTR - cands = list( - self.filter( - cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name) - ) - ) - results = [ - attr.objectdb_set.all() - for attr in _ATTR.objects.filter( - objectdb__in=cands, db_value=attribute_value - ).order_by("id") - ] - return chain(*results) + & Q(db_attributes__db_key=attribute_name) + & Q(db_attributes__db_value=attribute_value)) + .order_by("id") + ) + return results def get_objs_with_db_property(self, property_name, candidates=None): """ diff --git a/evennia/utils/picklefield.py b/evennia/utils/picklefield.py index 084fc6d638..2e62298ca5 100644 --- a/evennia/utils/picklefield.py +++ b/evennia/utils/picklefield.py @@ -31,7 +31,7 @@ Modified for Evennia by Griatch and the Evennia community. from ast import literal_eval from datetime import datetime -from copy import deepcopy +from copy import deepcopy, Error as CopyError from base64 import b64encode, b64decode from zlib import compress, decompress @@ -44,6 +44,7 @@ from django.forms.widgets import Textarea from pickle import loads, dumps from django.utils.encoding import force_str +from evennia.utils.dbserialize import pack_dbobj DEFAULT_PROTOCOL = 4 @@ -92,7 +93,16 @@ def dbsafe_encode(value, compress_object=False, pickle_protocol=DEFAULT_PROTOCOL # The reason this is important is because we do all of our lookups as # simple string matches, thus the character streams must be the same # for the lookups to work properly. See tests.py for more information. - value = dumps(deepcopy(value), protocol=pickle_protocol) + try: + value = deepcopy(value) + except CopyError: + # this can happen on a manager query where the search query string is a + # database model. + value = pack_dbobj(value) + + value = dumps(value, protocol=pickle_protocol) + + if compress_object: value = compress(value) value = b64encode(value).decode() # decode bytes to str