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

@ -10,7 +10,7 @@ from django.contrib.auth.models import User
from src.players.models import PlayerDB
from src.server.sessionhandler import SESSIONS
from src.server.models import ServerConfig
from src.utils import utils, prettytable
from src.utils import utils, prettytable, search
from src.commands.default.muxcommand import MuxCommand
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
@ -65,24 +65,22 @@ class CmdBoot(MuxCommand):
break
else:
# Boot by player object
pobj = caller.search("*%s" % args.lstrip('*'), global_search=True, player=True)
pobj = search.player_search(args)
if not pobj:
self.caller("Player %s was not found." % pobj.key)
return
if pobj.character.has_player:
if not pobj.access(caller, 'boot'):
string = "You don't have the permission to boot %s."
pobj.msg(string)
return
# we have a bootable object with a connected user
matches = SESSIONS.sessions_from_player(pobj)
for match in matches:
boot_list.append(match)
else:
caller.msg("That object has no connected player.")
pobj = pobj[0]
if not pobj.access(caller, 'boot'):
string = "You don't have the permission to boot %s."
pobj.msg(string)
return
# we have a bootable object with a connected user
matches = SESSIONS.sessions_from_player(pobj)
for match in matches:
boot_list.append(match)
if not boot_list:
caller.msg("No matches found.")
caller.msg("No matching sessions found. The Player does not seem to be online.")
return
# Carry out the booting of the sessions in the boot list.
@ -96,8 +94,7 @@ class CmdBoot(MuxCommand):
for session in boot_list:
name = session.uname
session.msg(feedback)
session.disconnect()
caller.msg("You booted %s." % name)
pobj.disconnect_session_from_player(session.sessid)
# regex matching IP addresses with wildcards, eg. 233.122.4.*

View file

@ -224,7 +224,7 @@ class CmdIC(MuxPlayerCommand):
return
if not new_character:
# search for a matching character
new_character = search.objects(self.args, player)
new_character = search.object_search(self.args, player)
if new_character:
new_character = new_character[0]
else:

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)
#

View file

@ -822,6 +822,7 @@ class TypedObject(SharedMemoryModel):
return any((cls for cls in self.typeclass.__class__.mro()
if any(("%s.%s" % (_GA(cls,"__module__"), _GA(cls,"__name__")) == typec for typec in typeclasses))))
#
# Object manipulation methods
#
@ -874,7 +875,6 @@ class TypedObject(SharedMemoryModel):
# this will automatically use a default class if
# there is an error with the given typeclass.
new_typeclass = self.typeclass
print new_typeclass
if self.typeclass_path != new_typeclass.path and no_default:
# something went wrong; the default was loaded instead,
# and we don't allow that; instead we return to previous.

View file

@ -48,31 +48,46 @@ HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_c
# is reachable from within each command class
# by using self.caller.search()!
#
# def object_search(self, ostring=None,
# attribute_name=None,
# typeclass=None,
# candidates=None,
# exact=True):
#
# Search globally or in a list of candidates and return results. The result is always an Object.
# Always returns a list.
#
# Arguments:
# 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
# "Big sword"). Since this is more expensive than exact matching, it is
# recommended to be used together with the objlist keyword to limit the number
# of possibilities. This value has no meaning if searching for attributes/properties.
#
# Returns:
# A list of matching objects (or a list with one unique match)
# def object_search(self, ostring, caller=None,
# candidates=None,
# attribute_name=None):
# """
# Search as an object and return results.
#
# ostring: (string) The string to compare names against.
# Can be a dbref. If name is appended by *, a player is searched for.
# caller: (Object) The object performing the search.
# candidates (list of Objects): restrict search only to those objects
# attribute_name: (string) Which attribute to search in each object.
# If None, the default 'name' attribute is used.
# """
search_object = ObjectDB.objects.object_search
search_objects = search_object
object_search = search_object
objects = search_objects
#
# Search for players
#
# NOTE: Most usually you would do such searches from
# from inseide command definitions using
# self.caller.search() by appending an '*' to the
# beginning of the search criterion.
#
# def player_search(self, ostring):
# """
# Searches for a particular player by name or
@ -83,6 +98,7 @@ objects = search_objects
search_player = PlayerDB.objects.player_search
search_players = search_player
player_search = search_player
players = search_players
#
@ -100,6 +116,7 @@ players = search_players
search_script = ScriptDB.objects.script_search
search_scripts = search_script
script_search = search_script
scripts = search_scripts
#
# Searching for communication messages
@ -120,6 +137,7 @@ scripts = search_scripts
search_message = Msg.objects.message_search
search_messages = search_message
message_search = search_message
messages = search_messages
#
@ -134,6 +152,7 @@ messages = search_messages
search_channel = Channel.objects.channel_search
search_channels = search_channel
channel_search = search_channel
channels = search_channels
#
@ -149,4 +168,5 @@ channels = search_channels
search_help_entry = HelpEntry.objects.search_help
search_help_entries = search_help_entry
help_entry_search = search_help_entry
help_entries = search_help_entries