Fixed a search feature that was not included in the revision of the object search mechanism - the ability to search based on an object. Resolves Issue 363.

This commit is contained in:
Griatch 2013-05-14 21:17:29 +02:00
parent a533232885
commit e15d6dfb6e
2 changed files with 53 additions and 35 deletions

View file

@ -12,6 +12,10 @@ from src.utils.utils import to_unicode, make_iter, string_partial_matching
__all__ = ("ObjectManager",) __all__ = ("ObjectManager",)
_GA = object.__getattribute__ _GA = object.__getattribute__
# delayed import
_OBJATTR = None
# Try to use a custom way to parse id-tagged multimatches. # Try to use a custom way to parse id-tagged multimatches.
_AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1)) _AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
@ -114,7 +118,7 @@ class ObjectManager(TypedObjectManager):
should be a valid location object. should be a valid location object.
""" """
cand_restriction = candidates and Q(objattribute__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() cand_restriction = candidates and Q(objattribute__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
return self.filter(cand_restriction & Q(objattribute__db_key=attribute_name)) return list(self.filter(cand_restriction & Q(objattribute__db_key=attribute_name)))
@returns_typeclass_list @returns_typeclass_list
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None): def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None):
@ -128,9 +132,17 @@ class ObjectManager(TypedObjectManager):
the internal representation. This is reasonably effective but since Attribute values the internal representation. This is reasonably effective but since Attribute values
cannot be indexed, searching by Attribute key is to be preferred whenever possible. cannot be indexed, searching by Attribute key is to be preferred whenever possible.
""" """
cand_restriction = candidates and Q(db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() cand_restriction = candidates and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
return self.filter(cand_restriction & type_restriction & Q(objattribute__db_key=attribute_name, objattribute__db_value=attribute_value)) if isinstance(attribute_value, (basestring, int, float, bool, long)):
return self.filter(cand_restriction & type_restriction & Q(objattribute__db_key=attribute_name, objattribute__db_value=attribute_value))
else:
# We have to loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
global _OBJATTR
if not _OBJATTR:
from src.objects.models import ObjAttribute as _OBJATTR
cands = list(self.filter(cand_restriction & type_restriction & Q(objattribute__db_key=attribute_name)))
return [_GA(attr, "db_obj") for attr in _OBJATTR.objects.filter(db_obj__in=cands, db_value=attribute_value)]
@returns_typeclass_list @returns_typeclass_list
def get_objs_with_db_property(self, property_name, candidates=None): def get_objs_with_db_property(self, property_name, candidates=None):
@ -142,7 +154,7 @@ class ObjectManager(TypedObjectManager):
property_name = "db_%s" % property_name.lstrip('db_') property_name = "db_%s" % property_name.lstrip('db_')
cand_restriction = candidates and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() cand_restriction = candidates and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
try: try:
return self.filter(cand_restriction).exclude(Q(property_name=None)) return list(self.filter(cand_restriction).exclude(Q(property_name=None)))
except exceptions.FieldError: except exceptions.FieldError:
return [] return []
@ -159,7 +171,7 @@ class ObjectManager(TypedObjectManager):
cand_restriction = candidates and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q() cand_restriction = candidates and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
try: try:
return self.filter(cand_restriction & type_restriction & Q(property_name=property_value)) return list(self.filter(cand_restriction & type_restriction & Q(property_name=property_value)))
except exceptions.FieldError: except exceptions.FieldError:
return [] return []
@ -182,6 +194,12 @@ class ObjectManager(TypedObjectManager):
candidates - list of candidate objects to restrict on candidates - list of candidate objects to restrict on
typeclasses - list of typeclass path strings to restrict on typeclasses - list of typeclass path strings to restrict on
""" """
if not isinstance(ostring, basestring):
if hasattr(ostring, "key"):
ostring = ostring.key
else:
return []
# build query objects # build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
cand_restriction = candidates and Q(pk__in=make_iter(candidates_id)) or Q() cand_restriction = candidates and Q(pk__in=make_iter(candidates_id)) or Q()
@ -212,7 +230,7 @@ class ObjectManager(TypedObjectManager):
# main search methods and helper functions # main search methods and helper functions
@returns_typeclass_list @returns_typeclass_list
def object_search(self, ostring, def object_search(self, searchdata,
attribute_name=None, attribute_name=None,
typeclass=None, typeclass=None,
candidates=None, candidates=None,
@ -222,10 +240,10 @@ class ObjectManager(TypedObjectManager):
Always returns a list. Always returns a list.
Arguments: Arguments:
ostring: (str) The string to compare names against. By default (if not attribute_name searchdata: (str or obj) The entity to match for. This is usually a key string but may also be an object itself.
is set), this will search object.key and object.aliases in order. Can also By default (if not attribute_name is set), this will search object.key and object.aliases in order. Can also
be on the form #dbref, which will, if exact=True be matched against primary key. be on the form #dbref, which will, if exact=True be matched against primary key.
attribute_name: (str): Use this named ObjectAttribute to match ostring against, instead attribute_name: (str): Use this named ObjectAttribute to match searchdata against, instead
of the defaults. of the defaults.
typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help
speed up global searches. speed up global searches.
@ -242,20 +260,19 @@ class ObjectManager(TypedObjectManager):
A list of matching objects (or a list with one unique match) A list of matching objects (or a list with one unique match)
""" """
def _searcher(ostring, candidates, typeclass, exact=False): def _searcher(searchdata, candidates, typeclass, exact=False):
"Helper method for searching objects. typeclass is only used for global searching (no candidates)" "Helper method for searching objects. typeclass is only used for global searching (no candidates)"
if attribute_name and isinstance(attribute_name, basestring): if attribute_name:
# attribute/property search (always exact). # attribute/property search (always exact).
matches = self.get_objs_with_db_property_value(attribute_name, ostring, candidates=candidates, typeclasses=typeclass) matches = self.get_objs_with_db_property_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
if matches: if matches:
return matches return matches
return self.get_objs_with_attr_value(attribute_name, ostring, candidates=candidates, typeclasses=typeclass) return self.get_objs_with_attr_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
else: else:
# normal key/alias search # normal key/alias search
return self.get_objs_with_key_or_alias(ostring, exact=exact, candidates=candidates, typeclasses=typeclass) return self.get_objs_with_key_or_alias(searchdata, exact=exact, candidates=candidates, typeclasses=typeclass)
if not searchdata and searchdata != 0:
if not ostring and ostring != 0:
return [] return []
if typeclass: if typeclass:
@ -272,7 +289,7 @@ class ObjectManager(TypedObjectManager):
if typeclass: if typeclass:
candidates = [cand for cand in candidates if _GA(cand, "db_typeclass_path") in typeclass] candidates = [cand for cand in candidates if _GA(cand, "db_typeclass_path") in typeclass]
dbref = not attribute_name and exact and self.dbref(ostring) dbref = not attribute_name and exact and self.dbref(searchdata)
if dbref != None: if dbref != None:
# Easiest case - dbref matching (always exact) # Easiest case - dbref matching (always exact)
dbref_match = self.dbref_search(dbref) dbref_match = self.dbref_search(dbref)
@ -283,15 +300,14 @@ class ObjectManager(TypedObjectManager):
return [] return []
# Search through all possibilities. # Search through all possibilities.
match_number = None match_number = None
# always run first check exact - we don't want partial matches if on the form of 1-keyword etc. # always run first check exact - we don't want partial matches if on the form of 1-keyword etc.
matches = _searcher(ostring, candidates, typeclass, exact=True) matches = _searcher(searchdata, candidates, typeclass, exact=True)
if not matches: if not matches:
# no matches found - check if we are dealing with N-keyword query - if so, strip it. # no matches found - check if we are dealing with N-keyword query - if so, strip it.
match_number, ostring = _AT_MULTIMATCH_INPUT(ostring) match_number, searchdata = _AT_MULTIMATCH_INPUT(searchdata)
# run search again, with the exactness set by call # run search again, with the exactness set by call
matches = _searcher(ostring, candidates, typeclass, exact=exact) matches = _searcher(searchdata, candidates, typeclass, exact=exact)
# deal with result # deal with result
if len(matches) > 1 and match_number != None: if len(matches) > 1 and match_number != None:

View file

@ -546,7 +546,7 @@ class ObjectDB(TypedObject):
# Main Search method # Main Search method
# #
def search(self, ostring, def search(self, searchdata,
global_search=False, global_search=False,
use_nicks=False, use_nicks=False,
typeclass=None, typeclass=None,
@ -564,7 +564,7 @@ class ObjectDB(TypedObject):
Inputs: Inputs:
ostring (str): Primary search criterion. Will be matched against object.key (with object.aliases second) searchdata (str or obj): Primary search criterion. Will be matched against object.key (with object.aliases second)
unless the keyword attribute_name specifies otherwise. Special strings: unless the keyword attribute_name specifies otherwise. Special strings:
#<num> - search by unique dbref. This is always a global search. #<num> - search by unique dbref. This is always a global search.
me,self - self-reference to this object me,self - self-reference to this object
@ -575,7 +575,7 @@ class ObjectDB(TypedObject):
be a list of typeclasses for a broader search. be a list of typeclasses for a broader search.
location (Object): Specify a location to search, if different from the self's given location location (Object): Specify a location to search, if different from the self's given location
plus its contents. This can also be a list of locations. plus its contents. This can also be a list of locations.
attribute_name (str): Use this named Attribute to match ostring against, instead of object.key. attribute_name (str): Use this named Attribute to match searchdata against, instead of object.key.
quiet (bool) - don't display default error messages - return multiple matches as a list and quiet (bool) - don't display default error messages - return multiple matches as a list and
no matches as None. If not set (default), will echo error messages and return None. no matches as None. If not set (default), will echo error messages and return None.
exact (bool) - if unset (default) - prefers to match to beginning of string rather than not matching exact (bool) - if unset (default) - prefers to match to beginning of string rather than not matching
@ -597,10 +597,12 @@ class ObjectDB(TypedObject):
a unique object match a unique object match
""" """
is_string = isinstance(searchdata, basestring)
# handle some common self-references: # handle some common self-references:
if ostring == _HERE: if searchdata == _HERE:
return self.location return self.location
if ostring in (_ME, _SELF): if searchdata in (_ME, _SELF):
return self return self
if use_nicks: if use_nicks:
@ -611,12 +613,12 @@ class ObjectDB(TypedObject):
if self.has_player: if self.has_player:
nicks = list(nicks) + list(PlayerNick.objects.filter(db_obj=self.db_player, db_type=nicktype)) nicks = list(nicks) + list(PlayerNick.objects.filter(db_obj=self.db_player, db_type=nicktype))
for nick in nicks: for nick in nicks:
if ostring == nick.db_nick: if searchdata == nick.db_nick:
ostring = nick.db_real searchdata = nick.db_real
break break
candidates=None candidates=None
if global_search or (ostring.startswith("#") and len(ostring) > 1 and ostring[1:].isdigit()): if global_search or (is_string and searchdata.startswith("#") and len(searchdata) > 1 and searchdata[1:].isdigit()):
# only allow exact matching if searching the entire database or unique #dbrefs # only allow exact matching if searching the entire database or unique #dbrefs
exact = True exact = True
elif location: elif location:
@ -635,25 +637,25 @@ class ObjectDB(TypedObject):
# db manager expects database objects # db manager expects database objects
candidates = [obj.dbobj for obj in candidates] candidates = [obj.dbobj for obj in candidates]
results = ObjectDB.objects.object_search(ostring, results = ObjectDB.objects.object_search(searchdata,
attribute_name=attribute_name, attribute_name=attribute_name,
typeclass=typeclass, typeclass=typeclass,
candidates=candidates, candidates=candidates,
exact=exact) exact=exact)
if quiet: if quiet:
return results return results
return _AT_SEARCH_RESULT(self, ostring, results, global_search) return _AT_SEARCH_RESULT(self, searchdata, results, global_search)
def search_player(self, ostring, quiet=False): def search_player(self, searchdata, quiet=False):
""" """
Simple wrapper of the player search also handling me, self Simple wrapper of the player search also handling me, self
""" """
if ostring in (_ME, _SELF) and _GA(self, "db_player"): if searchdata in (_ME, _SELF) and _GA(self, "db_player"):
return _GA(self, "db_player") return _GA(self, "db_player")
results = PlayerDB.objects.player_search(ostring) results = PlayerDB.objects.player_search(searchdata)
if quiet: if quiet:
return results return results
return _AT_SEARCH_RESULT(self, ostring, results, True) return _AT_SEARCH_RESULT(self, searchdata, results, True)
# #
# Execution/action methods # Execution/action methods