Implemented a modified and cleaned objectdb.search and accompanying object.manager.search_object that also searches globally. The default commands have not yet been converted to use the new call.

This commit is contained in:
Griatch 2013-05-11 20:01:19 +02:00
parent be22a31ec4
commit 218e4a149c
6 changed files with 131 additions and 118 deletions

View file

@ -1,19 +1,16 @@
"""
Custom manager for Objects.
"""
try: import cPickle as pickle
except ImportError: import pickle
from django.db.models import Q
from django.conf import settings
from django.db.models.fields import exceptions
from src.typeclasses.managers import TypedObjectManager
from src.typeclasses.managers import returns_typeclass, returns_typeclass_list
from src.utils import utils
from src.utils.utils import to_unicode, make_iter, string_partial_matching, to_str
from src.utils.utils import to_unicode, make_iter, string_partial_matching
__all__ = ("ObjectManager",)
_GA = object.__getattribute__
_DUMPS = lambda inp: to_unicode(pickle.dumps(inp))
# Try to use a custom way to parse id-tagged multimatches.
@ -120,21 +117,20 @@ class ObjectManager(TypedObjectManager):
return self.filter(cand_restriction & Q(objattribute__db_key=attribute_name))
@returns_typeclass_list
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None):
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None):
"""
Returns all objects having the valid
attrname set to the given value. Note that no conversion is made
to attribute_value, and so it can accept also non-strings. For this reason it does
not make sense to offer an "exact" type matching for this.
Returns all objects having the valid attrname set to the given value.
candidates - list of candidate objects to search
typeclasses - list of typeclass-path strings to restrict matches with
This uses the Attribute's PickledField to transparently search the database by matching
the internal representation. This is reasonably effective but since Attribute values
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()
if type(attribute_value) in (basestring, int, float):
# simple attribute_value - do direct lookup
return self.filter(cand_restriction & Q(objattribute__db_key=attribute_name, objattribute__db_value=_DUMPS(("simple", attribute_value))))
else:
# go via attribute conversion
attrs= self.model.objattribute_set.related.model.objects.select_related("db_obj").filter(cand_restriction & Q(db_key=attribute_name))
return [attr.db_obj for attr in attrs if attribute_value == attr.value]
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))
@returns_typeclass_list
def get_objs_with_db_property(self, property_name, candidates=None):
@ -151,16 +147,19 @@ class ObjectManager(TypedObjectManager):
return []
@returns_typeclass_list
def get_objs_with_db_property_value(self, property_name, property_value, candidates=None):
def get_objs_with_db_property_value(self, property_name, property_value, candidates=None, typeclasses=None):
"""
Returns all objects having a given db field property
Returns all objects having a given db field property.
candidates - list of objects to search
typeclasses - list of typeclass-path strings to restrict matches with
"""
if isinstance(property_value, basestring):
property_value = to_unicode(property_value)
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()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
try:
return self.filter(cand_restriction & Q(property_name=property_value))
return self.filter(cand_restriction & type_restriction & Q(property_name=property_value))
except exceptions.FieldError:
return []
@ -176,23 +175,26 @@ class ObjectManager(TypedObjectManager):
return self.filter(db_location=location).exclude(exclude_restriction)
@returns_typeclass_list
def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None):
def get_objs_with_key_or_alias(self, ostring, exact=True, candidates=None, typeclasses=None):
"""
Returns objects based on key or alias match. Will also do fuzzy matching based on
the utils.string_partial_matching function.
candidates - list of candidate objects to restrict on
typeclasses - list of typeclass path strings to restrict on
"""
# build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
cand_restriction = candidates and Q(pk__in=candidates_id) or Q()
cand_restriction = candidates and Q(pk__in=make_iter(candidates_id)) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
if exact:
# exact match - do direct search
return self.filter(cand_restriction & (Q(db_key__iexact=ostring) | Q(alias__db_key__iexact=ostring))).distinct()
return self.filter(cand_restriction & type_restriction & (Q(db_key__iexact=ostring) | Q(alias__db_key__iexact=ostring))).distinct()
elif candidates:
# fuzzy with candidates
key_candidates = self.filter(cand_restriction)
key_candidates = self.filter(cand_restriction & type_restriction)
else:
# fuzzy without supplied candidates - we select our own candidates
key_candidates = self.filter(Q(db_key__istartswith=ostring) | Q(alias__db_key__istartswith=ostring)).distinct()
key_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(alias__db_key__istartswith=ostring))).distinct()
candidates_id = [_GA(obj, "id") for obj in key_candidates]
# fuzzy matching
key_strings = key_candidates.values_list("db_key", flat=True)
@ -210,25 +212,25 @@ class ObjectManager(TypedObjectManager):
# main search methods and helper functions
@returns_typeclass_list
def object_search(self, ostring, caller=None,
def object_search(self, ostring=None,
attribute_name=None,
typeclass=None,
candidates=None,
exact=True):
"""
Search as an object and return results. The result is always an Object.
If * is appended (player search, a Character controlled by this Player
is looked for. The Character is returned, not the Player. Use player_search
to find Player objects. Always returns a list.
Search as an object globally or in a list of candidates and return results. The result is always an Object.
Always returns a list.
Arguments:
ostring: (string) The string to compare names against.
Can be a dbref. If name is prepended by *, a player is searched for.
caller: (Object) The optional object performing the search.
attribute_name: (string) Which object attribute to match ostring against. If not
set, the "key" and "aliases" properties are searched in order.
candidates (list obj ObjectDBs): If objlist is supplied, global_search keyword is ignored
and search will only be performed among the candidates in this list. A common list
of candidates is the contents of the current location searched.
ostring: (str) The string to compare names against. 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.
attribute_name: (str): Use this named ObjectAttribute to match ostring against, instead
of the defaults.
typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help
speed up global searches.
candidates (list obj ObjectDBs): If supplied, search will only be performed among the candidates
in this list. A common list of candidates is the contents of the current location searched.
exact (bool): Match names/aliases exactly or partially. Partial matching matches the
beginning of words in the names/aliases, using a matching routine to separate
multiple matches in names with multiple components (so "bi sw" will match
@ -240,41 +242,42 @@ class ObjectManager(TypedObjectManager):
A list of matching objects (or a list with one unique match)
"""
def _searcher(ostring, exact=False):
"Helper method for searching objects"
if attribute_name:
def _searcher(ostring, candidates, typeclass, exact=False):
"Helper method for searching objects. typeclass is only used for global searching (no candidates)"
if attribute_name and isinstance(attribute_name, basestring):
# attribute/property search (always exact).
matches = self.get_objs_with_db_property_value(attribute_name, ostring, candidates=candidates)
matches = self.get_objs_with_db_property_value(attribute_name, ostring, candidates=candidates, typeclasses=typeclass)
if matches:
return matches
return self.get_objs_with_attr_value(attribute_name, ostring, candidates=candidates)
if ostring.startswith("*"):
# Player search - try to find obj by its player's name
player_match = self.get_object_with_player(ostring, candidates=candidates)
if player_match is not None:
return [player_match]
return []
return self.get_objs_with_attr_value(attribute_name, ostring, candidates=candidates, typeclasses=typeclass)
else:
# normal key/alias search
return self.get_objs_with_key_or_alias(ostring, exact=exact, candidates=candidates)
return self.get_objs_with_key_or_alias(ostring, exact=exact, candidates=candidates, typeclasses=typeclass)
if not ostring and ostring != 0:
return []
# Convenience check to make sure candidates are really dbobjs
if typeclass:
# typeclass may also be a list
for i, typeclass in enumerate(make_iter(typeclass)):
if callable(typeclass):
typeclass[i] = u"%s.%s" % (typeclass.__module__, typeclass.__name__)
else:
typeclass[i] = u"%s" % typeclass
if candidates:
candidates = [cand.dbobj for cand in make_iter(candidates) if hasattr(cand, "dbobj")]
# Convenience check to make sure candidates are really dbobjs
candidates = [cand.dbobj for cand in make_iter(candidates) if _GA(cand, "_hasattr")(cand, "dbobj")]
if typeclass:
candidates = [cand for cand in candidates if _GA(cand, "db_typeclass_path") in typeclass]
# If candidates is given as an empty list, don't go any further.
if candidates == []:
return []
dbref = not attribute_name and self.dbref(ostring)
if dbref or dbref == 0:
dbref = not attribute_name and exact and self.dbref(ostring)
if dbref != None:
# Easiest case - dbref matching (always exact)
dbref_match = self.dbref_search(dbref)
if dbref_match:
if candidates == None or dbref_match.dbobj in candidates:
if not candidates or dbref_match.dbobj in candidates:
return [dbref_match]
else:
return []
@ -283,12 +286,12 @@ class ObjectManager(TypedObjectManager):
match_number = None
# always run first check exact - we don't want partial matches if on the form of 1-keyword etc.
matches = _searcher(ostring, exact=True)
matches = _searcher(ostring, candidates, typeclass, exact=True)
if not matches:
# no matches found - check if we are dealing with N-keyword query - if so, strip it.
match_number, ostring = _AT_MULTIMATCH_INPUT(ostring)
# run search again, with the exactness set by caller
matches = _searcher(ostring, exact=exact)
# run search again, with the exactness set by call
matches = _searcher(ostring, candidates, typeclass, exact=exact)
# deal with result
if len(matches) > 1 and match_number != None:

View file

@ -550,7 +550,6 @@ class ObjectDB(TypedObject):
typeclass=None,
location=None,
attribute_name=None,
attribute_value=None,
quiet=False,
exact=False):
"""
@ -559,26 +558,25 @@ class ObjectDB(TypedObject):
Perform a standard object search in the database, handling
multiple results and lack thereof gracefully. By default, only
objects in self's current location or inventory is searched.
Note: to find Players, use eg. ev.player_search.
Inputs:
ostring: (str) The string to match object.key against. Special strings:
*<string> - search only for objects of type settings.CHARACTER_DEFAULT
ostring (str): Primary search criterion. Will be matched against object.key (with object.aliases second)
unless the keyword attribute_name specifies otherwise. Special strings:
#<num> - search by unique dbref. This is always a global search.
me,self - self-reference to this object
<num>-<string> - can be used to differentiate between multiple same-named matches
global_search: Search all objects globally. This is overruled by "location" keyword.
use_nicks : Use nickname-replace (nick of type "object") on the search string
typeclass : Limit search only to Objects with this typeclass. Overrides the
use of the *-operator at the beginning of the string.
location : Specify a location to search, if different from the self's given location
global_search (bool): Search all objects globally. This is overruled by "location" keyword.
use_nicks (bool): Use nickname-replace (nicktype "object") on the search string
typeclass (str or Typeclass): Limit search only to Objects with this typeclass. May be a list of typeclasses
for a broader search.
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.
attribute_name: Match only objects also having Attributes with this name
attribute_value: Match only objects where obj.db.attribute_name also has this value
quiet - don't display default error messages - return multiple matches as a list and
attribute_name (str): Use this named Attribute to match ostring against, instead of object.key.
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.
exact - 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
at all. If set, requires exact mathing of entire string.
Returns:
@ -606,9 +604,6 @@ class ObjectDB(TypedObject):
if use_nicks:
nick = None
nicktype = "object"
if player or ostring.startswith('*'):
ostring = ostring.lstrip("*")
nicktype = "player"
# look up nicks
nicks = ObjectNick.objects.filter(db_obj=self, db_type=nicktype)
if self.has_player:
@ -619,8 +614,8 @@ class ObjectDB(TypedObject):
break
candidates=None
if global_search or (global_dbref and ostring.startswith("#")):
# only allow exact matching if searching the entire database
if global_search or (ostring.startswith("#") and len(ostring) > 1 and ostring[1:].isdigit()):
# only allow exact matching if searching the entire database or unique #dbrefs
exact = True
elif location:
# location(s) were given
@ -638,16 +633,14 @@ class ObjectDB(TypedObject):
# db manager expects database objects
candidates = [obj.dbobj for obj in candidates]
results = ObjectDB.objects.object_search(ostring, caller=self,
results = ObjectDB.objects.object_search(ostring=ostring,
typeclass=typeclass,
attribute_name=attribute_name,
candidates=candidates,
exact=exact)
if ignore_errors:
if quiet:
return results
result = _AT_SEARCH_RESULT(self, ostring, results, global_search)
if player and result:
return result.player
return result
return _AT_SEARCH_RESULT(self, ostring, results, global_search)
#