[fix] Correct search_typeclass bugs. Resolve #2694.
This commit is contained in:
parent
c7a2b8b37b
commit
e5b698fab8
2 changed files with 95 additions and 31 deletions
|
|
@ -5,12 +5,13 @@ all Attributes and TypedObjects).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import shlex
|
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 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.attributes import Attribute
|
||||||
from evennia.typeclasses.tags import Tag
|
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",)
|
__all__ = ("TypedObjectManager",)
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
|
|
@ -537,9 +538,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
||||||
|
|
||||||
def typeclass_search(self, typeclass, include_children=False, include_parents=False):
|
def typeclass_search(self, typeclass, include_children=False, include_parents=False):
|
||||||
"""
|
"""
|
||||||
Searches through all objects returning those which has a
|
Searches through all objects returning those which has a certain typeclass.
|
||||||
certain typeclass. If location is set, limit search to objects
|
|
||||||
in that location.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
typeclass (str or class): A typeclass class or a python path to a typeclass.
|
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.
|
objects (list): The objects found with the given typeclasses.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not callable(typeclass):
|
||||||
if callable(typeclass):
|
typeclass = class_from_module(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 include_children:
|
if include_children:
|
||||||
# build requests for child typeclass objects
|
query = typeclass.objects.all_family()
|
||||||
clsmodule, clsname = typeclass.rsplit(".", 1)
|
else:
|
||||||
cls = variable_from_module(clsmodule, clsname)
|
query = typeclass.objects.all()
|
||||||
subclasses = cls.__subclasses__()
|
|
||||||
if subclasses:
|
if include_parents:
|
||||||
for child in (child for child in subclasses if hasattr(child, "path")):
|
parents = typeclass.__mro__
|
||||||
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__
|
|
||||||
if parents:
|
if parents:
|
||||||
|
parent_queries = []
|
||||||
for parent in (parent for parent in parents if hasattr(parent, "path")):
|
for parent in (parent for parent in parents if hasattr(parent, "path")):
|
||||||
query = query | Q(db_typeclass_path__exact=parent.path)
|
parent_queries.append(super().filter(db_typeclass_path__exact=parent.path))
|
||||||
# actually query the database
|
query = query.union(*parent_queries)
|
||||||
return super().filter(query)
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
class TypeclassManager(TypedObjectManager):
|
class TypeclassManager(TypedObjectManager):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Unit tests for typeclass base system
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.test import override_settings
|
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 evennia.utils.test_resources import BaseEvenniaTest, EvenniaTestCase
|
||||||
from mock import patch
|
from mock import patch
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
|
|
@ -213,6 +213,82 @@ class TestTypedObjectManager(BaseEvenniaTest):
|
||||||
self.assertEqual(tagobj.db_data, "data4")
|
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):
|
class TestTags(BaseEvenniaTest):
|
||||||
def test_has_tag_key_only(self):
|
def test_has_tag_key_only(self):
|
||||||
self.obj1.tags.add("tagC")
|
self.obj1.tags.add("tagC")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue