From e5b698fab8c6e32df5cd7edee317e17298fecad3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 13 Oct 2022 16:41:00 +0200 Subject: [PATCH] [fix] Correct search_typeclass bugs. Resolve #2694. --- evennia/typeclasses/managers.py | 48 ++++++++------------ evennia/typeclasses/tests.py | 78 ++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 31 deletions(-) diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index e5d1130f12..9429c2034f 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -5,12 +5,13 @@ all Attributes and TypedObjects). """ import shlex -from django.db.models import F, Q, Count, ExpressionWrapper, FloatField + +from django.db.models import Count, ExpressionWrapper, F, FloatField, Q 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 from evennia.typeclasses.tags import Tag +from evennia.utils import idmapper +from evennia.utils.utils import class_from_module, make_iter, variable_from_module __all__ = ("TypedObjectManager",) _GA = object.__getattribute__ @@ -537,9 +538,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): def typeclass_search(self, typeclass, include_children=False, include_parents=False): """ - Searches through all objects returning those which has a - certain typeclass. If location is set, limit search to objects - in that location. + Searches through all objects returning those which has a certain typeclass. Args: typeclass (str or class): A typeclass class or a python path to a typeclass. @@ -554,34 +553,23 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): objects (list): The objects found with the given typeclasses. """ - - if callable(typeclass): - cls = typeclass.__class__ - typeclass = "%s.%s" % (cls.__module__, cls.__name__) - elif not isinstance(typeclass, str) and hasattr(typeclass, "path"): - typeclass = typeclass.path - - # query objects of exact typeclass - query = Q(db_typeclass_path__exact=typeclass) + if not callable(typeclass): + typeclass = class_from_module(typeclass) if include_children: - # build requests for child typeclass objects - clsmodule, clsname = typeclass.rsplit(".", 1) - cls = variable_from_module(clsmodule, clsname) - subclasses = cls.__subclasses__() - if subclasses: - for child in (child for child in subclasses if hasattr(child, "path")): - query = query | Q(db_typeclass_path__exact=child.path) - elif include_parents: - # build requests for parent typeclass objects - clsmodule, clsname = typeclass.rsplit(".", 1) - cls = variable_from_module(clsmodule, clsname) - parents = cls.__mro__ + query = typeclass.objects.all_family() + else: + query = typeclass.objects.all() + + if include_parents: + parents = typeclass.__mro__ if parents: + parent_queries = [] for parent in (parent for parent in parents if hasattr(parent, "path")): - query = query | Q(db_typeclass_path__exact=parent.path) - # actually query the database - return super().filter(query) + parent_queries.append(super().filter(db_typeclass_path__exact=parent.path)) + query = query.union(*parent_queries) + + return query class TypeclassManager(TypedObjectManager): diff --git a/evennia/typeclasses/tests.py b/evennia/typeclasses/tests.py index 41ddaa41d5..c28112f128 100644 --- a/evennia/typeclasses/tests.py +++ b/evennia/typeclasses/tests.py @@ -4,7 +4,7 @@ Unit tests for typeclass base system """ from django.test import override_settings -from evennia.typeclasses import attributes +from evennia.objects.objects import DefaultObject from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTestCase from mock import patch from parameterized import parameterized @@ -213,6 +213,82 @@ class TestTypedObjectManager(BaseEvenniaTest): self.assertEqual(tagobj.db_data, "data4") +# setting up testing typeclass with child- and parent class +class TestSearchManagerTypeclassParent(DefaultObject): + pass + + +class TestSearchManagerTypeclass(TestSearchManagerTypeclassParent): + pass + + +class TestSearchManagerTypeclassChild(TestSearchManagerTypeclass): + pass + + +class TestSearchTypeclassFamily(EvenniaTestCase): + """ + Test the manager method for searching for inheriting typeclasses. + + """ + + def setUp(self): + self.obj_parent, _ = TestSearchManagerTypeclassParent.create(key="obj_parent") + self.obj1, _ = TestSearchManagerTypeclass.create(key="obj1") + self.obj2, _ = TestSearchManagerTypeclass.create(key="obj2") + self.obj_child, _ = TestSearchManagerTypeclassChild.create(key="obj_child") + + def test_typeclass_search__inputs(self): + """Test basic functionality""" + + res1 = self.obj1.__class__.objects.typeclass_search(self.obj1.__class__) + res2 = self.obj1.__class__.objects.typeclass_search( + "evennia.typeclasses.tests.TestSearchManagerTypeclass" + ) + self.assertEqual(list(res1), [self.obj1, self.obj2]) + self.assertEqual(list(res2), [self.obj1, self.obj2]) + + def test_typeclass_search__children_and_parents(self): + """Test getting parents/child classes""" + + # just the objects of this typeclass + res1 = self.obj1.__class__.objects.typeclass_search(self.obj1.__class__) + res2 = self.obj2.__class__.objects.typeclass_search(self.obj2.__class__) + + # these objects + children + res3 = self.obj1.__class__.objects.typeclass_search( + self.obj1.__class__, include_children=True + ) + # these objects + parents + res4 = self.obj1.__class__.objects.typeclass_search( + self.obj1.__class__, include_parents=True + ) + # these objects + parents + children + res5 = self.obj1.__class__.objects.typeclass_search( + self.obj1.__class__, include_children=True, include_parents=True + ) + + self.assertEqual(set(res1), {self.obj1, self.obj2}) + self.assertEqual(set(res2), {self.obj1, self.obj2}) + self.assertEqual(set(res3), {self.obj1, self.obj2, self.obj_child}) + self.assertEqual(set(res4), {self.obj1, self.obj2, self.obj_parent}) + self.assertEqual(set(res5), {self.obj1, self.obj2, self.obj_child, self.obj_parent}) + + def test_typeclass_search__nested(self): + """Test several levels deep searches""" + # check all children of the parent + res1 = self.obj1.__class__.objects.typeclass_search( + self.obj_parent.__class__, include_children=True + ) + # check all parents of the child + res2 = self.obj1.__class__.objects.typeclass_search( + self.obj_child.__class__, include_parents=True + ) + + self.assertEqual(set(res1), {self.obj_parent, self.obj1, self.obj2, self.obj_child}) + self.assertEqual(set(res2), {self.obj_parent, self.obj1, self.obj2, self.obj_child}) + + class TestTags(BaseEvenniaTest): def test_has_tag_key_only(self): self.obj1.tags.add("tagC")