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

View file

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

View file

@ -1,19 +1,16 @@
""" """
Custom manager for Objects. Custom manager for Objects.
""" """
try: import cPickle as pickle
except ImportError: import pickle
from django.db.models import Q from django.db.models import Q
from django.conf import settings from django.conf import settings
from django.db.models.fields import exceptions from django.db.models.fields import exceptions
from src.typeclasses.managers import TypedObjectManager from src.typeclasses.managers import TypedObjectManager
from src.typeclasses.managers import returns_typeclass, returns_typeclass_list from src.typeclasses.managers import returns_typeclass, returns_typeclass_list
from src.utils import utils 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",) __all__ = ("ObjectManager",)
_GA = object.__getattribute__ _GA = object.__getattribute__
_DUMPS = lambda inp: to_unicode(pickle.dumps(inp))
# Try to use a custom way to parse id-tagged multimatches. # 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)) return 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): def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None):
""" """
Returns all objects having the valid Returns all objects having the valid attrname set to the given value.
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 candidates - list of candidate objects to search
not make sense to offer an "exact" type matching for this. 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() 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): type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
# simple attribute_value - do direct lookup return self.filter(cand_restriction & type_restriction & Q(objattribute__db_key=attribute_name, objattribute__db_value=attribute_value))
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]
@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):
@ -151,16 +147,19 @@ class ObjectManager(TypedObjectManager):
return [] return []
@returns_typeclass_list @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): if isinstance(property_value, basestring):
property_value = to_unicode(property_value) property_value = to_unicode(property_value)
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()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
try: 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: except exceptions.FieldError:
return [] return []
@ -176,23 +175,26 @@ class ObjectManager(TypedObjectManager):
return self.filter(db_location=location).exclude(exclude_restriction) return self.filter(db_location=location).exclude(exclude_restriction)
@returns_typeclass_list @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 Returns objects based on key or alias match. Will also do fuzzy matching based on
the utils.string_partial_matching function. 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 # 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=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: if exact:
# exact match - do direct search # 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: elif candidates:
# fuzzy with candidates # fuzzy with candidates
key_candidates = self.filter(cand_restriction) key_candidates = self.filter(cand_restriction & type_restriction)
else: else:
# fuzzy without supplied candidates - we select our own candidates # 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] candidates_id = [_GA(obj, "id") for obj in key_candidates]
# fuzzy matching # fuzzy matching
key_strings = key_candidates.values_list("db_key", flat=True) key_strings = key_candidates.values_list("db_key", flat=True)
@ -210,25 +212,25 @@ 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, caller=None, def object_search(self, ostring=None,
attribute_name=None, attribute_name=None,
typeclass=None,
candidates=None, candidates=None,
exact=True): exact=True):
""" """
Search as an object and return results. The result is always an Object. Search as an object globally or in a list of candidates and return results. The result is always an Object.
If * is appended (player search, a Character controlled by this Player Always returns a list.
is looked for. The Character is returned, not the Player. Use player_search
to find Player objects. Always returns a list.
Arguments: Arguments:
ostring: (string) The string to compare names against. ostring: (str) The string to compare names against. By default (if not attribute_name
Can be a dbref. If name is prepended by *, a player is searched for. is set), this will search object.key and object.aliases in order. Can also
caller: (Object) The optional object performing the search. be on the form #dbref, which will, if exact=True be matched against primary key.
attribute_name: (string) Which object attribute to match ostring against. If not attribute_name: (str): Use this named ObjectAttribute to match ostring against, instead
set, the "key" and "aliases" properties are searched in order. of the defaults.
candidates (list obj ObjectDBs): If objlist is supplied, global_search keyword is ignored typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help
and search will only be performed among the candidates in this list. A common list speed up global searches.
of candidates is the contents of the current location searched. 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 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 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 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) A list of matching objects (or a list with one unique match)
""" """
def _searcher(ostring, exact=False): def _searcher(ostring, candidates, typeclass, exact=False):
"Helper method for searching objects" "Helper method for searching objects. typeclass is only used for global searching (no candidates)"
if attribute_name: if attribute_name and isinstance(attribute_name, basestring):
# attribute/property search (always exact). # 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: if matches:
return matches return matches
return self.get_objs_with_attr_value(attribute_name, ostring, candidates=candidates) return self.get_objs_with_attr_value(attribute_name, ostring, candidates=candidates, typeclasses=typeclass)
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 []
else: else:
# normal key/alias search # 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: if not ostring and ostring != 0:
return [] 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: 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. dbref = not attribute_name and exact and self.dbref(ostring)
if candidates == []: if dbref != None:
return []
dbref = not attribute_name and self.dbref(ostring)
if dbref or dbref == 0:
# Easiest case - dbref matching (always exact) # Easiest case - dbref matching (always exact)
dbref_match = self.dbref_search(dbref) dbref_match = self.dbref_search(dbref)
if dbref_match: if dbref_match:
if candidates == None or dbref_match.dbobj in candidates: if not candidates or dbref_match.dbobj in candidates:
return [dbref_match] return [dbref_match]
else: else:
return [] return []
@ -283,12 +286,12 @@ class ObjectManager(TypedObjectManager):
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, exact=True) matches = _searcher(ostring, 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, ostring = _AT_MULTIMATCH_INPUT(ostring)
# run search again, with the exactness set by caller # run search again, with the exactness set by call
matches = _searcher(ostring, exact=exact) matches = _searcher(ostring, 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

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

View file

@ -822,6 +822,7 @@ class TypedObject(SharedMemoryModel):
return any((cls for cls in self.typeclass.__class__.mro() 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)))) if any(("%s.%s" % (_GA(cls,"__module__"), _GA(cls,"__name__")) == typec for typec in typeclasses))))
# #
# Object manipulation methods # Object manipulation methods
# #
@ -874,7 +875,6 @@ class TypedObject(SharedMemoryModel):
# this will automatically use a default class if # this will automatically use a default class if
# there is an error with the given typeclass. # there is an error with the given typeclass.
new_typeclass = self.typeclass new_typeclass = self.typeclass
print new_typeclass
if self.typeclass_path != new_typeclass.path and no_default: if self.typeclass_path != new_typeclass.path and no_default:
# something went wrong; the default was loaded instead, # something went wrong; the default was loaded instead,
# and we don't allow that; instead we return to previous. # 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 # is reachable from within each command class
# by using self.caller.search()! # 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, # def object_search(self, ostring, caller=None,
# candidates=None, # candidates=None,
# attribute_name=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_object = ObjectDB.objects.object_search
search_objects = search_object search_objects = search_object
object_search = search_object
objects = search_objects objects = search_objects
# #
# Search for players # 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): # def player_search(self, ostring):
# """ # """
# Searches for a particular player by name or # Searches for a particular player by name or
@ -83,6 +98,7 @@ objects = search_objects
search_player = PlayerDB.objects.player_search search_player = PlayerDB.objects.player_search
search_players = search_player search_players = search_player
player_search = search_player
players = search_players players = search_players
# #
@ -100,6 +116,7 @@ players = search_players
search_script = ScriptDB.objects.script_search search_script = ScriptDB.objects.script_search
search_scripts = search_script search_scripts = search_script
script_search = search_script
scripts = search_scripts scripts = search_scripts
# #
# Searching for communication messages # Searching for communication messages
@ -120,6 +137,7 @@ scripts = search_scripts
search_message = Msg.objects.message_search search_message = Msg.objects.message_search
search_messages = search_message search_messages = search_message
message_search = search_message
messages = search_messages messages = search_messages
# #
@ -134,6 +152,7 @@ messages = search_messages
search_channel = Channel.objects.channel_search search_channel = Channel.objects.channel_search
search_channels = search_channel search_channels = search_channel
channel_search = search_channel
channels = search_channels channels = search_channels
# #
@ -149,4 +168,5 @@ channels = search_channels
search_help_entry = HelpEntry.objects.search_help search_help_entry = HelpEntry.objects.search_help
search_help_entries = search_help_entry search_help_entries = search_help_entry
help_entry_search = search_help_entry
help_entries = search_help_entries help_entries = search_help_entries