[fix] Correct search_typeclass bugs. Resolve #2694.

This commit is contained in:
Griatch 2022-10-13 16:41:00 +02:00
parent c7a2b8b37b
commit e5b698fab8
2 changed files with 95 additions and 31 deletions

View file

@ -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):

View file

@ -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")