diff --git a/src/commands/default/admin.py b/src/commands/default/admin.py index 9976d50bbe..40aa928d99 100644 --- a/src/commands/default/admin.py +++ b/src/commands/default/admin.py @@ -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.* diff --git a/src/commands/default/player.py b/src/commands/default/player.py index 7ef3947906..c49bd6de4b 100644 --- a/src/commands/default/player.py +++ b/src/commands/default/player.py @@ -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: diff --git a/src/objects/manager.py b/src/objects/manager.py index 7117527dac..0ab04edce8 100644 --- a/src/objects/manager.py +++ b/src/objects/manager.py @@ -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: diff --git a/src/objects/models.py b/src/objects/models.py index 8af15b1ad2..91e5a53d87 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -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: - * - 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: # - search by unique dbref. This is always a global search. me,self - self-reference to this object - - 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) # diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 889fba81a8..afea58464b 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -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. diff --git a/src/utils/search.py b/src/utils/search.py index eb0f305a64..a7abbc2354 100644 --- a/src/utils/search.py +++ b/src/utils/search.py @@ -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